| @@ -10,13 +10,21 @@ type RunDurationProps = { | |||||
| }; | }; | ||||
| function RunDuration({ createTime, finishTime, className, style }: RunDurationProps) { | function RunDuration({ createTime, finishTime, className, style }: RunDurationProps) { | ||||
| const [now] = useServerTime(); | const [now] = useServerTime(); | ||||
| const [currentTime, setCurrentTime] = useState<Date>(now()); | |||||
| const [currentTime, setCurrentTime] = useState<Date>(finishTime ? new Date(finishTime) : now()); | |||||
| // console.log( | |||||
| // 'currentTime', | |||||
| // new Date(createTime ?? 0), | |||||
| // currentTime, | |||||
| // (currentTime.getTime() - new Date(createTime ?? 0).getTime()) / 1000, | |||||
| // ); | |||||
| // 定时刷新耗时 | // 定时刷新耗时 | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (finishTime) { | if (finishTime) { | ||||
| setCurrentTime(new Date(finishTime)); | setCurrentTime(new Date(finishTime)); | ||||
| } else { | } else { | ||||
| setCurrentTime(now()); | |||||
| const timer = setInterval(() => { | const timer = setInterval(() => { | ||||
| setCurrentTime(now()); | setCurrentTime(now()); | ||||
| }, 1000); | }, 1000); | ||||
| @@ -25,6 +33,7 @@ function RunDuration({ createTime, finishTime, className, style }: RunDurationPr | |||||
| }; | }; | ||||
| } | } | ||||
| }, [finishTime, now]); | }, [finishTime, now]); | ||||
| return ( | return ( | ||||
| <span className={className} style={style}> | <span className={className} style={style}> | ||||
| {elapsedTime(createTime, currentTime)} | {elapsedTime(createTime, currentTime)} | ||||
| @@ -1,11 +1,24 @@ | |||||
| import { parseJsonText } from '@/utils'; | |||||
| import { useEffect } from 'react'; | |||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import { NodeStatus } from '@/types'; | import { NodeStatus } from '@/types'; | ||||
| import { parseJsonText } from '@/utils'; | |||||
| import { useEffect } from 'react'; | |||||
| export type MessageHandler = (experimentInsId: number, status: string, finishedAt: string, nodes: Record<string, NodeStatus>) => void | |||||
| export const useSSE = (experimentInsId: number, status: ExperimentStatus, name: string, namespace: string, onMessage: MessageHandler) => { | |||||
| const isRunning = status === ExperimentStatus.Pending || status === ExperimentStatus.Running | |||||
| export type MessageHandler = ( | |||||
| experimentId: number, | |||||
| experimentInsId: number, | |||||
| status: string, | |||||
| finishTime: string, | |||||
| nodes: Record<string, NodeStatus>, | |||||
| ) => void; | |||||
| export const useSSE = ( | |||||
| experimentId: number, | |||||
| experimentInsId: number, | |||||
| status: ExperimentStatus, | |||||
| name: string, | |||||
| namespace: string, | |||||
| onMessage: MessageHandler, | |||||
| ) => { | |||||
| const isRunning = status === ExperimentStatus.Pending || status === ExperimentStatus.Running; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (isRunning) { | if (isRunning) { | ||||
| const { origin } = location; | const { origin } = location; | ||||
| @@ -22,8 +35,8 @@ export const useSSE = (experimentInsId: number, status: ExperimentStatus, name: | |||||
| const dataJson = parseJsonText(data); | const dataJson = parseJsonText(data); | ||||
| const statusData = dataJson?.result?.object?.status; | const statusData = dataJson?.result?.object?.status; | ||||
| if (statusData) { | if (statusData) { | ||||
| const { finishedAt, phase, nodes } = statusData; | |||||
| onMessage(experimentInsId, phase, finishedAt, nodes); | |||||
| const { finishedAt, phase, nodes } = statusData; | |||||
| onMessage(experimentId, experimentInsId, phase, finishedAt, nodes); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -33,8 +46,7 @@ export const useSSE = (experimentInsId: number, status: ExperimentStatus, name: | |||||
| return () => { | return () => { | ||||
| evtSource.close(); | evtSource.close(); | ||||
| } | |||||
| }; | |||||
| } | } | ||||
| }, [experimentInsId, isRunning, name, namespace, onMessage]); | |||||
| }, [experimentId, experimentInsId, isRunning, name, namespace, onMessage]); | |||||
| }; | }; | ||||
| @@ -68,7 +68,7 @@ function ActiveLearnInstance() { | |||||
| return; | return; | ||||
| } | } | ||||
| // 设置节点状态 | |||||
| // 设置总 workflow 状态 | |||||
| const nodeStatusJson = parseJsonText(node_status); | const nodeStatusJson = parseJsonText(node_status); | ||||
| if (nodeStatusJson) { | if (nodeStatusJson) { | ||||
| setNodes(nodeStatusJson); | setNodes(nodeStatusJson); | ||||
| @@ -105,18 +105,17 @@ function ActiveLearnInstance() { | |||||
| if (dataJson) { | if (dataJson) { | ||||
| const nodes = dataJson?.result?.object?.status?.nodes; | const nodes = dataJson?.result?.object?.status?.nodes; | ||||
| if (nodes) { | if (nodes) { | ||||
| // 节点 | |||||
| // 设置节点 | |||||
| setNodes(nodes); | setNodes(nodes); | ||||
| // 设置总 workflow 状态 | |||||
| const workflowStatus = 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 (workflowStatus) { | if (workflowStatus) { | ||||
| setWorkflowStatus(workflowStatus); | setWorkflowStatus(workflowStatus); | ||||
| // 实验结束,关闭 SSE | |||||
| // 实验结束,关闭 SSE,获取实验实例结果 | |||||
| if ( | if ( | ||||
| workflowStatus.phase !== ExperimentStatus.Pending && | workflowStatus.phase !== ExperimentStatus.Pending && | ||||
| workflowStatus.phase !== ExperimentStatus.Running | workflowStatus.phase !== ExperimentStatus.Running | ||||
| @@ -151,7 +150,7 @@ function ActiveLearnInstance() { | |||||
| <ActiveLearnBasic | <ActiveLearnBasic | ||||
| className={styles['active-learn-instance__basic']} | className={styles['active-learn-instance__basic']} | ||||
| info={experimentInfo} | info={experimentInfo} | ||||
| runStatus={workflowStatus} | |||||
| workflowStatus={workflowStatus} | |||||
| instanceStatus={instanceInfo?.status as ExperimentStatus} | instanceStatus={instanceInfo?.status as ExperimentStatus} | ||||
| isInstance | isInstance | ||||
| /> | /> | ||||
| @@ -28,14 +28,14 @@ type BasicInfoProps = { | |||||
| info?: ActiveLearnData; | info?: ActiveLearnData; | ||||
| className?: string; | className?: string; | ||||
| isInstance?: boolean; | isInstance?: boolean; | ||||
| runStatus?: NodeStatus; | |||||
| workflowStatus?: NodeStatus; | |||||
| instanceStatus?: ExperimentStatus; | instanceStatus?: ExperimentStatus; | ||||
| }; | }; | ||||
| function BasicInfo({ | function BasicInfo({ | ||||
| info, | info, | ||||
| className, | className, | ||||
| runStatus, | |||||
| workflowStatus, | |||||
| instanceStatus, | instanceStatus, | ||||
| isInstance = false, | isInstance = false, | ||||
| }: BasicInfoProps) { | }: BasicInfoProps) { | ||||
| @@ -212,8 +212,8 @@ function BasicInfo({ | |||||
| return ( | return ( | ||||
| <div className={classNames(styles['active-learn-basic'], className)}> | <div className={classNames(styles['active-learn-basic'], className)}> | ||||
| {isInstance && runStatus && ( | |||||
| <ExperimentRunBasic runStatus={runStatus} instanceStatus={instanceStatus} /> | |||||
| {isInstance && workflowStatus && ( | |||||
| <ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} /> | |||||
| )} | )} | ||||
| {!isInstance && ( | {!isInstance && ( | ||||
| <ConfigInfo | <ConfigInfo | ||||
| @@ -112,17 +112,17 @@ function AutoMLInstance() { | |||||
| if (dataJson) { | if (dataJson) { | ||||
| const nodes = dataJson?.result?.object?.status?.nodes; | const nodes = dataJson?.result?.object?.status?.nodes; | ||||
| if (nodes) { | if (nodes) { | ||||
| // 节点 | |||||
| // 设置节点 | |||||
| setNodes(nodes); | setNodes(nodes); | ||||
| // 设置总 workflow 状态 | |||||
| const workflowStatus = 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 (workflowStatus) { | if (workflowStatus) { | ||||
| setWorkflowStatus(workflowStatus); | setWorkflowStatus(workflowStatus); | ||||
| // 实验结束,关闭 SSE | |||||
| // 实验结束,关闭 SSE,获取实验实例结果 | |||||
| if ( | if ( | ||||
| workflowStatus.phase !== ExperimentStatus.Pending && | workflowStatus.phase !== ExperimentStatus.Pending && | ||||
| workflowStatus.phase !== ExperimentStatus.Running | workflowStatus.phase !== ExperimentStatus.Running | ||||
| @@ -157,7 +157,7 @@ function AutoMLInstance() { | |||||
| <AutoMLBasic | <AutoMLBasic | ||||
| className={styles['auto-ml-instance__basic']} | className={styles['auto-ml-instance__basic']} | ||||
| info={autoMLInfo} | info={autoMLInfo} | ||||
| runStatus={workflowStatus} | |||||
| workflowStatus={workflowStatus} | |||||
| instanceStatus={instanceInfo?.status as ExperimentStatus} | instanceStatus={instanceInfo?.status as ExperimentStatus} | ||||
| isInstance | isInstance | ||||
| /> | /> | ||||
| @@ -38,7 +38,7 @@ type AutoMLBasicProps = { | |||||
| info?: AutoMLData; | info?: AutoMLData; | ||||
| className?: string; | className?: string; | ||||
| isInstance?: boolean; | isInstance?: boolean; | ||||
| runStatus?: NodeStatus; | |||||
| workflowStatus?: NodeStatus; | |||||
| instanceStatus?: ExperimentStatus; | instanceStatus?: ExperimentStatus; | ||||
| instanceCreateTime?: string; | instanceCreateTime?: string; | ||||
| }; | }; | ||||
| @@ -46,7 +46,7 @@ type AutoMLBasicProps = { | |||||
| function AutoMLBasic({ | function AutoMLBasic({ | ||||
| info, | info, | ||||
| className, | className, | ||||
| runStatus, | |||||
| workflowStatus, | |||||
| instanceStatus, | instanceStatus, | ||||
| isInstance = false, | isInstance = false, | ||||
| }: AutoMLBasicProps) { | }: AutoMLBasicProps) { | ||||
| @@ -293,8 +293,8 @@ function AutoMLBasic({ | |||||
| return ( | return ( | ||||
| <div className={classNames(styles['auto-ml-basic'], className)}> | <div className={classNames(styles['auto-ml-basic'], className)}> | ||||
| {isInstance && runStatus && ( | |||||
| <ExperimentRunBasic runStatus={runStatus} instanceStatus={instanceStatus} /> | |||||
| {isInstance && workflowStatus && ( | |||||
| <ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} /> | |||||
| )} | )} | ||||
| {!isInstance && ( | {!isInstance && ( | ||||
| <ConfigInfo | <ConfigInfo | ||||
| @@ -54,10 +54,6 @@ | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| width: 200px; | width: 200px; | ||||
| .statusIcon { | |||||
| visibility: visible; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -182,7 +182,10 @@ function ExperimentInstanceList({ | |||||
| > | > | ||||
| {index + 1} | {index + 1} | ||||
| </a> | </a> | ||||
| <ExperimentInstanceComponent instance={item}></ExperimentInstanceComponent> | |||||
| <ExperimentInstanceComponent | |||||
| experimentId={item[config['idInsProperty'] as keyof ExperimentInstance] as number} | |||||
| instance={item} | |||||
| ></ExperimentInstanceComponent> | |||||
| <div className={styles.operation}> | <div className={styles.operation}> | ||||
| <Button | <Button | ||||
| type="link" | type="link" | ||||
| @@ -3,38 +3,41 @@ import { ExperimentStatus } from '@/enums'; | |||||
| import { useSSE, type MessageHandler } from '@/hooks/useSSE'; | import { useSSE, type MessageHandler } from '@/hooks/useSSE'; | ||||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | import { experimentStatusInfo } from '@/pages/Experiment/status'; | ||||
| import { ExperimentInstance, NodeStatus } from '@/types'; | import { ExperimentInstance, NodeStatus } from '@/types'; | ||||
| import { getWorkflowStatus } from '@/utils'; | |||||
| import { ExperimentCompleted } from '@/utils/constant'; | import { ExperimentCompleted } from '@/utils/constant'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { getExperimentInstanceStatus, getWorkflowStatus } from '@/utils/experiment'; | |||||
| import { Typography } from 'antd'; | import { Typography } from 'antd'; | ||||
| import React, { useCallback } from 'react'; | import React, { useCallback } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ExperimentInstanceComponentProps = { | type ExperimentInstanceComponentProps = { | ||||
| experimentId: number; | |||||
| instance: ExperimentInstance; | instance: ExperimentInstance; | ||||
| }; | }; | ||||
| function ExperimentInstanceComponent({ instance }: ExperimentInstanceComponentProps) { | |||||
| const { id, argo_ins_name, argo_ins_ns, node_status, create_time, finish_time } = instance; | |||||
| const status = instance.status as ExperimentStatus; | |||||
| function ExperimentInstanceComponent({ experimentId, instance }: ExperimentInstanceComponentProps) { | |||||
| const { id, argo_ins_name, argo_ins_ns, node_status } = instance; | |||||
| const workflowStatus = getWorkflowStatus(node_status) as NodeStatus | undefined; | const workflowStatus = getWorkflowStatus(node_status) as NodeStatus | undefined; | ||||
| const createTime = workflowStatus?.startedAt ?? create_time; | |||||
| const finishTime = workflowStatus?.finishedAt ?? finish_time; | |||||
| const status = getExperimentInstanceStatus(instance.status as ExperimentStatus); | |||||
| const createTime = workflowStatus?.startedAt; | |||||
| const finishTime = workflowStatus?.finishedAt; | |||||
| const statusInfo = experimentStatusInfo[status]; | |||||
| const handleSSEMessage: MessageHandler = useCallback( | const handleSSEMessage: MessageHandler = useCallback( | ||||
| (experimentInsId: number, status: string, finish_time: string) => { | |||||
| (experimentId: number, experimentInsId: number, status: string, finishTime: string) => { | |||||
| window.postMessage({ | window.postMessage({ | ||||
| type: ExperimentCompleted, | type: ExperimentCompleted, | ||||
| payload: { | payload: { | ||||
| id: experimentInsId, | |||||
| experimentId, | |||||
| experimentInsId, | |||||
| status, | status, | ||||
| finish_time, | |||||
| finishTime, | |||||
| }, | }, | ||||
| }); | }); | ||||
| }, | }, | ||||
| [], | [], | ||||
| ); | ); | ||||
| useSSE(id, status, argo_ins_name, argo_ins_ns, handleSSEMessage); | |||||
| useSSE(experimentId, id, status, argo_ins_name, argo_ins_ns, handleSSEMessage); | |||||
| return ( | return ( | ||||
| <React.Fragment> | <React.Fragment> | ||||
| @@ -47,15 +50,19 @@ function ExperimentInstanceComponent({ instance }: ExperimentInstanceComponentPr | |||||
| </Typography.Text> | </Typography.Text> | ||||
| </div> | </div> | ||||
| <div className={styles.statusBox}> | <div className={styles.statusBox}> | ||||
| <img | |||||
| style={{ width: '17px', marginRight: '7px' }} | |||||
| src={experimentStatusInfo[status]?.icon} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <span style={{ color: experimentStatusInfo[status]?.color }} className={styles.statusIcon}> | |||||
| {experimentStatusInfo[status]?.label} | |||||
| </span> | |||||
| {statusInfo ? ( | |||||
| <> | |||||
| <img | |||||
| style={{ width: '17px', marginRight: '7px' }} | |||||
| src={statusInfo.icon} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <span style={{ color: statusInfo.color }}>{statusInfo.label}</span> | |||||
| </> | |||||
| ) : ( | |||||
| '--' | |||||
| )} | |||||
| </div> | </div> | ||||
| </React.Fragment> | </React.Fragment> | ||||
| ); | ); | ||||
| @@ -8,6 +8,7 @@ import { | |||||
| batchDeleteActiveLearnInsReq, | batchDeleteActiveLearnInsReq, | ||||
| deleteActiveLearnInsReq, | deleteActiveLearnInsReq, | ||||
| deleteActiveLearnReq, | deleteActiveLearnReq, | ||||
| editActiveLearnInsReq, | |||||
| getActiveLearnInsListReq, | getActiveLearnInsListReq, | ||||
| getActiveLearnListReq, | getActiveLearnListReq, | ||||
| runActiveLearnReq, | runActiveLearnReq, | ||||
| @@ -17,6 +18,7 @@ import { | |||||
| batchDeleteExperimentInsReq, | batchDeleteExperimentInsReq, | ||||
| deleteAutoMLReq, | deleteAutoMLReq, | ||||
| deleteExperimentInsReq, | deleteExperimentInsReq, | ||||
| editExperimentInsReq, | |||||
| getAutoMLListReq, | getAutoMLListReq, | ||||
| getExperimentInsListReq, | getExperimentInsListReq, | ||||
| runAutoMLReq, | runAutoMLReq, | ||||
| @@ -26,6 +28,7 @@ import { | |||||
| batchDeleteRayInsReq, | batchDeleteRayInsReq, | ||||
| deleteRayInsReq, | deleteRayInsReq, | ||||
| deleteRayReq, | deleteRayReq, | ||||
| editRayInsReq, | |||||
| getRayInsListReq, | getRayInsListReq, | ||||
| getRayListReq, | getRayListReq, | ||||
| runRayReq, | runRayReq, | ||||
| @@ -40,17 +43,19 @@ export enum ExperimentListType { | |||||
| type ExperimentListInfo = { | type ExperimentListInfo = { | ||||
| getListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取列表 | getListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取列表 | ||||
| getInsListReq: (params: any) => Promise<any>; // 获取实例列表 | |||||
| getInsListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取实例列表 | |||||
| deleteRecordReq: (params: any) => Promise<any>; // 删除 | deleteRecordReq: (params: any) => Promise<any>; // 删除 | ||||
| runRecordReq: (params: any) => Promise<any>; // 运行 | runRecordReq: (params: any) => Promise<any>; // 运行 | ||||
| deleteInsReq: (params: any) => Promise<any>; // 删除实例 | deleteInsReq: (params: any) => Promise<any>; // 删除实例 | ||||
| batchDeleteInsReq: (params: any) => Promise<any>; // 批量删除实例 | batchDeleteInsReq: (params: any) => Promise<any>; // 批量删除实例 | ||||
| stopInsReq: (params: any) => Promise<any>; // 终止实例 | stopInsReq: (params: any) => Promise<any>; // 终止实例 | ||||
| editInsReq: (params: any) => Promise<any>; // 编辑实例 | |||||
| title: string; // 标题 | title: string; // 标题 | ||||
| pathPrefix: string; // 路由路径前缀 | pathPrefix: string; // 路由路径前缀 | ||||
| idProperty: string; // ID属性 | idProperty: string; // ID属性 | ||||
| nameProperty: string; // 名称属性 | nameProperty: string; // 名称属性 | ||||
| descProperty: string; // 描述属性 | descProperty: string; // 描述属性 | ||||
| idInsProperty: string; // 实例返回的ID属性 | |||||
| }; | }; | ||||
| export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo> = { | export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo> = { | ||||
| @@ -62,11 +67,13 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo | |||||
| deleteInsReq: deleteExperimentInsReq, | deleteInsReq: deleteExperimentInsReq, | ||||
| batchDeleteInsReq: batchDeleteExperimentInsReq, | batchDeleteInsReq: batchDeleteExperimentInsReq, | ||||
| stopInsReq: stopExperimentInsReq, | stopInsReq: stopExperimentInsReq, | ||||
| editInsReq: editExperimentInsReq, | |||||
| title: '自主机器学习', | title: '自主机器学习', | ||||
| pathPrefix: 'automl', | pathPrefix: 'automl', | ||||
| nameProperty: 'name', | nameProperty: 'name', | ||||
| descProperty: 'description', | descProperty: 'description', | ||||
| idProperty: 'machineLearnId', | idProperty: 'machineLearnId', | ||||
| idInsProperty: 'machine_learn_id', | |||||
| }, | }, | ||||
| [ExperimentListType.HyperParameter]: { | [ExperimentListType.HyperParameter]: { | ||||
| getListReq: getRayListReq, | getListReq: getRayListReq, | ||||
| @@ -76,11 +83,13 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo | |||||
| deleteInsReq: deleteRayInsReq, | deleteInsReq: deleteRayInsReq, | ||||
| batchDeleteInsReq: batchDeleteRayInsReq, | batchDeleteInsReq: batchDeleteRayInsReq, | ||||
| stopInsReq: stopRayInsReq, | stopInsReq: stopRayInsReq, | ||||
| editInsReq: editRayInsReq, | |||||
| title: '超参数自动寻优', | title: '超参数自动寻优', | ||||
| pathPrefix: 'hyperparameter', | pathPrefix: 'hyperparameter', | ||||
| nameProperty: 'name', | nameProperty: 'name', | ||||
| descProperty: 'description', | descProperty: 'description', | ||||
| idProperty: 'rayId', | idProperty: 'rayId', | ||||
| idInsProperty: 'ray_id', | |||||
| }, | }, | ||||
| [ExperimentListType.ActiveLearn]: { | [ExperimentListType.ActiveLearn]: { | ||||
| getListReq: getActiveLearnListReq, | getListReq: getActiveLearnListReq, | ||||
| @@ -90,10 +99,12 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo | |||||
| deleteInsReq: deleteActiveLearnInsReq, | deleteInsReq: deleteActiveLearnInsReq, | ||||
| batchDeleteInsReq: batchDeleteActiveLearnInsReq, | batchDeleteInsReq: batchDeleteActiveLearnInsReq, | ||||
| stopInsReq: stopActiveLearnInsReq, | stopInsReq: stopActiveLearnInsReq, | ||||
| editInsReq: editActiveLearnInsReq, | |||||
| title: '自动学习', | title: '自动学习', | ||||
| pathPrefix: 'active-learn', | pathPrefix: 'active-learn', | ||||
| nameProperty: 'name', | nameProperty: 'name', | ||||
| descProperty: 'description', | descProperty: 'description', | ||||
| idProperty: 'activeLearnId', | idProperty: 'activeLearnId', | ||||
| idInsProperty: 'active_learn_id', | |||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -30,7 +30,7 @@ import { | |||||
| } from 'antd'; | } from 'antd'; | ||||
| import { type SearchProps } from 'antd/es/input'; | import { type SearchProps } from 'antd/es/input'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useCallback, useEffect, useRef, useState } from 'react'; | |||||
| import { useCallback, useEffect, useState } from 'react'; | |||||
| import ExperimentInstanceList from '../ExperimentInstanceList'; | import ExperimentInstanceList from '../ExperimentInstanceList'; | ||||
| import { ExperimentListType, experimentListConfig } from './config'; | import { ExperimentListType, experimentListConfig } from './config'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -52,7 +52,6 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| const [experimentInsList, setExperimentInsList] = useState<ExperimentInstanceData[]>([]); | const [experimentInsList, setExperimentInsList] = useState<ExperimentInstanceData[]>([]); | ||||
| const [expandedRowKeys, setExpandedRowKeys] = useState<number[]>([]); | const [expandedRowKeys, setExpandedRowKeys] = useState<number[]>([]); | ||||
| const [experimentInsTotal, setExperimentInsTotal] = useState(0); | const [experimentInsTotal, setExperimentInsTotal] = useState(0); | ||||
| const [now] = useServerTime(); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | const [pagination, setPagination] = useState<TablePaginationConfig>( | ||||
| cacheState?.pagination ?? { | cacheState?.pagination ?? { | ||||
| current: 1, | current: 1, | ||||
| @@ -60,10 +59,10 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| }, | }, | ||||
| ); | ); | ||||
| const config = experimentListConfig[type]; | const config = experimentListConfig[type]; | ||||
| const timerRef = useRef<ReturnType<typeof window.setTimeout> | undefined>(); | |||||
| const [now] = useServerTime(); | |||||
| // 获取自主机器学习或超参数自动优化列表 | |||||
| const getAutoMLList = useCallback( | |||||
| // 获取实验列表 | |||||
| const getExperimentList = useCallback( | |||||
| async (skipLoading: boolean = false) => { | async (skipLoading: boolean = false) => { | ||||
| const params: Record<string, any> = { | const params: Record<string, any> = { | ||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| @@ -81,20 +80,16 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| [pagination, searchText, config], | [pagination, searchText, config], | ||||
| ); | ); | ||||
| useEffect(() => { | |||||
| getAutoMLList(); | |||||
| }, [getAutoMLList]); | |||||
| // 获取实验实例列表 | // 获取实验实例列表 | ||||
| const getExperimentInsList = useCallback( | const getExperimentInsList = useCallback( | ||||
| async (recordId: number, page: number, size: number) => { | |||||
| async (recordId: number, page: number, size: number, skipLoading: boolean = false) => { | |||||
| const params = { | const params = { | ||||
| [config.idProperty]: recordId, | [config.idProperty]: recordId, | ||||
| page: page, | page: page, | ||||
| size: size, | size: size, | ||||
| }; | }; | ||||
| const request = config.getInsListReq; | const request = config.getInsListReq; | ||||
| const [res] = await to(request(params)); | |||||
| const [res] = await to(request(params, skipLoading)); | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { content = [], totalElements = 0 } = res.data; | const { content = [], totalElements = 0 } = res.data; | ||||
| try { | try { | ||||
| @@ -116,60 +111,113 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| // TODO: 目前是直接刷新实验列表,后续需要优化,只刷新状态 | // TODO: 目前是直接刷新实验列表,后续需要优化,只刷新状态 | ||||
| const refreshExperimentList = useCallback( | const refreshExperimentList = useCallback( | ||||
| (skipLoading: boolean = false) => { | (skipLoading: boolean = false) => { | ||||
| getAutoMLList(skipLoading); | |||||
| getExperimentList(skipLoading); | |||||
| }, | }, | ||||
| [getAutoMLList], | |||||
| [getExperimentList], | |||||
| ); | ); | ||||
| // 刷新实验实例列表 | // 刷新实验实例列表 | ||||
| const refreshExperimentIns = useCallback( | const refreshExperimentIns = useCallback( | ||||
| (experimentId: number) => { | |||||
| (experimentId: number, skipLoading: boolean = false) => { | |||||
| const length = experimentInsList.length; | const length = experimentInsList.length; | ||||
| getExperimentInsList(experimentId, 0, length); | |||||
| getExperimentInsList(experimentId, 0, length, skipLoading); | |||||
| }, | }, | ||||
| [getExperimentInsList, experimentInsList], | [getExperimentInsList, experimentInsList], | ||||
| ); | ); | ||||
| // 新增,删除版本时,重置分页,然后刷新版本列表 | |||||
| // 更新实验实例状态 | |||||
| const editExperimentIns = useCallback( | |||||
| async ( | |||||
| experimentId: number, | |||||
| experimentInsId: number, | |||||
| status: ExperimentStatus, | |||||
| argo_ins_name: string, | |||||
| argo_ins_ns: string, | |||||
| ) => { | |||||
| const params = { | |||||
| [config.idInsProperty]: experimentId, | |||||
| id: experimentInsId, | |||||
| status: status, | |||||
| argo_ins_name, | |||||
| argo_ins_ns, | |||||
| }; | |||||
| const request = config.editInsReq; | |||||
| const [res] = await to(request(params)); | |||||
| if (res && res.data) { | |||||
| refreshExperimentIns(experimentId, true); | |||||
| refreshExperimentList(true); | |||||
| } | |||||
| }, | |||||
| [config, refreshExperimentIns, refreshExperimentList], | |||||
| ); | |||||
| // 获取实验列表 | |||||
| useEffect(() => { | |||||
| getExperimentList(); | |||||
| }, [getExperimentList]); | |||||
| // expandedRowKeys 变化 | |||||
| useEffect(() => { | |||||
| if (expandedRowKeys.length > 0) { | |||||
| getExperimentInsList(expandedRowKeys[0], 0, 5); | |||||
| refreshExperimentList(); | |||||
| } | |||||
| }, [expandedRowKeys, getExperimentInsList, refreshExperimentList]); | |||||
| // 实验实例状态变化 | |||||
| useEffect(() => { | useEffect(() => { | ||||
| const handleMessage = (e: MessageEvent) => { | const handleMessage = (e: MessageEvent) => { | ||||
| const { type, payload } = e.data; | const { type, payload } = e.data; | ||||
| if (type === ExperimentCompleted) { | if (type === ExperimentCompleted) { | ||||
| const { id, status, finish_time } = payload; | |||||
| // 修改实例的状态和结束时间 | |||||
| setExperimentInsList((prev) => | |||||
| prev.map((v) => | |||||
| v.id === id | |||||
| ? { | |||||
| ...v, | |||||
| status: status, | |||||
| finish_time: finish_time, | |||||
| } | |||||
| : v, | |||||
| ), | |||||
| const { experimentId, experimentInsId, status, finishTime } = payload; | |||||
| const currentIns = experimentInsList.find((v) => v.id === experimentInsId); | |||||
| console.log( | |||||
| '实验实例状态变化', | |||||
| currentIns?.status, | |||||
| status, | |||||
| experimentId, | |||||
| experimentInsId, | |||||
| finishTime, | |||||
| ); | ); | ||||
| if (timerRef.current) { | |||||
| clearTimeout(timerRef.current); | |||||
| timerRef.current = undefined; | |||||
| if ( | |||||
| !currentIns || | |||||
| currentIns.status === ExperimentStatus.Terminated || | |||||
| currentIns.status === status | |||||
| ) { | |||||
| return; | |||||
| } | } | ||||
| timerRef.current = setTimeout(() => { | |||||
| refreshExperimentList(true); | |||||
| }, 10000); | |||||
| // refreshExperimentList(true); | |||||
| // refreshExperimentIns(experimentId); | |||||
| editExperimentIns( | |||||
| experimentId, | |||||
| experimentInsId, | |||||
| status, | |||||
| currentIns.argo_ins_name, | |||||
| currentIns.argo_ins_ns, | |||||
| ); | |||||
| // 修改实例的状态和结束时间 | |||||
| // setExperimentInsList((prev) => | |||||
| // prev.map((v) => | |||||
| // v.id === experimentInsId | |||||
| // ? { | |||||
| // ...v, | |||||
| // status: status, | |||||
| // finish_time: finishTime, | |||||
| // } | |||||
| // : v, | |||||
| // ), | |||||
| // ); | |||||
| } | } | ||||
| }; | }; | ||||
| window.addEventListener('message', handleMessage); | window.addEventListener('message', handleMessage); | ||||
| return () => { | return () => { | ||||
| window.removeEventListener('message', handleMessage); | window.removeEventListener('message', handleMessage); | ||||
| if (timerRef.current) { | |||||
| clearTimeout(timerRef.current); | |||||
| timerRef.current = undefined; | |||||
| } | |||||
| }; | }; | ||||
| }, [refreshExperimentList]); | |||||
| }, [experimentInsList, editExperimentIns]); | |||||
| // 搜索 | // 搜索 | ||||
| const onSearch: SearchProps['onSearch'] = (value) => { | const onSearch: SearchProps['onSearch'] = (value) => { | ||||
| @@ -213,6 +261,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| setCacheState({ | setCacheState({ | ||||
| pagination, | pagination, | ||||
| searchText, | searchText, | ||||
| expandedRowKeys, | |||||
| }); | }); | ||||
| if (record) { | if (record) { | ||||
| @@ -231,6 +280,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| setCacheState({ | setCacheState({ | ||||
| pagination, | pagination, | ||||
| searchText, | searchText, | ||||
| expandedRowKeys, | |||||
| }); | }); | ||||
| navigate(`info/${record.id}`); | navigate(`info/${record.id}`); | ||||
| @@ -243,8 +293,8 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| if (res) { | if (res) { | ||||
| message.success('运行成功'); | message.success('运行成功'); | ||||
| setExpandedRowKeys([record.id]); | setExpandedRowKeys([record.id]); | ||||
| refreshExperimentList(); | |||||
| getExperimentInsList(record.id, 0, 5); | |||||
| // getExperimentInsList(record.id, 0, 5); | |||||
| // refreshExperimentList(); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -254,8 +304,8 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| setExperimentInsList([]); | setExperimentInsList([]); | ||||
| if (expanded) { | if (expanded) { | ||||
| setExpandedRowKeys([record.id]); | setExpandedRowKeys([record.id]); | ||||
| getExperimentInsList(record.id, 0, 5); | |||||
| refreshExperimentList(); | |||||
| // getExperimentInsList(record.id, 0, 5); | |||||
| // refreshExperimentList(); | |||||
| } else { | } else { | ||||
| setExpandedRowKeys([]); | setExpandedRowKeys([]); | ||||
| } | } | ||||
| @@ -263,6 +313,11 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| // 跳转到实验实例详情 | // 跳转到实验实例详情 | ||||
| const gotoInstanceInfo = (autoML: AutoMLData, record: ExperimentInstanceData) => { | const gotoInstanceInfo = (autoML: AutoMLData, record: ExperimentInstanceData) => { | ||||
| setCacheState({ | |||||
| pagination, | |||||
| searchText, | |||||
| expandedRowKeys, | |||||
| }); | |||||
| navigate(`instance/${autoML.id}/${record.id}`); | navigate(`instance/${autoML.id}/${record.id}`); | ||||
| }; | }; | ||||
| @@ -275,8 +330,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| // 实验实例终止 | // 实验实例终止 | ||||
| const handleInstanceTerminate = async (experimentIns: ExperimentInstanceData) => { | const handleInstanceTerminate = async (experimentIns: ExperimentInstanceData) => { | ||||
| // 刷新实验列表 | |||||
| refreshExperimentList(); | |||||
| // 修改实例的状态和结束时间 | |||||
| setExperimentInsList((prevList) => { | setExperimentInsList((prevList) => { | ||||
| return prevList.map((item) => { | return prevList.map((item) => { | ||||
| if (item.id === experimentIns.id) { | if (item.id === experimentIns.id) { | ||||
| @@ -289,6 +343,11 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| return item; | return item; | ||||
| }); | }); | ||||
| }); | }); | ||||
| // 刷新实验列表 | |||||
| refreshExperimentList(true); | |||||
| if (expandedRowKeys.length > 0) { | |||||
| refreshExperimentIns(expandedRowKeys[0]); | |||||
| } | |||||
| }; | }; | ||||
| // --------------------------- Table --------------------------- | // --------------------------- Table --------------------------- | ||||
| // 分页切换 | // 分页切换 | ||||
| @@ -3,58 +3,61 @@ import RunDuration from '@/components/RunDuration'; | |||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | import { experimentStatusInfo } from '@/pages/Experiment/status'; | ||||
| import { type NodeStatus } from '@/types'; | import { type NodeStatus } from '@/types'; | ||||
| import { getExperimentInstanceStatus } from '@/utils/experiment'; | |||||
| import { formatDate } from '@/utils/format'; | import { formatDate } from '@/utils/format'; | ||||
| import { Flex } from 'antd'; | import { Flex } from 'antd'; | ||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| type ExperimentRunBasicProps = { | type ExperimentRunBasicProps = { | ||||
| runStatus?: NodeStatus; | |||||
| workflowStatus?: NodeStatus; | |||||
| instanceStatus?: ExperimentStatus; | instanceStatus?: ExperimentStatus; | ||||
| }; | }; | ||||
| function ExperimentRunBasic({ runStatus, instanceStatus }: ExperimentRunBasicProps) { | |||||
| function ExperimentRunBasic({ workflowStatus, instanceStatus }: ExperimentRunBasicProps) { | |||||
| const instanceDatas = useMemo(() => { | const instanceDatas = useMemo(() => { | ||||
| if (!runStatus) { | |||||
| return []; | |||||
| } | |||||
| const status = | |||||
| instanceStatus === ExperimentStatus.Terminated ? instanceStatus : runStatus.phase; | |||||
| const status = getExperimentInstanceStatus(instanceStatus as ExperimentStatus, workflowStatus); | |||||
| const statusInfo = experimentStatusInfo[status]; | const statusInfo = experimentStatusInfo[status]; | ||||
| return [ | return [ | ||||
| { | { | ||||
| label: '启动时间', | label: '启动时间', | ||||
| value: formatDate(runStatus.startedAt), | |||||
| value: formatDate(workflowStatus?.startedAt), | |||||
| }, | }, | ||||
| { | { | ||||
| label: '执行时长', | label: '执行时长', | ||||
| value: <RunDuration createTime={runStatus.startedAt} finishTime={runStatus.finishedAt} />, | |||||
| value: ( | |||||
| <RunDuration | |||||
| createTime={workflowStatus?.startedAt} | |||||
| finishTime={workflowStatus?.finishedAt} | |||||
| /> | |||||
| ), | |||||
| }, | }, | ||||
| { | { | ||||
| label: '状态', | label: '状态', | ||||
| value: ( | |||||
| value: statusInfo ? ( | |||||
| <Flex align="center"> | <Flex align="center"> | ||||
| <img | <img | ||||
| style={{ width: '17px', marginRight: '7px' }} | style={{ width: '17px', marginRight: '7px' }} | ||||
| src={statusInfo?.icon} | |||||
| src={statusInfo.icon} | |||||
| draggable={false} | draggable={false} | ||||
| alt="" | alt="" | ||||
| /> | /> | ||||
| <div | <div | ||||
| style={{ | style={{ | ||||
| color: statusInfo?.color, | |||||
| color: statusInfo.color, | |||||
| fontSize: '15px', | fontSize: '15px', | ||||
| lineHeight: 1.6, | lineHeight: 1.6, | ||||
| }} | }} | ||||
| > | > | ||||
| {statusInfo?.label} | |||||
| {statusInfo.label} | |||||
| </div> | </div> | ||||
| </Flex> | </Flex> | ||||
| ) : ( | |||||
| '--' | |||||
| ), | ), | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| }, [runStatus, instanceStatus]); | |||||
| }, [workflowStatus, instanceStatus]); | |||||
| return ( | return ( | ||||
| <ConfigInfo | <ConfigInfo | ||||
| @@ -7,6 +7,7 @@ import { getWorkflowById } from '@/services/pipeline/index.js'; | |||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { fittingString, parseJsonText } from '@/utils'; | import { fittingString, parseJsonText } from '@/utils'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { getExperimentInstanceStatus } from '@/utils/experiment'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import G6, { Util } from '@antv/g6'; | import G6, { Util } from '@antv/g6'; | ||||
| import { Button } from 'antd'; | import { Button } from 'antd'; | ||||
| @@ -34,6 +35,8 @@ function ExperimentText() { | |||||
| const evtSourceRef = useRef(); | const evtSourceRef = useRef(); | ||||
| const width = 110; | const width = 110; | ||||
| const height = 36; | const height = 36; | ||||
| const status = getExperimentInstanceStatus(experimentIns?.status, workflowStatus); | |||||
| const statusInfo = experimentStatusInfo[status]; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| initGraph(); | initGraph(); | ||||
| @@ -92,16 +95,17 @@ function ExperimentText() { | |||||
| const workflowData = workflowRef.current; | const workflowData = workflowRef.current; | ||||
| const experimentStatusObjs = parseJsonText(nodes_status); | const experimentStatusObjs = parseJsonText(nodes_status); | ||||
| if (experimentStatusObjs) { | if (experimentStatusObjs) { | ||||
| // 更新各个节点 | |||||
| workflowData.nodes.forEach((item) => { | workflowData.nodes.forEach((item) => { | ||||
| const experimentNode = experimentStatusObjs?.[item.id]; | const experimentNode = experimentStatusObjs?.[item.id]; | ||||
| updateWorkflowNode(item, experimentNode); | updateWorkflowNode(item, experimentNode); | ||||
| }); | }); | ||||
| // 处理workflow状态 | |||||
| // 设置 workflow 总状态 | |||||
| Object.keys(experimentStatusObjs).some((key) => { | Object.keys(experimentStatusObjs).some((key) => { | ||||
| if (key.startsWith(NodePrefix)) { | if (key.startsWith(NodePrefix)) { | ||||
| const workflowStatus = experimentStatusObjs[key]; | |||||
| setWorkflowStatus(workflowStatus); | |||||
| const tempWorkflowStatus = experimentStatusObjs[key]; | |||||
| setWorkflowStatus(tempWorkflowStatus); | |||||
| return true; | return true; | ||||
| } | } | ||||
| return false; | return false; | ||||
| @@ -154,27 +158,30 @@ function ExperimentText() { | |||||
| if (!statusData) { | if (!statusData) { | ||||
| return; | return; | ||||
| } | } | ||||
| const { startedAt, finishedAt, phase, nodes = {} } = statusData; | |||||
| const { finishedAt, phase, nodes = {} } = statusData; | |||||
| // 更新实验实例状态和结束时间 | |||||
| setExperimentIns((prev) => ({ | setExperimentIns((prev) => ({ | ||||
| ...prev, | ...prev, | ||||
| finish_time: finishedAt, | finish_time: finishedAt, | ||||
| status: phase, | status: phase, | ||||
| })); | })); | ||||
| const workflowStatus = Object.values(nodes).find((node) => | |||||
| // 设置总 workflow 状态 | |||||
| const tempWorkflowStatus = Object.values(nodes).find((node) => | |||||
| node.displayName.startsWith(NodePrefix), | node.displayName.startsWith(NodePrefix), | ||||
| ); | ); | ||||
| // 设置工作流状态 | |||||
| if (workflowStatus) { | |||||
| setWorkflowStatus(workflowStatus); | |||||
| if (tempWorkflowStatus) { | |||||
| setWorkflowStatus(tempWorkflowStatus); | |||||
| } | } | ||||
| // 更新各个节点 | |||||
| const workflowData = workflowRef.current; | const workflowData = workflowRef.current; | ||||
| workflowData.nodes.forEach((item) => { | workflowData.nodes.forEach((item) => { | ||||
| const experimentNode = Object.values(nodes).find((node) => node.displayName === item.id); | const experimentNode = Object.values(nodes).find((node) => node.displayName === item.id); | ||||
| updateWorkflowNode(item, experimentNode); | updateWorkflowNode(item, experimentNode); | ||||
| }); | }); | ||||
| // 绘制图 | |||||
| getGraphData(workflowData, false); | getGraphData(workflowData, false); | ||||
| // 更新打开的抽屉数据 | // 更新打开的抽屉数据 | ||||
| @@ -200,6 +207,7 @@ function ExperimentText() { | |||||
| evtSourceRef.current = evtSource; | evtSourceRef.current = evtSource; | ||||
| }; | }; | ||||
| // 更新各个节点 | |||||
| function updateWorkflowNode(workflowNode, statusNode) { | function updateWorkflowNode(workflowNode, statusNode) { | ||||
| if (!statusNode) { | if (!statusNode) { | ||||
| return; | return; | ||||
| @@ -505,18 +513,19 @@ function ExperimentText() { | |||||
| </div> | </div> | ||||
| <div className={styles['pipeline-container__top__info']}> | <div className={styles['pipeline-container__top__info']}> | ||||
| 状态: | 状态: | ||||
| <div | |||||
| style={{ | |||||
| width: '8px', | |||||
| height: '8px', | |||||
| borderRadius: '50%', | |||||
| marginRight: '6px', | |||||
| backgroundColor: experimentStatusInfo[workflowStatus?.phase]?.color, | |||||
| }} | |||||
| ></div> | |||||
| <span style={{ color: experimentStatusInfo[workflowStatus?.phase]?.color }}> | |||||
| {experimentStatusInfo[workflowStatus?.phase]?.label} | |||||
| </span> | |||||
| {statusInfo ? ( | |||||
| <> | |||||
| <img | |||||
| style={{ width: '17px', marginRight: '7px' }} | |||||
| src={statusInfo.icon} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <span style={{ color: statusInfo.color }}>{statusInfo.label}</span> | |||||
| </> | |||||
| ) : ( | |||||
| '--' | |||||
| )} | |||||
| </div> | </div> | ||||
| <Button | <Button | ||||
| className={styles['pipeline-container__top__param-button']} | className={styles['pipeline-container__top__param-button']} | ||||
| @@ -55,10 +55,6 @@ | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| width: 160px; | width: 160px; | ||||
| .statusIcon { | |||||
| visibility: visible; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -3,9 +3,9 @@ import { ExperimentStatus } from '@/enums'; | |||||
| import { useSSE, type MessageHandler } from '@/hooks/useSSE'; | import { useSSE, type MessageHandler } from '@/hooks/useSSE'; | ||||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | import { experimentStatusInfo } from '@/pages/Experiment/status'; | ||||
| import { ExperimentInstance, NodeStatus } from '@/types'; | import { ExperimentInstance, NodeStatus } from '@/types'; | ||||
| import { getWorkflowStatus } from '@/utils'; | |||||
| import { ExperimentCompleted } from '@/utils/constant'; | import { ExperimentCompleted } from '@/utils/constant'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { getExperimentInstanceStatus, getWorkflowStatus } from '@/utils/experiment'; | |||||
| import { Typography } from 'antd'; | import { Typography } from 'antd'; | ||||
| import React, { useCallback } from 'react'; | import React, { useCallback } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -15,26 +15,29 @@ type ExperimentInstanceComponentProps = { | |||||
| }; | }; | ||||
| function ExperimentInstanceComponent({ instance }: ExperimentInstanceComponentProps) { | function ExperimentInstanceComponent({ instance }: ExperimentInstanceComponentProps) { | ||||
| const { id, argo_ins_name, argo_ins_ns, nodes_status, create_time, finish_time } = instance; | |||||
| const status = instance.status as ExperimentStatus; | |||||
| const { id, experiment_id, argo_ins_name, argo_ins_ns, nodes_status, create_time, finish_time } = | |||||
| instance; | |||||
| const workflowStatus = getWorkflowStatus(nodes_status) as NodeStatus | undefined; | const workflowStatus = getWorkflowStatus(nodes_status) as NodeStatus | undefined; | ||||
| const status = getExperimentInstanceStatus(instance.status as ExperimentStatus); | |||||
| const createTime = workflowStatus?.startedAt ?? create_time; | const createTime = workflowStatus?.startedAt ?? create_time; | ||||
| const finishTime = workflowStatus?.finishedAt ?? finish_time; | const finishTime = workflowStatus?.finishedAt ?? finish_time; | ||||
| const statusInfo = experimentStatusInfo[status]; | |||||
| const handleSSEMessage: MessageHandler = useCallback( | const handleSSEMessage: MessageHandler = useCallback( | ||||
| (experimentInsId: number, status: string, finish_time: string) => { | |||||
| (experimentId: number, experimentInsId: number, status: string, finishTime: string) => { | |||||
| window.postMessage({ | window.postMessage({ | ||||
| type: ExperimentCompleted, | type: ExperimentCompleted, | ||||
| payload: { | payload: { | ||||
| id: experimentInsId, | |||||
| experimentId, | |||||
| experimentInsId, | |||||
| status, | status, | ||||
| finish_time, | |||||
| finishTime, | |||||
| }, | }, | ||||
| }); | }); | ||||
| }, | }, | ||||
| [], | [], | ||||
| ); | ); | ||||
| useSSE(id, status, argo_ins_name, argo_ins_ns, handleSSEMessage); | |||||
| useSSE(experiment_id, id, status, argo_ins_name, argo_ins_ns, handleSSEMessage); | |||||
| return ( | return ( | ||||
| <React.Fragment> | <React.Fragment> | ||||
| @@ -49,15 +52,19 @@ function ExperimentInstanceComponent({ instance }: ExperimentInstanceComponentPr | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className={styles.statusBox}> | <div className={styles.statusBox}> | ||||
| <img | |||||
| style={{ width: '17px', marginRight: '7px' }} | |||||
| src={experimentStatusInfo[status]?.icon} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <span style={{ color: experimentStatusInfo[status]?.color }} className={styles.statusIcon}> | |||||
| {experimentStatusInfo[status]?.label} | |||||
| </span> | |||||
| {statusInfo ? ( | |||||
| <> | |||||
| <img | |||||
| style={{ width: '17px', marginRight: '7px' }} | |||||
| src={statusInfo.icon} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <span style={{ color: statusInfo.color }}>{statusInfo.label}</span> | |||||
| </> | |||||
| ) : ( | |||||
| '--' | |||||
| )} | |||||
| </div> | </div> | ||||
| </React.Fragment> | </React.Fragment> | ||||
| ); | ); | ||||
| @@ -5,6 +5,7 @@ import { useCacheState } from '@/hooks/useCacheState'; | |||||
| import { useServerTime } from '@/hooks/useServerTime'; | import { useServerTime } from '@/hooks/useServerTime'; | ||||
| import { | import { | ||||
| deleteExperimentById, | deleteExperimentById, | ||||
| editExperimentInsReq, | |||||
| getExperiment, | getExperiment, | ||||
| getExperimentById, | getExperimentById, | ||||
| getQueryByExperimentId, | getQueryByExperimentId, | ||||
| @@ -40,7 +41,7 @@ function Experiment() { | |||||
| const [workflowList, setWorkflowList] = useState([]); | const [workflowList, setWorkflowList] = useState([]); | ||||
| const [experimentId, setExperimentId] = useState(null); | const [experimentId, setExperimentId] = useState(null); | ||||
| const [experimentInsList, setExperimentInsList] = useState([]); | const [experimentInsList, setExperimentInsList] = useState([]); | ||||
| const [expandedRowKeys, setExpandedRowKeys] = useState(null); | |||||
| const [expandedRowKeys, setExpandedRowKeys] = useState([]); | |||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| const [isAdd, setIsAdd] = useState(true); | const [isAdd, setIsAdd] = useState(true); | ||||
| const [isModalOpen, setIsModalOpen] = useState(false); | const [isModalOpen, setIsModalOpen] = useState(false); | ||||
| @@ -61,7 +62,7 @@ function Experiment() { | |||||
| // 获取实验列表 | // 获取实验列表 | ||||
| const getExperimentList = useCallback( | const getExperimentList = useCallback( | ||||
| async (skipLoading) => { | |||||
| async (skipLoading = false) => { | |||||
| const params = { | const params = { | ||||
| page: pagination.current - 1, | page: pagination.current - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| @@ -84,12 +85,114 @@ function Experiment() { | |||||
| // 刷新实验列表状态, | // 刷新实验列表状态, | ||||
| // 目前是直接刷新实验列表,后续需要优化,只刷新状态 | // 目前是直接刷新实验列表,后续需要优化,只刷新状态 | ||||
| const refreshExperimentList = useCallback( | const refreshExperimentList = useCallback( | ||||
| (skipLoading) => { | |||||
| (skipLoading = false) => { | |||||
| getExperimentList(skipLoading); | getExperimentList(skipLoading); | ||||
| }, | }, | ||||
| [getExperimentList], | [getExperimentList], | ||||
| ); | ); | ||||
| // 获取 TensorBoard 状态 | |||||
| const getTensorBoardStatus = useCallback(async (experimentIn) => { | |||||
| const params = { | |||||
| namespace: experimentIn.nodes_result.tensorboard_log.namespace, | |||||
| path: experimentIn.nodes_result.tensorboard_log.path, | |||||
| pvc_name: experimentIn.nodes_result.tensorboard_log.pvc_name, | |||||
| }; | |||||
| const [res] = await to(getTensorBoardStatusReq(params)); | |||||
| if (res && res.data) { | |||||
| setExperimentInsList((prevList) => { | |||||
| return prevList.map((item) => { | |||||
| if (item.id === experimentIn.id) { | |||||
| return { | |||||
| ...item, | |||||
| tensorBoardStatus: res.data.status, | |||||
| tensorboardUrl: res.data.url, | |||||
| }; | |||||
| } | |||||
| return item; | |||||
| }); | |||||
| }); | |||||
| let timerId = timerIds.get(experimentIn.id); | |||||
| if (timerId) { | |||||
| clearTimeout(timerId); | |||||
| timerIds.delete(experimentIn.id); | |||||
| } | |||||
| timerId = setTimeout(() => { | |||||
| getTensorBoardStatus(experimentIn); | |||||
| }, 10 * 1000); | |||||
| timerIds.set(experimentIn.id, timerId); | |||||
| } | |||||
| }, []); | |||||
| // 获取实验实例列表 | |||||
| const getExperimentInsList = useCallback( | |||||
| async (experimentId, page, size = 5, skipLoading = false) => { | |||||
| const params = { | |||||
| experimentId: experimentId, | |||||
| page: page, | |||||
| size: size, | |||||
| }; | |||||
| const [res, error] = await to(getQueryByExperimentId(params, skipLoading)); | |||||
| if (res && res.data) { | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| try { | |||||
| const list = content.map((v) => { | |||||
| const nodes_result = v.nodes_result ? JSON.parse(v.nodes_result) : {}; | |||||
| return { | |||||
| ...v, | |||||
| nodes_result, | |||||
| }; | |||||
| }); | |||||
| if (page === 0) { | |||||
| setExperimentInsList(list); | |||||
| clearExperimentInTimers(); | |||||
| } else { | |||||
| setExperimentInsList((prev) => [...prev, ...list]); | |||||
| } | |||||
| setExperimentInsTotal(totalElements); | |||||
| // 获取 TensorBoard 状态 | |||||
| list.forEach((item) => { | |||||
| if (item.nodes_result?.tensorboard_log) { | |||||
| getTensorBoardStatus(item); | |||||
| } | |||||
| }); | |||||
| } catch (error) { | |||||
| console.error('JSON parse error: ', error); | |||||
| } | |||||
| } | |||||
| }, | |||||
| [getTensorBoardStatus], | |||||
| ); | |||||
| // 刷新实验实例列表 | |||||
| const refreshExperimentIns = useCallback( | |||||
| (experimentId, skipLoading = false) => { | |||||
| const length = experimentInsList.length; | |||||
| getExperimentInsList(experimentId, 0, length, skipLoading); | |||||
| }, | |||||
| [experimentInsList, getExperimentInsList], | |||||
| ); | |||||
| // 更新实验状态 | |||||
| const editExperimentIns = useCallback( | |||||
| async (experimentId, experimentInsId, status, argo_ins_name, argo_ins_ns) => { | |||||
| const params = { | |||||
| experiment_id: experimentId, | |||||
| id: experimentInsId, | |||||
| status: status, | |||||
| argo_ins_name, | |||||
| argo_ins_ns, | |||||
| }; | |||||
| const [res, error] = await to(editExperimentInsReq(params)); | |||||
| if (res && res.data) { | |||||
| refreshExperimentIns(experimentId, true); | |||||
| refreshExperimentList(true); | |||||
| } | |||||
| }, | |||||
| [refreshExperimentIns, refreshExperimentList], | |||||
| ); | |||||
| // 获取流水线列表 | // 获取流水线列表 | ||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取流水线列表 | // 获取流水线列表 | ||||
| @@ -111,52 +214,66 @@ function Experiment() { | |||||
| clearExperimentInTimers(); | clearExperimentInTimers(); | ||||
| }; | }; | ||||
| }, []); | }, []); | ||||
| // 获取实验列表 | // 获取实验列表 | ||||
| useEffect(() => { | useEffect(() => { | ||||
| getExperimentList(); | getExperimentList(); | ||||
| }, [getExperimentList]); | }, [getExperimentList]); | ||||
| // 新增,删除版本时,重置分页,然后刷新版本列表 | |||||
| // 更新实验实例状态 | |||||
| useEffect(() => { | useEffect(() => { | ||||
| const handleMessage = (e) => { | const handleMessage = (e) => { | ||||
| const { type, payload } = e.data; | const { type, payload } = e.data; | ||||
| if (type === ExperimentCompleted) { | if (type === ExperimentCompleted) { | ||||
| const { id, status, finish_time } = payload; | |||||
| // 修改实例的状态和结束时间 | |||||
| setExperimentInsList((prev) => | |||||
| prev.map((v) => | |||||
| v.id === id | |||||
| ? { | |||||
| ...v, | |||||
| status: status, | |||||
| finish_time: finish_time, | |||||
| } | |||||
| : v, | |||||
| ), | |||||
| const { experimentId, experimentInsId, status, finishTime } = payload; | |||||
| const currentIns = experimentInsList.find((v) => v.id === experimentInsId); | |||||
| console.log( | |||||
| '实验实例状态变化', | |||||
| currentIns?.status, | |||||
| status, | |||||
| experimentId, | |||||
| experimentInsId, | |||||
| finishTime, | |||||
| ); | ); | ||||
| if (timerRef.current) { | |||||
| clearTimeout(timerRef.current); | |||||
| timerRef.current = undefined; | |||||
| if ( | |||||
| !currentIns || | |||||
| currentIns.status === ExperimentStatus.Terminated || | |||||
| currentIns.status === status | |||||
| ) { | |||||
| return; | |||||
| } | } | ||||
| timerRef.current = setTimeout(() => { | |||||
| refreshExperimentList(true); | |||||
| }, 10000); | |||||
| editExperimentIns( | |||||
| experimentId, | |||||
| experimentInsId, | |||||
| status, | |||||
| currentIns.argo_ins_name, | |||||
| currentIns.argo_ins_ns, | |||||
| ); | |||||
| // refreshExperimentList(true); | |||||
| // refreshExperimentIns(experimentId); | |||||
| // 修改实例的状态和结束时间 | |||||
| // setExperimentInsList((prev) => | |||||
| // prev.map((v) => | |||||
| // v.id === experimentInsId | |||||
| // ? { | |||||
| // ...v, | |||||
| // status: status, | |||||
| // finish_time: finishTime, | |||||
| // } | |||||
| // : v, | |||||
| // ), | |||||
| // ); | |||||
| } | } | ||||
| }; | }; | ||||
| window.addEventListener('message', handleMessage); | window.addEventListener('message', handleMessage); | ||||
| return () => { | return () => { | ||||
| window.removeEventListener('message', handleMessage); | window.removeEventListener('message', handleMessage); | ||||
| if (timerRef.current) { | |||||
| clearTimeout(timerRef.current); | |||||
| timerRef.current = undefined; | |||||
| } | |||||
| }; | }; | ||||
| }, [refreshExperimentList]); | |||||
| }, [experimentInsList, editExperimentIns]); | |||||
| // 搜索 | // 搜索 | ||||
| const onSearch = (value) => { | const onSearch = (value) => { | ||||
| @@ -167,44 +284,6 @@ function Experiment() { | |||||
| })); | })); | ||||
| }; | }; | ||||
| // 获取实验实例列表 | |||||
| const getQueryByExperiment = async (experimentId, page, size = 5) => { | |||||
| const params = { | |||||
| experimentId: experimentId, | |||||
| page: page, | |||||
| size: size, | |||||
| }; | |||||
| const [res, error] = await to(getQueryByExperimentId(params)); | |||||
| if (res && res.data) { | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| setExpandedRowKeys(experimentId); | |||||
| try { | |||||
| const list = content.map((v) => { | |||||
| const nodes_result = v.nodes_result ? JSON.parse(v.nodes_result) : {}; | |||||
| return { | |||||
| ...v, | |||||
| nodes_result, | |||||
| }; | |||||
| }); | |||||
| if (page === 0) { | |||||
| setExperimentInsList(list); | |||||
| clearExperimentInTimers(); | |||||
| } else { | |||||
| setExperimentInsList((prev) => [...prev, ...list]); | |||||
| } | |||||
| setExperimentInsTotal(totalElements); | |||||
| // 获取 TensorBoard 状态 | |||||
| list.forEach((item) => { | |||||
| if (item.nodes_result?.tensorboard_log) { | |||||
| getTensorBoardStatus(item); | |||||
| } | |||||
| }); | |||||
| } catch (error) { | |||||
| console.error('JSON parse error: ', error); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 运行 TensorBoard | // 运行 TensorBoard | ||||
| const runTensorBoard = async (experimentIn) => { | const runTensorBoard = async (experimentIn) => { | ||||
| const params = { | const params = { | ||||
| @@ -224,49 +303,16 @@ function Experiment() { | |||||
| } | } | ||||
| }; | }; | ||||
| // 获取 TensorBoard 状态 | |||||
| const getTensorBoardStatus = async (experimentIn) => { | |||||
| const params = { | |||||
| namespace: experimentIn.nodes_result.tensorboard_log.namespace, | |||||
| path: experimentIn.nodes_result.tensorboard_log.path, | |||||
| pvc_name: experimentIn.nodes_result.tensorboard_log.pvc_name, | |||||
| }; | |||||
| const [res] = await to(getTensorBoardStatusReq(params)); | |||||
| if (res && res.data) { | |||||
| setExperimentInsList((prevList) => { | |||||
| return prevList.map((item) => { | |||||
| if (item.id === experimentIn.id) { | |||||
| return { | |||||
| ...item, | |||||
| tensorBoardStatus: res.data.status, | |||||
| tensorboardUrl: res.data.url, | |||||
| }; | |||||
| } | |||||
| return item; | |||||
| }); | |||||
| }); | |||||
| let timerId = timerIds.get(experimentIn.id); | |||||
| if (timerId) { | |||||
| clearTimeout(timerId); | |||||
| timerIds.delete(experimentIn.id); | |||||
| } | |||||
| timerId = setTimeout(() => { | |||||
| getTensorBoardStatus(experimentIn); | |||||
| }, 10 * 1000); | |||||
| timerIds.set(experimentIn.id, timerId); | |||||
| } | |||||
| }; | |||||
| // 展开实例 | // 展开实例 | ||||
| const expandChange = (e, record) => { | |||||
| const expandChange = (expanded, record) => { | |||||
| clearExperimentInTimers(); | clearExperimentInTimers(); | ||||
| setExperimentInsList([]); | setExperimentInsList([]); | ||||
| if (record.id === expandedRowKeys) { | |||||
| setExpandedRowKeys(null); | |||||
| } else { | |||||
| getQueryByExperiment(record.id, 0, 5); | |||||
| if (expanded) { | |||||
| setExpandedRowKeys([record.id]); | |||||
| getExperimentInsList(record.id, 0, 5); | |||||
| refreshExperimentList(); | refreshExperimentList(); | ||||
| } else { | |||||
| setExpandedRowKeys([]); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -344,8 +390,9 @@ function Experiment() { | |||||
| const [res] = await to(runExperiments(id)); | const [res] = await to(runExperiments(id)); | ||||
| if (res) { | if (res) { | ||||
| message.success('运行成功'); | message.success('运行成功'); | ||||
| setExpandedRowKeys([id]); | |||||
| refreshExperimentList(); | refreshExperimentList(); | ||||
| getQueryByExperiment(id, 0, 5); | |||||
| getExperimentInsList(id, 0, 5); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -388,8 +435,6 @@ function Experiment() { | |||||
| // 实验实例终止 | // 实验实例终止 | ||||
| const handleInstanceTerminate = async (experimentIn) => { | const handleInstanceTerminate = async (experimentIn) => { | ||||
| // 刷新实验列表 | |||||
| refreshExperimentList(); | |||||
| setExperimentInsList((prevList) => { | setExperimentInsList((prevList) => { | ||||
| return prevList.map((item) => { | return prevList.map((item) => { | ||||
| if (item.id === experimentIn.id) { | if (item.id === experimentIn.id) { | ||||
| @@ -402,6 +447,9 @@ function Experiment() { | |||||
| return item; | return item; | ||||
| }); | }); | ||||
| }); | }); | ||||
| // 刷新实验列表 | |||||
| refreshExperimentList(true); | |||||
| refreshExperimentIns(experimentIn.experiment_id); | |||||
| }; | }; | ||||
| // 实验对比菜单 | // 实验对比菜单 | ||||
| @@ -423,16 +471,10 @@ function Experiment() { | |||||
| }; | }; | ||||
| }; | }; | ||||
| // 刷新实验实例列表 | |||||
| const refreshExperimentIns = (experimentId) => { | |||||
| const length = experimentInsList.length; | |||||
| getQueryByExperiment(experimentId, 0, length); | |||||
| }; | |||||
| // 加载更多实验实例 | // 加载更多实验实例 | ||||
| const loadMoreExperimentIns = () => { | const loadMoreExperimentIns = () => { | ||||
| const page = Math.round(experimentInsList.length / 5); | const page = Math.round(experimentInsList.length / 5); | ||||
| getQueryByExperiment(expandedRowKeys, page, 5); | |||||
| getExperimentInsList(expandedRowKeys, page, 5); | |||||
| }; | }; | ||||
| // 处理删除 | // 处理删除 | ||||
| @@ -617,7 +659,7 @@ function Experiment() { | |||||
| ></ExperimentInstanceList> | ></ExperimentInstanceList> | ||||
| ), | ), | ||||
| onExpand: expandChange, | onExpand: expandChange, | ||||
| expandedRowKeys: [expandedRowKeys], | |||||
| expandedRowKeys: expandedRowKeys, | |||||
| }} | }} | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| @@ -120,18 +120,17 @@ function HyperParameterInstance() { | |||||
| if (dataJson) { | if (dataJson) { | ||||
| const nodes = dataJson?.result?.object?.status?.nodes; | const nodes = dataJson?.result?.object?.status?.nodes; | ||||
| if (nodes) { | if (nodes) { | ||||
| // 节点 | |||||
| // 设置节点 | |||||
| setNodes(nodes); | setNodes(nodes); | ||||
| // 设置总 workflow 状态 | |||||
| const workflowStatus = 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 (workflowStatus) { | if (workflowStatus) { | ||||
| setWorkflowStatus(workflowStatus); | setWorkflowStatus(workflowStatus); | ||||
| // 实验结束,关闭 SSE | |||||
| // 实验结束,关闭 SSE,获取实验实例结果 | |||||
| if ( | if ( | ||||
| workflowStatus.phase !== ExperimentStatus.Pending && | workflowStatus.phase !== ExperimentStatus.Pending && | ||||
| workflowStatus.phase !== ExperimentStatus.Running | workflowStatus.phase !== ExperimentStatus.Running | ||||
| @@ -166,7 +165,7 @@ function HyperParameterInstance() { | |||||
| <HyperParameterBasic | <HyperParameterBasic | ||||
| className={styles['hyper-parameter-instance__basic']} | className={styles['hyper-parameter-instance__basic']} | ||||
| info={experimentInfo} | info={experimentInfo} | ||||
| runStatus={workflowStatus} | |||||
| workflowStatus={workflowStatus} | |||||
| instanceStatus={instanceInfo?.status as ExperimentStatus} | instanceStatus={instanceInfo?.status as ExperimentStatus} | ||||
| isInstance | isInstance | ||||
| /> | /> | ||||
| @@ -30,14 +30,14 @@ type HyperParameterBasicProps = { | |||||
| info?: HyperParameterData; | info?: HyperParameterData; | ||||
| className?: string; | className?: string; | ||||
| isInstance?: boolean; | isInstance?: boolean; | ||||
| runStatus?: NodeStatus; | |||||
| workflowStatus?: NodeStatus; | |||||
| instanceStatus?: ExperimentStatus; | instanceStatus?: ExperimentStatus; | ||||
| }; | }; | ||||
| function HyperParameterBasic({ | function HyperParameterBasic({ | ||||
| info, | info, | ||||
| className, | className, | ||||
| runStatus, | |||||
| workflowStatus, | |||||
| instanceStatus, | instanceStatus, | ||||
| isInstance = false, | isInstance = false, | ||||
| }: HyperParameterBasicProps) { | }: HyperParameterBasicProps) { | ||||
| @@ -144,8 +144,8 @@ function HyperParameterBasic({ | |||||
| return ( | return ( | ||||
| <div className={classNames(styles['hyper-parameter-basic'], className)}> | <div className={classNames(styles['hyper-parameter-basic'], className)}> | ||||
| {isInstance && runStatus && ( | |||||
| <ExperimentRunBasic runStatus={runStatus} instanceStatus={instanceStatus} /> | |||||
| {isInstance && workflowStatus && ( | |||||
| <ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} /> | |||||
| )} | )} | ||||
| {!isInstance && ( | {!isInstance && ( | ||||
| <ConfigInfo | <ConfigInfo | ||||
| @@ -16,7 +16,7 @@ function AssetsManagement() { | |||||
| }; | }; | ||||
| const [res] = await to(getWorkspaceAssetCountReq(params)); | const [res] = await to(getWorkspaceAssetCountReq(params)); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { component, dataset, image, model, workflow } = res.data; | |||||
| const { dataset, image, model, workflow } = res.data; | |||||
| const items = [ | const items = [ | ||||
| { | { | ||||
| title: '数据集', | title: '数据集', | ||||
| @@ -30,10 +30,10 @@ function AssetsManagement() { | |||||
| title: '镜像', | title: '镜像', | ||||
| value: image, | value: image, | ||||
| }, | }, | ||||
| { | |||||
| title: '组件', | |||||
| value: component, | |||||
| }, | |||||
| // { | |||||
| // title: '组件', | |||||
| // value: component, | |||||
| // }, | |||||
| // { | // { | ||||
| // title: '代码配置', | // title: '代码配置', | ||||
| // value: 0, | // value: 0, | ||||
| @@ -55,10 +55,11 @@ export function runActiveLearnReq(id) { | |||||
| // ----------------------- 实验实例 ----------------------- | // ----------------------- 实验实例 ----------------------- | ||||
| // 获取实验实例列表 | // 获取实验实例列表 | ||||
| export function getActiveLearnInsListReq(params) { | |||||
| export function getActiveLearnInsListReq(params, skipLoading) { | |||||
| return request(`/api/mmp/activeLearnIns`, { | return request(`/api/mmp/activeLearnIns`, { | ||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| skipLoading | |||||
| }); | }); | ||||
| } | } | ||||
| @@ -100,3 +101,12 @@ export function getExpMetricsReq(data) { | |||||
| }); | }); | ||||
| } | } | ||||
| // 编辑实验实例 | |||||
| export function editActiveLearnInsReq(data) { | |||||
| return request(`/api/mmp/activeLearnIns`, { | |||||
| method: 'PUT', | |||||
| data, | |||||
| skipLoading: true, | |||||
| }); | |||||
| } | |||||
| @@ -7,7 +7,7 @@ | |||||
| import { request } from '@umijs/max'; | import { request } from '@umijs/max'; | ||||
| // 分页查询自动学习 | // 分页查询自动学习 | ||||
| export function getAutoMLListReq(params,skipLoading) { | |||||
| export function getAutoMLListReq(params, skipLoading) { | |||||
| return request(`/api/mmp/machineLearn`, { | return request(`/api/mmp/machineLearn`, { | ||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| @@ -55,10 +55,11 @@ export function runAutoMLReq(id) { | |||||
| // ----------------------- 实验实例 ----------------------- | // ----------------------- 实验实例 ----------------------- | ||||
| // 获取实验实例列表 | // 获取实验实例列表 | ||||
| export function getExperimentInsListReq(params) { | |||||
| export function getExperimentInsListReq(params, skipLoading) { | |||||
| return request(`/api/mmp/machineLearnIns`, { | return request(`/api/mmp/machineLearnIns`, { | ||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| skipLoading, | |||||
| }); | }); | ||||
| } | } | ||||
| @@ -90,3 +91,12 @@ export function batchDeleteExperimentInsReq(data) { | |||||
| data, | data, | ||||
| }); | }); | ||||
| } | } | ||||
| // 编辑实验实例 | |||||
| export function editExperimentInsReq(data) { | |||||
| return request(`/api/mmp/machineLearnIns`, { | |||||
| method: 'PUT', | |||||
| data, | |||||
| skipLoading: true, | |||||
| }); | |||||
| } | |||||
| @@ -29,10 +29,11 @@ export function deleteExperimentById(id) { | |||||
| }); | }); | ||||
| } | } | ||||
| // 根据id查询实验实例 | // 根据id查询实验实例 | ||||
| export function getQueryByExperimentId(params) { | |||||
| export function getQueryByExperimentId(params, skipLoading) { | |||||
| return request(`/api/mmp/experimentIns`, { | return request(`/api/mmp/experimentIns`, { | ||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| skipLoading | |||||
| }); | }); | ||||
| } | } | ||||
| // 根据id删除实验实例 | // 根据id删除实验实例 | ||||
| @@ -54,6 +55,16 @@ export function putQueryByExperimentInsId(id) { | |||||
| method: 'PUT', | method: 'PUT', | ||||
| }); | }); | ||||
| } | } | ||||
| // 编辑实验实例 | |||||
| export function editExperimentInsReq(data) { | |||||
| return request(`/api/mmp/experimentIns`, { | |||||
| method: 'PUT', | |||||
| data, | |||||
| skipLoading: true, | |||||
| }); | |||||
| } | |||||
| // 查询实验实例实时日志 | // 查询实验实例实时日志 | ||||
| export function getQueryByExperimentLog(data) { | export function getQueryByExperimentLog(data) { | ||||
| return request('/api/mmp/experimentIns/realTimeLog/', { | return request('/api/mmp/experimentIns/realTimeLog/', { | ||||
| @@ -55,10 +55,11 @@ export function runRayReq(id) { | |||||
| // ----------------------- 实验实例 ----------------------- | // ----------------------- 实验实例 ----------------------- | ||||
| // 获取实验实例列表 | // 获取实验实例列表 | ||||
| export function getRayInsListReq(params) { | |||||
| export function getRayInsListReq(params, skipLoading) { | |||||
| return request(`/api/mmp/rayIns`, { | return request(`/api/mmp/rayIns`, { | ||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| skipLoading, | |||||
| }); | }); | ||||
| } | } | ||||
| @@ -98,3 +99,13 @@ export function getExpMetricsReq(data) { | |||||
| data, | data, | ||||
| }); | }); | ||||
| } | } | ||||
| // 编辑实验实例 | |||||
| export function editRayInsReq(data) { | |||||
| return request(`/api/mmp/rayIns`, { | |||||
| method: 'PUT', | |||||
| data, | |||||
| skipLoading: true, | |||||
| }); | |||||
| } | |||||
| @@ -1,4 +1,3 @@ | |||||
| import { now } from '@/hooks/useServerTime'; | |||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||
| /** | /** | ||||
| @@ -13,8 +12,12 @@ export const elapsedTime = (begin?: string | Date | null, end?: string | Date | | |||||
| return '--'; | return '--'; | ||||
| } | } | ||||
| if (end === undefined || end === null) { | |||||
| return '--'; | |||||
| } | |||||
| const beginDate = dayjs(begin); | const beginDate = dayjs(begin); | ||||
| const endDate = end === undefined || end === null ? dayjs(now()) : dayjs(end); | |||||
| const endDate = dayjs(end); // end === undefined || end === null ? dayjs(now()) : dayjs(end); | |||||
| if (!beginDate.isValid() || !endDate.isValid()) { | if (!beginDate.isValid() || !endDate.isValid()) { | ||||
| return '--'; | return '--'; | ||||
| } | } | ||||
| @@ -0,0 +1,60 @@ | |||||
| import { ExperimentStatus } from '@/enums'; | |||||
| import { NodeStatus } from '@/types'; | |||||
| import { parseJsonText } from './index'; | |||||
| /** | |||||
| * 获取工作流节点 | |||||
| * | |||||
| * @param node_status - 流水线workflow节点,json字符串 | |||||
| * @return workflow 节点 | |||||
| */ | |||||
| export const getWorkflowStatus = (node_status?: string | null) => { | |||||
| if (!node_status) { | |||||
| return undefined; | |||||
| } | |||||
| const nodeStatusJson = parseJsonText(node_status); | |||||
| if (!nodeStatusJson) { | |||||
| return undefined; | |||||
| } | |||||
| for (const key in nodeStatusJson) { | |||||
| if (key.startsWith('workflow')) { | |||||
| return nodeStatusJson[key]; | |||||
| } | |||||
| } | |||||
| return undefined; | |||||
| }; | |||||
| /** | |||||
| * 获取实例状态 | |||||
| * 终止或者 workflowStatus 不存在时,取实例状态,否则取流水线状态 | |||||
| * | |||||
| * @param instanceStatus - 实例状态 | |||||
| * @param workflowStatus - 流水线workflow节点 | |||||
| * @return 实例状态 | |||||
| */ | |||||
| export const getExperimentInstanceStatus = ( | |||||
| instanceStatus: ExperimentStatus, | |||||
| workflowStatus?: NodeStatus, | |||||
| ): ExperimentStatus => { | |||||
| return instanceStatus === ExperimentStatus.Terminated || !workflowStatus | |||||
| ? instanceStatus | |||||
| : workflowStatus?.phase; | |||||
| }; | |||||
| /** | |||||
| * 获取实例状态 | |||||
| * 终止或者 workflowStatus 不存在时,取实例状态,否则取流水线状态 | |||||
| * | |||||
| * @param instanceStatus - 实例状态 | |||||
| * @param workflowStatus - 流水线workflow节点 | |||||
| * @return 实例状态 | |||||
| */ | |||||
| export const getInstanceStatusInList = ( | |||||
| instanceStatus: ExperimentStatus, | |||||
| node_status?: string | null, | |||||
| ): ExperimentStatus => { | |||||
| const workflowStatus = getWorkflowStatus(node_status); | |||||
| return getExperimentInstanceStatus(instanceStatus, workflowStatus); | |||||
| }; | |||||
| @@ -352,27 +352,3 @@ export const trimCharacter = (str: string, ch: string): string => { | |||||
| export const convertEmptyStringToUndefined = (value?: string): string | undefined => { | export const convertEmptyStringToUndefined = (value?: string): string | undefined => { | ||||
| return value === '' ? undefined : value; | return value === '' ? undefined : value; | ||||
| }; | }; | ||||
| /** | |||||
| * 获取工作流节点 | |||||
| * | |||||
| * @param node_status - the status of the node | |||||
| * @return the workflow node | |||||
| */ | |||||
| export const getWorkflowStatus = (node_status?: string | null) => { | |||||
| if (!node_status) { | |||||
| return; | |||||
| } | |||||
| const nodeStatusJson = parseJsonText(node_status); | |||||
| if (!nodeStatusJson) { | |||||
| return; | |||||
| } | |||||
| for (const key in nodeStatusJson) { | |||||
| if (key.startsWith('workflow')) { | |||||
| return nodeStatusJson[key]; | |||||
| } | |||||
| } | |||||
| return; | |||||
| }; | |||||