| @@ -27,7 +27,7 @@ const NodePrefix = 'workflow'; | |||
| function ActiveLearnInstance() { | |||
| const [experimentInfo, setExperimentInfo] = useState<ActiveLearnData | undefined>(undefined); | |||
| const [instanceInfo, setInstanceInfo] = useState<ActiveLearnInstanceData | undefined>(undefined); | |||
| // 超参数寻优运行有3个节点,运行状态取工作流状态,而不是 auto-hpo 节点状态 | |||
| // 主动学习运行有3个节点,运行状态取工作流状态,而不是 active-learn 节点状态 | |||
| const [workflowStatus, setWorkflowStatus] = useState<NodeStatus | undefined>(undefined); | |||
| const [nodes, setNodes] = useState<Record<string, NodeStatus> | undefined>(undefined); | |||
| const params = useParams(); | |||
| @@ -49,11 +49,14 @@ function ActiveLearnInstance() { | |||
| const [res] = await to(getActiveLearnInsReq(instanceId)); | |||
| if (res && res.data) { | |||
| const info = res.data as ActiveLearnInstanceData; | |||
| const { param, node_status, argo_ins_name, argo_ins_ns, status } = info; | |||
| const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info; | |||
| // 解析配置参数 | |||
| const paramJson = parseJsonText(param); | |||
| if (paramJson) { | |||
| setExperimentInfo(paramJson.data); | |||
| setExperimentInfo({ | |||
| ...paramJson.data, | |||
| create_time, | |||
| }); | |||
| } | |||
| setInstanceInfo(info); | |||
| @@ -206,19 +206,19 @@ function BasicInfo({ info, className, runStatus, isInstance = false }: BasicInfo | |||
| }, [info, getResourceDescription]); | |||
| const instanceDatas = useMemo(() => { | |||
| if (!runStatus) { | |||
| if (!info || !runStatus) { | |||
| return []; | |||
| } | |||
| return [ | |||
| { | |||
| label: '启动时间', | |||
| value: formatDate(runStatus.startedAt), | |||
| value: formatDate(info.create_time), | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '执行时长', | |||
| value: elapsedTime(runStatus.startedAt, runStatus.finishedAt), | |||
| value: elapsedTime(info.create_time, runStatus.finishedAt), | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| @@ -19,6 +19,10 @@ function BasicConfig() { | |||
| required: true, | |||
| message: '请输入实验名称', | |||
| }, | |||
| { | |||
| max: 64, | |||
| message: '实验名称不能超过64个字符', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入实验名称" maxLength={64} showCount allowClear /> | |||
| @@ -5,6 +5,8 @@ import { NodeStatus } from '@/types'; | |||
| import { Tabs } from 'antd'; | |||
| import styles from './index.less'; | |||
| const NodePrefix = 'active-learn'; | |||
| type ExperimentLogProps = { | |||
| instanceInfo: ActiveLearnInstanceData; | |||
| nodes: Record<string, NodeStatus>; | |||
| @@ -23,7 +25,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { | |||
| }) | |||
| .forEach((key) => { | |||
| const node = nodes[key]; | |||
| if (node.displayName.startsWith('active-learn')) { | |||
| if (node.displayName.startsWith(NodePrefix)) { | |||
| hpoNodeStatus = node; | |||
| } else if (node.displayName.startsWith('git-clone') && !frameworkCloneNodeStatus) { | |||
| frameworkCloneNodeStatus = node; | |||
| @@ -1,6 +1,5 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { AutoMLTaskType, ExperimentStatus } from '@/enums'; | |||
| import LogList from '@/pages/Experiment/components/LogList'; | |||
| import { getExperimentInsReq } from '@/services/autoML'; | |||
| import { NodeStatus } from '@/types'; | |||
| import { parseJsonText } from '@/utils'; | |||
| @@ -11,6 +10,7 @@ import { Tabs } from 'antd'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import AutoMLBasic from '../components/AutoMLBasic'; | |||
| import ExperimentHistory from '../components/ExperimentHistory'; | |||
| import ExperimentLog from '../components/ExperimentLog'; | |||
| import ExperimentResult from '../components/ExperimentResult'; | |||
| import { AutoMLData, AutoMLInstanceData } from '../types'; | |||
| import styles from './index.less'; | |||
| @@ -22,11 +22,13 @@ enum TabKeys { | |||
| History = 'history', | |||
| } | |||
| const NodePrefix = 'auto-ml'; | |||
| const NodePrefix = 'workflow'; | |||
| function AutoMLInstance() { | |||
| const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined); | |||
| const [instanceInfo, setInstanceInfo] = useState<AutoMLInstanceData | undefined>(undefined); | |||
| const [workflowStatus, setWorkflowStatus] = useState<NodeStatus | undefined>(undefined); | |||
| const [nodes, setNodes] = useState<Record<string, NodeStatus> | undefined>(undefined); | |||
| const params = useParams(); | |||
| const instanceId = safeInvoke(Number)(params.id); | |||
| const evtSourceRef = useRef<EventSource | null>(null); | |||
| @@ -46,34 +48,39 @@ function AutoMLInstance() { | |||
| const [res] = await to(getExperimentInsReq(instanceId)); | |||
| if (res && res.data) { | |||
| const info = res.data as AutoMLInstanceData; | |||
| const { param, node_status, argo_ins_name, argo_ins_ns, status } = info; | |||
| const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info; | |||
| // 解析配置参数 | |||
| const paramJson = parseJsonText(param); | |||
| if (paramJson) { | |||
| setAutoMLInfo(paramJson); | |||
| setAutoMLInfo({ | |||
| ...paramJson, | |||
| create_time, | |||
| }); | |||
| } | |||
| setInstanceInfo(info); | |||
| // 这个接口返回的状态有延时,SSE 返回的状态是最新的 | |||
| // SSE 调用时,不需要解析 node_status, 也不要重新建立 SSE | |||
| if (isStatusDetermined) { | |||
| setInstanceInfo((prev) => ({ | |||
| ...info, | |||
| nodeStatus: prev!.nodeStatus, | |||
| })); | |||
| return; | |||
| } | |||
| // 进行节点状态 | |||
| const nodeStatusJson = parseJsonText(node_status); | |||
| if (nodeStatusJson) { | |||
| Object.keys(nodeStatusJson).forEach((key) => { | |||
| setNodes(nodeStatusJson); | |||
| // 工作流 | |||
| Object.keys(nodeStatusJson).some((key) => { | |||
| if (key.startsWith(NodePrefix)) { | |||
| const value = nodeStatusJson[key]; | |||
| info.nodeStatus = value; | |||
| const workflowStatus = nodeStatusJson[key]; | |||
| setWorkflowStatus(workflowStatus); | |||
| return true; | |||
| } | |||
| return false; | |||
| }); | |||
| } | |||
| setInstanceInfo(info); | |||
| // 运行中或者等待中,开启 SSE | |||
| if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) { | |||
| setupSSE(argo_ins_name, argo_ins_ns); | |||
| @@ -97,19 +104,20 @@ function AutoMLInstance() { | |||
| if (dataJson) { | |||
| const nodes = dataJson?.result?.object?.status?.nodes; | |||
| if (nodes) { | |||
| const statusData = Object.values(nodes).find((node: any) => | |||
| const workflowStatus = Object.values(nodes).find((node: any) => | |||
| node.displayName.startsWith(NodePrefix), | |||
| ) as NodeStatus; | |||
| if (statusData) { | |||
| setInstanceInfo((prev) => ({ | |||
| ...prev!, | |||
| nodeStatus: statusData, | |||
| })); | |||
| // 节点 | |||
| setNodes(nodes); | |||
| if (workflowStatus) { | |||
| setWorkflowStatus(workflowStatus); | |||
| // 实验结束,关闭 SSE | |||
| if ( | |||
| statusData.phase !== ExperimentStatus.Pending && | |||
| statusData.phase !== ExperimentStatus.Running | |||
| workflowStatus.phase !== ExperimentStatus.Pending && | |||
| workflowStatus.phase !== ExperimentStatus.Running | |||
| ) { | |||
| closeSSE(); | |||
| getExperimentInsInfo(true); | |||
| @@ -141,7 +149,7 @@ function AutoMLInstance() { | |||
| <AutoMLBasic | |||
| className={styles['auto-ml-instance__basic']} | |||
| info={autoMLInfo} | |||
| runStatus={instanceInfo?.nodeStatus} | |||
| runStatus={workflowStatus} | |||
| isInstance | |||
| /> | |||
| ), | |||
| @@ -152,16 +160,7 @@ function AutoMLInstance() { | |||
| icon: <KFIcon type="icon-rizhi1" />, | |||
| children: ( | |||
| <div className={styles['auto-ml-instance__log']}> | |||
| {instanceInfo && instanceInfo.nodeStatus && ( | |||
| <LogList | |||
| instanceName={instanceInfo.argo_ins_name} | |||
| instanceNamespace={instanceInfo.argo_ins_ns} | |||
| pipelineNodeId={instanceInfo.nodeStatus.displayName} | |||
| workflowId={instanceInfo.nodeStatus.id} | |||
| instanceNodeStartTime={instanceInfo.nodeStatus.startedAt} | |||
| instanceNodeStatus={instanceInfo.nodeStatus.phase as ExperimentStatus} | |||
| ></LogList> | |||
| )} | |||
| {instanceInfo && nodes && <ExperimentLog instanceInfo={instanceInfo} nodes={nodes} />} | |||
| </div> | |||
| ), | |||
| }, | |||
| @@ -193,18 +193,18 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB | |||
| }, [info]); | |||
| const instanceDatas = useMemo(() => { | |||
| if (!runStatus) { | |||
| if (!info || !runStatus) { | |||
| return []; | |||
| } | |||
| return [ | |||
| { | |||
| label: '启动时间', | |||
| value: formatDate(runStatus.startedAt), | |||
| value: formatDate(info.create_time), | |||
| }, | |||
| { | |||
| label: '执行时长', | |||
| value: elapsedTime(runStatus.startedAt, runStatus.finishedAt), | |||
| value: elapsedTime(info.create_time, runStatus.finishedAt), | |||
| }, | |||
| { | |||
| label: '状态', | |||
| @@ -18,6 +18,10 @@ function BasicConfig() { | |||
| required: true, | |||
| message: '请输入实验名称', | |||
| }, | |||
| { | |||
| max: 64, | |||
| message: '实验名称不能超过64个字符', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入实验名称" maxLength={64} showCount allowClear /> | |||
| @@ -0,0 +1,7 @@ | |||
| .experiment-log { | |||
| height: 100%; | |||
| &__log { | |||
| height: 100%; | |||
| } | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| import { ExperimentStatus } from '@/enums'; | |||
| import { AutoMLInstanceData } from '@/pages/AutoML/types'; | |||
| import LogList from '@/pages/Experiment/components/LogList'; | |||
| import { NodeStatus } from '@/types'; | |||
| import styles from './index.less'; | |||
| const NodePrefix = 'auto-ml'; | |||
| type ExperimentLogProps = { | |||
| instanceInfo: AutoMLInstanceData; | |||
| nodes: Record<string, NodeStatus>; | |||
| }; | |||
| function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { | |||
| const nodeStatus: NodeStatus | undefined = Object.values(nodes).find((node: any) => | |||
| node.displayName.startsWith(NodePrefix), | |||
| ) as NodeStatus; | |||
| return ( | |||
| <div className={styles['experiment-log']}> | |||
| <div className={styles['experiment-log__log']}> | |||
| {nodeStatus && ( | |||
| <LogList | |||
| instanceName={instanceInfo.argo_ins_name} | |||
| instanceNamespace={instanceInfo.argo_ins_ns} | |||
| pipelineNodeId={nodeStatus.displayName} | |||
| workflowId={nodeStatus.id} | |||
| instanceNodeStartTime={nodeStatus.startedAt} | |||
| instanceNodeStatus={nodeStatus.phase as ExperimentStatus} | |||
| ></LogList> | |||
| )} | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ExperimentLog; | |||
| @@ -51,7 +51,7 @@ function HyperParameterInstance() { | |||
| const [res] = await to(getRayInsReq(instanceId)); | |||
| if (res && res.data) { | |||
| const info = res.data as HyperParameterInstanceData; | |||
| const { param, node_status, argo_ins_name, argo_ins_ns, status } = info; | |||
| const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info; | |||
| // 解析配置参数 | |||
| const paramJson = parseJsonText(param); | |||
| if (paramJson) { | |||
| @@ -70,7 +70,10 @@ function HyperParameterInstance() { | |||
| if (!Array.isArray(paramJson.points_to_evaluate)) { | |||
| paramJson.points_to_evaluate = []; | |||
| } | |||
| setExperimentInfo(paramJson); | |||
| setExperimentInfo({ | |||
| ...paramJson, | |||
| create_time, | |||
| }); | |||
| } | |||
| setInstanceInfo(info); | |||
| @@ -19,6 +19,10 @@ function BasicConfig() { | |||
| required: true, | |||
| message: '请输入实验名称', | |||
| }, | |||
| { | |||
| max: 64, | |||
| message: '实验名称不能超过64个字符', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入实验名称" maxLength={64} showCount allowClear /> | |||
| @@ -5,6 +5,8 @@ import { NodeStatus } from '@/types'; | |||
| import { Tabs } from 'antd'; | |||
| import styles from './index.less'; | |||
| const NodePrefix = 'auto-hpo'; | |||
| type ExperimentLogProps = { | |||
| instanceInfo: HyperParameterInstanceData; | |||
| nodes: Record<string, NodeStatus>; | |||
| @@ -23,7 +25,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { | |||
| }) | |||
| .forEach((key) => { | |||
| const node = nodes[key]; | |||
| if (node.displayName.startsWith('auto-hpo')) { | |||
| if (node.displayName.startsWith(NodePrefix)) { | |||
| hpoNodeStatus = node; | |||
| } else if (node.displayName.startsWith('git-clone') && !frameworkCloneNodeStatus) { | |||
| frameworkCloneNodeStatus = node; | |||
| @@ -143,19 +143,19 @@ function HyperParameterBasic({ | |||
| }, [info, getResourceDescription]); | |||
| const instanceDatas = useMemo(() => { | |||
| if (!runStatus) { | |||
| if (!info || !runStatus) { | |||
| return []; | |||
| } | |||
| return [ | |||
| { | |||
| label: '启动时间', | |||
| value: formatDate(runStatus.startedAt), | |||
| value: formatDate(info.create_time), | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '执行时长', | |||
| value: elapsedTime(runStatus.startedAt, runStatus.finishedAt), | |||
| value: elapsedTime(info.create_time, runStatus.finishedAt), | |||
| ellipsis: true, | |||
| }, | |||
| { | |||