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