From ff0be129a08e69855a82516124ef9ad695ee6ed2 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Fri, 5 Jul 2024 13:52:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9A=E6=97=B6=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=AE=9E=E9=AA=8C=E5=AE=9E=E4=BE=8B=E8=8A=82=E7=82=B9=E7=8A=B6?= =?UTF-8?q?=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/assets/img/experiment-pending.png | Bin 0 -> 1599 bytes .../src/assets/img/experiment-running.png | Bin 2097 -> 2101 bytes react-ui/src/hooks/index.ts | 7 +- react-ui/src/pages/Experiment/Info/index.jsx | 257 ++++++++++++---- react-ui/src/pages/Experiment/Info/index.less | 6 + react-ui/src/pages/Experiment/Info/props.less | 7 +- react-ui/src/pages/Experiment/Info/props.tsx | 274 ++++++++---------- .../components/ExperimentParameter/index.less | 2 + .../components/ExperimentParameter/index.tsx | 7 +- .../components/ExperimentResult/index.less | 2 + .../components/ExperimentResult/index.tsx | 27 +- .../Experiment/components/LogGroup/index.tsx | 34 ++- .../Experiment/components/LogList/index.less | 2 + .../Experiment/components/LogList/index.tsx | 68 ++++- react-ui/src/pages/Experiment/index.jsx | 24 +- react-ui/src/types.ts | 1 + 16 files changed, 474 insertions(+), 244 deletions(-) create mode 100644 react-ui/src/assets/img/experiment-pending.png diff --git a/react-ui/src/assets/img/experiment-pending.png b/react-ui/src/assets/img/experiment-pending.png new file mode 100644 index 0000000000000000000000000000000000000000..ceefa0278c77c048250f0062ce908975ff73a174 GIT binary patch literal 1599 zcmV-F2Eh4=P)7%g5P;v?-8dWO z61N0#f(ywB!39@6pdzFMiF+HaAetOdlOPdSB|?Zab*fUKf)oR#ifE$PX=v)yyTk0- z4)$i(`_|sK-hlTMdEeiBJ2P+Iya^@sMloF8bv0Ks1r!1&0fWrHQT+ zgu(zST%V&02RMggjs<&X#~mHlY13Gm?Ci&{AG7`C?MkI;3MH{%&*f3OUCr7%q7k<* zbVVqIv)lpbqyRz^L?J`1$7bWz0;vjw<^wX~Fakg`sNsVqBR0xI)%cK#L99%L!?Gft zcUud}IaX1SPX@XZiAp|6MIe@@ItB;`XDgYIdmpO28SI`zr)ux94Z_n93bI!K!xo6! zlU*_F@S#1{wP{U38QRl3H^MqQGl*@W33{n*Xa*Aqj1Mp{qUTxZdZ`^KB+9bAT7p=) z5Dv+Lcm+Z1_L6|?Yk?Go=WSM7t0f5Z3s8szTe|YP#OC&H$ zKu)#x%*M8a97YR3vp{X&vt9c39=+NeKJ)Cq^TMs=sYCnR0zojBf#%U>34(X<)VS}p zYySPQ{Y48|Tb_E(?-~egA~=ajow4FMx0nWRC+QIoqX=Ta3IsQ^P$%{T2m|vd(FiTs z0uqMP{2ot$K=DMHJy@K?DFNX|BQ_-6fvUL)MQ$r2ig|C#sZ8y`LXG{V?n2n6mg z+?oI2&yBz0N1WD zN7}KyD6cPmmAZ6Xh2YS&BUops+(^!_z);%=O{f^s12^Thg~wR?u(Z=BWz;@~>3qyf z)X+RwM5&^UfmxV>;CZi6g$T}cHMs*VbJHcFN2MhW%bAtbaui5oi9pDxM8Nd!^S`JS zX183XrlG#b*H9*Ex0O1qIVQfrG>#~qUaP1T2%eC(|6^4`WLYu8`{v6X#9QMA{k)bS zR1?W=h?w1`re^_ngsE&6{Qr-6$tOpuuA$QWg?0lb#<$-tg1ZDo2n=P(mKss`4%^XR z7)6j~dc_Pv9iSG+^vVoEjhXG$WZMFv!O)6N$vN+u38X=xeJn+$#gJ`-@V3uj_X)Ol z4Kvlu?IzL~@4He~(@>MKk}un5wL3WCBa8;gl@icc?MwyI6d-1&65mLUicCX4P`+MU zh2e5tCX9-T94k(_v1*WGreQRpdqA86P>{nBjWE&>g$jK@=%8bVHas*@0wV$_V@_xQ zSB)^#5{ya}MTj7j2)++9gF(TueFBA#3xX17i4H3@3$-5?jj&pf8l`Tkq^N|{zd;s~ xh2QMP09Ny{ysrremW8?9rVb)cybdA{;vXPDl3^WCAQJ!p002ovPDHLkV1lCY-=qKl literal 0 HcmV?d00001 diff --git a/react-ui/src/assets/img/experiment-running.png b/react-ui/src/assets/img/experiment-running.png index 3155302a67febe5f321b51c9ae34f25244b2b12d..d4121030af50605ff5445f56df3d57f58668b09f 100644 GIT binary patch literal 2101 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 z z49ysgAn2^STterg!(^?<+Q3@D!w(Q{QNvTgWeLgk0M`ZFJ8)~)d37VF8@guZFOTq% zS+XW@3d|#jS|A%xYWAAn%+g1#!GiBVxokC4e8%_;9JmZ;po_0bJ@Q-G=BzdRRG%!I z8rnFOW*hdCPOE6B86~&u?waqfTa~l=#Rp+&5sWP><*Vs(BXW6h% zUwj5~DPG5TbhWL)AWRCNfveTCB<^D0NWZg{=!>06X$QlKCfvH)i!PN@uEFy-PvGK8 z9Sp*=2+O{66iG3(=`!}_^j(9$X)N{w1kdHSx)BKwZ_Ym0WLQ(I4?*l`ncn^xkmqsp zchMGq&QV!^F=~t@c6qV_F{oY^M-p@bS>~1M%nvB+?D!4aKis(St37WQh(jZP+2XHZK>^^V4g5T?ui*vx=da<+wD)|30xe8rJN{c``o;^19@?-S{mCbC$>e%;h#hB9 zf{wt5Dhg?Hy0Y*Ab~wm;vN=eVhbs^rCNe|b84A%sfN`a@r7OtQq=P2>l}OUPXEunV zZgOY=(HTV^2fEvO)HzE@598dFRz9l@1cT9W23ycdH*^c+`79((YjtHY0-{4R>g02{ zBeABC&tgM!>sy*cqb>+J^N3tba3*^y&LO{VvQT%?qM&*Phd_*vL>g;^wV~eo8p^93 zP$LK9v)F2w$&~JT;|}X=x*%zGdy_chRSwS@p)UBpztfe5K5qGyCuu4i)k2_~_gj9H z#Np8hZ7Qf+es*;%sg)DM=%e88!KUVtcH-VvGfukf@@fK3^8zsqC+5mc;4z>XqkFDf z;ddMwwjYt;K+n4goa0rc-3^c6emLI|SMP>PYC((initialValue: T) { */ export function useVisible(initialValue: boolean) { const [visible, setVisible] = useState(initialValue); + const ref = useRef(initialValue); const open = useCallback(() => { setVisible(true); @@ -41,7 +42,11 @@ export function useVisible(initialValue: boolean) { setVisible(false); }, []); - return [visible, open, close] as const; + useEffect(() => { + ref.current = visible; + }, [visible]); + + return [visible, open, close, ref] as const; } type Callback = (state: T) => void; diff --git a/react-ui/src/pages/Experiment/Info/index.jsx b/react-ui/src/pages/Experiment/Info/index.jsx index f8325562..3c1eda2f 100644 --- a/react-ui/src/pages/Experiment/Info/index.jsx +++ b/react-ui/src/pages/Experiment/Info/index.jsx @@ -1,71 +1,39 @@ +import { ExperimentStatus } from '@/enums'; import { useStateRef, useVisible } from '@/hooks'; import { getExperimentIns } from '@/services/experiment/index.js'; import { getWorkflowById } from '@/services/pipeline/index.js'; import themes from '@/styles/theme.less'; import { fittingString } from '@/utils'; import { elapsedTime, formatDate } from '@/utils/date'; -import G6 from '@antv/g6'; +import { to } from '@/utils/promise'; +import G6, { Util } from '@antv/g6'; import { Button } from 'antd'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import ParamsModal from '../components/ViewParamsModal'; import { experimentStatusInfo } from '../status'; import styles from './index.less'; -import Props from './props'; +import ExperimentDrawer from './props'; let graph = null; function ExperimentText() { - const [message, setMessage, messageRef] = useStateRef({}); - const propsRef = useRef(); - const navgite = useNavigate(); + const [experimentIns, setExperimentIns] = useState(undefined); + const [experimentNodeData, setExperimentNodeData, experimentNodeDataRef] = useStateRef(undefined); + const graphRef = useRef(); + const timerRef = useRef(); + const workflowRef = useRef(); const locationParams = useParams(); // 新版本获取路由参数接口 const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); - const graphRef = useRef(); - - const getGraphData = (data) => { - if (graph) { - graph.data(data); - graph.render(); - } else { - setTimeout(() => { - getGraphData(data); - }, 500); - } - }; - const getFirstWorkflow = (val) => { - getWorkflowById(val).then((pipelineRes) => { - if (graph && pipelineRes.data && pipelineRes.data.dag) { - getExperimentIns(locationParams.id).then((experimentRes) => { - if (experimentRes.code === 200) { - setMessage(experimentRes.data); - const experimentStatusObjs = JSON.parse(experimentRes.data.nodes_status); - const newNodeList = JSON.parse(pipelineRes.data.dag).nodes.map((item) => { - return { - ...item, - experimentEndTime: experimentStatusObjs?.[item.id]?.finishedAt, - experimentStartTime: experimentStatusObjs?.[item.id]?.startedAt, - experimentStatus: experimentStatusObjs?.[item.id]?.phase, - component_id: experimentStatusObjs?.[item.id]?.id, - img: experimentStatusObjs?.[item.id]?.phase - ? item.img.slice(0, item.img.length - 4) + - '-' + - experimentStatusObjs[item.id].phase + - '.png' - : item.img, - }; - }); - const newData = { ...JSON.parse(pipelineRes.data.dag), nodes: newNodeList }; - getGraphData(newData); - } - }); - } - }); - }; + const [propsDrawerOpen, openPropsDrawer, closePropsDrawer, propsDrawerOpenRef] = + useVisible(false); + const navigate = useNavigate(); + const width = 110; + const height = 36; useEffect(() => { initGraph(); - getFirstWorkflow(locationParams.workflowId); + getWorkflow(); const changeSize = () => { if (!graph || graph.get('destroyed')) return; @@ -77,9 +45,115 @@ function ExperimentText() { window.addEventListener('resize', changeSize); return () => { window.removeEventListener('resize', changeSize); + if (timerRef.current) { + clearTimeout(timerRef.current); + } }; }, []); + useEffect(() => { + propsDrawerOpenRef.current = propsDrawerOpen; + }, [propsDrawerOpen]); + + // 获取流水线模版 + const getWorkflow = async () => { + const [res] = await to(getWorkflowById(locationParams.workflowId)); + if (res && res.data && res.data.dag) { + try { + const dag = JSON.parse(res.data.dag); + dag.nodes.forEach((item) => { + item.in_parameters = JSON.parse(item.in_parameters); + item.out_parameters = JSON.parse(item.out_parameters); + item.control_strategy = JSON.parse(item.control_strategy); + item.imgName = item.img.slice(0, item.img.length - 4); + }); + workflowRef.current = dag; + getExperimentInstance(true); + } catch (error) { + // JSON.parse 错误 + console.log(error); + } + } + }; + + // 获取实验实例 + const getExperimentInstance = async (first) => { + const [res] = await to(getExperimentIns(locationParams.id)); + if (res && res.data && workflowRef.current) { + setExperimentIns(res.data); + const { status, nodes_status } = res.data; + const workflowData = workflowRef.current; + const experimentStatusObjs = JSON.parse(nodes_status); + workflowData.nodes.forEach((item) => { + const experimentNode = experimentStatusObjs?.[item.id] ?? {}; + const { finishedAt, startedAt, phase, id } = experimentNode; + item.experimentStartTime = startedAt; + item.experimentEndTime = finishedAt; + item.experimentStatus = phase; + item.workflowId = id; + item.img = phase ? `${item.imgName}-${phase}.png` : `${item.imgName}.png`; + }); + + // 更新打开的抽屉数据 + if (propsDrawerOpenRef.current && experimentNodeDataRef.current) { + const currentId = experimentNodeDataRef.current.id; + const node = workflowData.nodes.find((item) => item.id === currentId); + if (node) { + setExperimentNodeData(node); + } + } + + getGraphData(workflowData, first); + + // 运行中或者等待中,每5秒获取一次实验实例 + if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) { + timerRef.current = setTimeout(() => { + getExperimentInstance(false); + }, 5 * 1000); + } + + if (first && status === ExperimentStatus.Pending) { + const node = workflowData.nodes[0]; + if (node) { + setExperimentNodeData(node); + openPropsDrawer(); + } + } else if (first && status === ExperimentStatus.Running) { + const node = + workflowData.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running) ?? + workflowData.nodes[0]; + if (node) { + setExperimentNodeData(node); + openPropsDrawer(); + } + } + } + }; + + // 根据数据,渲染图 + const getGraphData = (data, first) => { + if (graph) { + const zoom = graph.getZoom(); + // 在拉取新数据重新渲染页面之前先获取点(0, 0)在画布上的位置 + const lastPoint = graph.getCanvasByPoint(0, 0); + graph.data(data); + graph.render(); + if (first) { + graph.fitView(); + } else { + graph.zoomTo(zoom); + // 获取重新渲染之后点(0, 0)在画布的位置 + const newPoint = graph.getCanvasByPoint(0, 0); + // 移动画布相对位移; + graph.translate(lastPoint.x - newPoint.x, lastPoint.y - newPoint.y); + } + } else { + setTimeout(() => { + getGraphData(data); + }, 500); + } + }; + const initGraph = () => { G6.registerNode( 'rect-node', @@ -124,6 +198,54 @@ function ExperimentText() { draggable: true, }); } + const hasRightImg = + cfg.experimentStatus === ExperimentStatus.Pending || + cfg.experimentStatus === ExperimentStatus.Running; + if (hasRightImg) { + const image = group.addShape('image', { + attrs: { + x: -10, + y: -10, + width: 20, + height: 20, + img: + cfg.experimentStatus === ExperimentStatus.Pending + ? require('@/assets/img/experiment-pending.png') + : require('@/assets/img/experiment-running.png'), + cursor: 'pointer', + }, + draggable: false, + capture: false, + }); + + if (cfg.experimentStatus === ExperimentStatus.Running) { + image.animate( + (ratio) => { + const toMatrix = Util.transform( + [1, 0, 0, 0, 1, 0, 0, 0, 1], + [ + ['r', ratio * Math.PI * 2], + ['t', width / 2 - 14 + 10, -height / 2 - 6 + 10], + ], + ); + return { + matrix: toMatrix, + }; + }, + { + repeat: true, // 动画重复 + duration: 1000, + easing: 'easeLinear', + }, + ); + } else if (cfg.experimentStatus === ExperimentStatus.Pending) { + const toMatrix = Util.transform( + [1, 0, 0, 0, 1, 0, 0, 0, 1], + [['t', width / 2 - 14 + 10, -height / 2 - 6 + 10]], + ); + image.setMatrix(toMatrix); + } + } const bbox = group.getBBox(); const anchorPoints = this.getAnchorPoints(cfg); anchorPoints.forEach((anchorPos, i) => { @@ -147,12 +269,12 @@ function ExperimentText() { // response the state changes and show/hide the link-point circles setState(name, value, item) { const group = item.getContainer(); - const shape = group.get('children')[0]; + const shape = group.get('children')?.[0]; if (name === 'hover') { if (value) { - shape.attr('stroke', themes['primaryColor']); + shape?.attr('stroke', themes['primaryColor']); } else { - shape.attr('stroke', 'transparent'); + shape?.attr('stroke', 'transparent'); } } }, @@ -189,7 +311,7 @@ function ExperimentText() { defaultNode: { type: 'rect-node', - size: [110, 36], + size: [width, height], labelCfg: { style: { @@ -257,7 +379,9 @@ function ExperimentText() { const bindEvents = () => { graph.on('node:click', (e) => { if (e.target.get('name') !== 'anchor-point' && e.item) { - propsRef.current.showDrawer(e, locationParams.id, messageRef.current); + const model = e.item.getModel(); + setExperimentNodeData(model); + openPropsDrawer(); } }); graph.on('node:mouseenter', (e) => { @@ -272,11 +396,11 @@ function ExperimentText() {
- 启动时间:{formatDate(message.create_time)} + 启动时间:{formatDate(experimentIns?.create_time)}
执行时长: - {elapsedTime(message.create_time, message.finish_time)} + {elapsedTime(experimentIns?.create_time, experimentIns?.finish_time)}
状态: @@ -286,11 +410,11 @@ function ExperimentText() { height: '8px', borderRadius: '50%', marginRight: '6px', - backgroundColor: experimentStatusInfo[message.status]?.color, + backgroundColor: experimentStatusInfo[experimentIns?.status]?.color, }} >
- - {experimentStatusInfo[message.status]?.label} + + {experimentStatusInfo[experimentIns?.status]?.label}
- + {experimentNodeData ? ( + + ) : null} ); diff --git a/react-ui/src/pages/Experiment/Info/index.less b/react-ui/src/pages/Experiment/Info/index.less index f2f5510d..9004ce33 100644 --- a/react-ui/src/pages/Experiment/Info/index.less +++ b/react-ui/src/pages/Experiment/Info/index.less @@ -30,4 +30,10 @@ background-image: url(/assets/images/pipeline-canvas-back.png); background-size: 100% 100%; } + + :global { + .ant-drawer-mask { + background: transparent !important; + } + } } diff --git a/react-ui/src/pages/Experiment/Info/props.less b/react-ui/src/pages/Experiment/Info/props.less index b3294d55..1d5bdc34 100644 --- a/react-ui/src/pages/Experiment/Info/props.less +++ b/react-ui/src/pages/Experiment/Info/props.less @@ -14,7 +14,12 @@ border: 1px solid #e0eaff; } .ant-tabs-content-holder { - overflow-y: auto; + .ant-tabs-content { + height: 100%; + .ant-tabs-tabpane { + height: 100%; + } + } } } } diff --git a/react-ui/src/pages/Experiment/Info/props.tsx b/react-ui/src/pages/Experiment/Info/props.tsx index 48d72c0d..5940756a 100644 --- a/react-ui/src/pages/Experiment/Info/props.tsx +++ b/react-ui/src/pages/Experiment/Info/props.tsx @@ -1,11 +1,9 @@ -import { getNodeResult, getQueryByExperimentLog } from '@/services/experiment/index.js'; +import { ExperimentStatus } from '@/enums'; import { PipelineNodeModelSerialize } from '@/types'; import { elapsedTime, formatDate } from '@/utils/date'; -import { to } from '@/utils/promise'; import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; -import { Drawer, Form, Tabs } from 'antd'; -import dayjs from 'dayjs'; -import { forwardRef, useImperativeHandle, useState } from 'react'; +import { Drawer, Tabs } from 'antd'; +import { forwardRef, useImperativeHandle, useMemo } from 'react'; import ExperimentParameter from '../components/ExperimentParameter'; import ExperimentResult from '../components/ExperimentResult'; import LogList from '../components/LogList'; @@ -19,154 +17,130 @@ export type ExperimentLog = { start_time?: string; // 日志开始时间 }; -const Props = forwardRef((_, ref) => { - const [form] = Form.useForm(); - const [experimentNodeData, setExperimentNodeData] = useState( - {} as PipelineNodeModelSerialize, - ); - const [experimentResults, setExperimentResults] = useState([]); - const [experimentLogList, setExperimentLogList] = useState([]); +type ExperimentDrawerProps = { + open: boolean; + onClose: () => void; + instanceId?: number; // 实验实例 id + instanceName?: string; // 实验实例 name + instanceNamespace?: string; // 实验实例 namespace + instanceNodeData: PipelineNodeModelSerialize; // 节点数据,在定时刷新实验实例状态中不会变化 + workflowId?: string; // 实验实例工作流 id + instanceNodeStatus?: ExperimentStatus; // 在定时刷新实验实例状态中,变化一两次 + instanceNodeStartTime?: string; // 在定时刷新实验实例状态中,变化一两次 + instanceNodeEndTime?: string; // 在定时刷新实验实例状态中,会经常变化 +}; - const items = [ - { - key: '1', - label: '日志详情', - children: ( - - ), - icon: , - }, - { - key: '2', - label: '配置参数', - icon: , - children: , - }, +const ExperimentDrawer = forwardRef( + ( { - key: '3', - label: '输出结果', - children: , - icon: , - }, - ]; - const [open, setOpen] = useState(false); - const onClose = () => { - setOpen(false); - }; - - // 获取实验日志 - const getExperimentLog = async (params: any, start_time: number) => { - const [res] = await to(getQueryByExperimentLog(params)); - if (res && res.data) { - const { log_type, pods, log_detail } = res.data; - if (log_type === 'normal') { - const list = [ - { - ...log_detail, - log_type, - }, - ]; - setExperimentLogList(list); - } else if (log_type === 'resource') { - const list = pods.map((v: string) => ({ - log_type, - pod_name: v, - log_content: '', - start_time, - })); - setExperimentLogList(list); - } - } - }; - - // 获取实验结果 - const getExperimentResult = async (params: any) => { - const [res] = await to(getNodeResult(params)); - if (res && res.data) { - setExperimentResults(res.data); - } - }; + open, + onClose, + instanceId, + instanceName, + instanceNamespace, + instanceNodeData, + workflowId, + instanceNodeStatus, + instanceNodeStartTime, + instanceNodeEndTime, + }: ExperimentDrawerProps, + ref, + ) => { + useImperativeHandle(ref, () => ({})); - useImperativeHandle(ref, () => ({ - showDrawer(e: any, id: string, message: any) { - setOpen(true); + // 如果性能有问题,可以进一步拆解 + const items = useMemo( + () => [ + { + key: '1', + label: '日志详情', + children: ( + + ), + icon: , + }, + { + key: '2', + label: '配置参数', + icon: , + children: , + }, + { + key: '3', + label: '输出结果', + children: ( + + ), + icon: , + }, + ], + [ + instanceNodeData, + instanceId, + instanceName, + instanceNamespace, + instanceNodeStatus, + workflowId, + instanceNodeStartTime, + ], + ); - // 获取实验参数 - const model = e.item.getModel(); - try { - const nodeData = { - ...model, - in_parameters: JSON.parse(model.in_parameters), - out_parameters: JSON.parse(model.out_parameters), - control_strategy: JSON.parse(model.control_strategy), - }; - setExperimentNodeData(nodeData); - form.setFieldsValue(nodeData); - } catch (error) { - console.log(error); - } - - // 获取实验日志和实验结果 - setExperimentLogList([]); - setExperimentResults([]); - // 如果已经运行到了 - if (e.item?.getModel()?.component_id) { - const model = e.item.getModel(); - const start_time = dayjs(model.experimentStartTime).valueOf() * 1.0e6; - const params = { - task_id: model.id, - component_id: model.component_id, - name: message.argo_ins_name, - namespace: message.argo_ins_ns, - start_time: start_time, - }; - getExperimentLog(params, start_time); - getExperimentResult({ id, node_id: model.id }); - } - }, - })); - return ( - -
-
- 任务名称:{experimentNodeData.label} -
-
- 执行状态: -
- - {experimentStatusInfo[experimentNodeData.experimentStatus]?.label} - -
-
- 启动时间:{formatDate(experimentNodeData.experimentStartTime)} -
-
- 耗时: - {elapsedTime( - experimentNodeData.experimentStartTime, - experimentNodeData.experimentEndTime, - )} + return ( + +
+
+ 任务名称:{instanceNodeData.label} +
+
+ 执行状态: + {instanceNodeStatus ? ( + <> +
+ + {experimentStatusInfo[instanceNodeStatus]?.label} + + + ) : ( + '--' + )} +
+
+ 启动时间:{formatDate(instanceNodeStartTime)} +
+
+ 耗时: + {elapsedTime(instanceNodeStartTime, instanceNodeEndTime)} +
-
- - - ); -}); + + + ); + }, +); -export default Props; +export default ExperimentDrawer; diff --git a/react-ui/src/pages/Experiment/components/ExperimentParameter/index.less b/react-ui/src/pages/Experiment/components/ExperimentParameter/index.less index 44b590ea..c5d9824e 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentParameter/index.less +++ b/react-ui/src/pages/Experiment/components/ExperimentParameter/index.less @@ -1,5 +1,7 @@ .experiment-parameter { + height: 100%; padding-top: 8px; + overflow-y: auto; &__title { display: flex; diff --git a/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx index 62be954f..eb516935 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx @@ -3,16 +3,15 @@ import ParameterSelect from '@/components/ParameterSelect'; import SubAreaTitle from '@/components/SubAreaTitle'; import { useComputingResource } from '@/hooks/resource'; import { PipelineNodeModelSerialize } from '@/types'; -import { Form, Input, Select, type FormProps } from 'antd'; +import { Form, Input, Select } from 'antd'; import styles from './index.less'; const { TextArea } = Input; type ExperimentParameterProps = { - form: FormProps['form']; nodeData: PipelineNodeModelSerialize; }; -function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { +function ExperimentParameter({ nodeData }: ExperimentParameterProps) { const [resourceStandardList] = useComputingResource(); // 资源规模 // 控制策略 @@ -42,7 +41,7 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { wrapperCol={{ span: 24, }} - form={form} + initialValues={nodeData} style={{ maxWidth: 600, }} diff --git a/react-ui/src/pages/Experiment/components/ExperimentResult/index.less b/react-ui/src/pages/Experiment/components/ExperimentResult/index.less index 078fe4f2..78684d72 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentResult/index.less +++ b/react-ui/src/pages/Experiment/components/ExperimentResult/index.less @@ -1,5 +1,7 @@ .experiment-result { + height: 100%; padding: 8px; + overflow-y: auto; color: @text-color; font-size: 14px; diff --git a/react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx index e3cbd4da..feabcc3d 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx @@ -1,11 +1,15 @@ +import { getNodeResult } from '@/services/experiment/index.js'; import { downLoadZip } from '@/utils/downloadfile'; import { openAntdModal } from '@/utils/modal'; +import { to } from '@/utils/promise'; import { App, Button } from 'antd'; +import { useEffect, useState } from 'react'; import ExportModelModal from '../ExportModelModal'; import styles from './index.less'; type ExperimentResultProps = { - results?: ExperimentResultData[] | null; + experimentInsId?: number; // 实验实例 id + pipelineNodeId?: string; // 流水线节点 id }; type ExperimentResultData = { @@ -18,8 +22,21 @@ type ExperimentResultData = { }[]; }; -function ExperimentResult({ results }: ExperimentResultProps) { +function ExperimentResult({ experimentInsId, pipelineNodeId }: ExperimentResultProps) { const { message } = App.useApp(); + const [experimentResults, setExperimentResults] = useState([]); + + useEffect(() => { + getExperimentResult({ id: `${experimentInsId}`, node_id: pipelineNodeId }); + }, []); + + // 获取实验结果 + const getExperimentResult = async (params: any) => { + const [res] = await to(getNodeResult(params)); + if (res && res.data) { + setExperimentResults(res.data); + } + }; // 下载 const download = (path: string) => { @@ -40,9 +57,9 @@ function ExperimentResult({ results }: ExperimentResultProps) { return (
- {results && results.length > 0 ? ( - results.map((item) => ( -
+ {experimentResults.length > 0 ? ( + experimentResults.map((item) => ( +
{item.name}