Browse Source

Merge pull request '合并dev' (#234) from dev-zw into dev

pull/235/head
cp3hnu 8 months ago
parent
commit
e4ffcea914
26 changed files with 551 additions and 318 deletions
  1. +10
    -1
      react-ui/src/components/RunDuration/index.tsx
  2. +22
    -10
      react-ui/src/hooks/useSSE.ts
  3. +5
    -6
      react-ui/src/pages/ActiveLearn/Instance/index.tsx
  4. +4
    -4
      react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx
  5. +4
    -4
      react-ui/src/pages/AutoML/Instance/index.tsx
  6. +4
    -4
      react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx
  7. +0
    -4
      react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.less
  8. +4
    -1
      react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.tsx
  9. +26
    -19
      react-ui/src/pages/AutoML/components/ExperimentInstanceList/instance.tsx
  10. +12
    -1
      react-ui/src/pages/AutoML/components/ExperimentList/config.ts
  11. +105
    -46
      react-ui/src/pages/AutoML/components/ExperimentList/index.tsx
  12. +18
    -15
      react-ui/src/pages/AutoML/components/ExperimentRunBasic/index.tsx
  13. +30
    -21
      react-ui/src/pages/Experiment/Info/index.jsx
  14. +0
    -4
      react-ui/src/pages/Experiment/components/ExperimentInstanceList/index.less
  15. +23
    -16
      react-ui/src/pages/Experiment/components/ExperimentInstanceList/instance.tsx
  16. +159
    -117
      react-ui/src/pages/Experiment/index.jsx
  17. +4
    -5
      react-ui/src/pages/HyperParameter/Instance/index.tsx
  18. +4
    -4
      react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx
  19. +5
    -5
      react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx
  20. +11
    -1
      react-ui/src/services/activeLearn/index.js
  21. +12
    -2
      react-ui/src/services/autoML/index.js
  22. +12
    -1
      react-ui/src/services/experiment/index.js
  23. +12
    -1
      react-ui/src/services/hyperParameter/index.js
  24. +5
    -2
      react-ui/src/utils/date.ts
  25. +60
    -0
      react-ui/src/utils/experiment.ts
  26. +0
    -24
      react-ui/src/utils/index.ts

+ 10
- 1
react-ui/src/components/RunDuration/index.tsx View File

@@ -10,13 +10,21 @@ type RunDurationProps = {
};
function RunDuration({ createTime, finishTime, className, style }: RunDurationProps) {
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(() => {
if (finishTime) {
setCurrentTime(new Date(finishTime));
} else {
setCurrentTime(now());
const timer = setInterval(() => {
setCurrentTime(now());
}, 1000);
@@ -25,6 +33,7 @@ function RunDuration({ createTime, finishTime, className, style }: RunDurationPr
};
}
}, [finishTime, now]);

return (
<span className={className} style={style}>
{elapsedTime(createTime, currentTime)}


+ 22
- 10
react-ui/src/hooks/useSSE.ts View File

@@ -1,11 +1,24 @@
import { parseJsonText } from '@/utils';
import { useEffect } from 'react';
import { ExperimentStatus } from '@/enums';
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(() => {
if (isRunning) {
const { origin } = location;
@@ -22,8 +35,8 @@ export const useSSE = (experimentInsId: number, status: ExperimentStatus, name:
const dataJson = parseJsonText(data);
const statusData = dataJson?.result?.object?.status;
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 () => {
evtSource.close();
}
};
}
}, [experimentInsId, isRunning, name, namespace, onMessage]);
}, [experimentId, experimentInsId, isRunning, name, namespace, onMessage]);
};

+ 5
- 6
react-ui/src/pages/ActiveLearn/Instance/index.tsx View File

@@ -68,7 +68,7 @@ function ActiveLearnInstance() {
return;
}

// 设置节点状态
// 设置总 workflow 状态
const nodeStatusJson = parseJsonText(node_status);
if (nodeStatusJson) {
setNodes(nodeStatusJson);
@@ -105,18 +105,17 @@ function ActiveLearnInstance() {
if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
// 节点
// 设置节点
setNodes(nodes);

// 设置总 workflow 状态
const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix),
) as NodeStatus;

// 设置工作流状态
if (workflowStatus) {
setWorkflowStatus(workflowStatus);

// 实验结束,关闭 SSE
// 实验结束,关闭 SSE,获取实验实例结果
if (
workflowStatus.phase !== ExperimentStatus.Pending &&
workflowStatus.phase !== ExperimentStatus.Running
@@ -151,7 +150,7 @@ function ActiveLearnInstance() {
<ActiveLearnBasic
className={styles['active-learn-instance__basic']}
info={experimentInfo}
runStatus={workflowStatus}
workflowStatus={workflowStatus}
instanceStatus={instanceInfo?.status as ExperimentStatus}
isInstance
/>


+ 4
- 4
react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx View File

@@ -28,14 +28,14 @@ type BasicInfoProps = {
info?: ActiveLearnData;
className?: string;
isInstance?: boolean;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus;
};

function BasicInfo({
info,
className,
runStatus,
workflowStatus,
instanceStatus,
isInstance = false,
}: BasicInfoProps) {
@@ -212,8 +212,8 @@ function BasicInfo({

return (
<div className={classNames(styles['active-learn-basic'], className)}>
{isInstance && runStatus && (
<ExperimentRunBasic runStatus={runStatus} instanceStatus={instanceStatus} />
{isInstance && workflowStatus && (
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
)}
{!isInstance && (
<ConfigInfo


+ 4
- 4
react-ui/src/pages/AutoML/Instance/index.tsx View File

@@ -112,17 +112,17 @@ function AutoMLInstance() {
if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
// 节点
// 设置节点
setNodes(nodes);

// 设置总 workflow 状态
const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix),
) as NodeStatus;

if (workflowStatus) {
setWorkflowStatus(workflowStatus);

// 实验结束,关闭 SSE
// 实验结束,关闭 SSE,获取实验实例结果
if (
workflowStatus.phase !== ExperimentStatus.Pending &&
workflowStatus.phase !== ExperimentStatus.Running
@@ -157,7 +157,7 @@ function AutoMLInstance() {
<AutoMLBasic
className={styles['auto-ml-instance__basic']}
info={autoMLInfo}
runStatus={workflowStatus}
workflowStatus={workflowStatus}
instanceStatus={instanceInfo?.status as ExperimentStatus}
isInstance
/>


+ 4
- 4
react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx View File

@@ -38,7 +38,7 @@ type AutoMLBasicProps = {
info?: AutoMLData;
className?: string;
isInstance?: boolean;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus;
instanceCreateTime?: string;
};
@@ -46,7 +46,7 @@ type AutoMLBasicProps = {
function AutoMLBasic({
info,
className,
runStatus,
workflowStatus,
instanceStatus,
isInstance = false,
}: AutoMLBasicProps) {
@@ -293,8 +293,8 @@ function AutoMLBasic({

return (
<div className={classNames(styles['auto-ml-basic'], className)}>
{isInstance && runStatus && (
<ExperimentRunBasic runStatus={runStatus} instanceStatus={instanceStatus} />
{isInstance && workflowStatus && (
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
)}
{!isInstance && (
<ConfigInfo


+ 0
- 4
react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.less View File

@@ -54,10 +54,6 @@
display: flex;
align-items: center;
width: 200px;

.statusIcon {
visibility: visible;
}
}
}



+ 4
- 1
react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.tsx View File

@@ -182,7 +182,10 @@ function ExperimentInstanceList({
>
{index + 1}
</a>
<ExperimentInstanceComponent instance={item}></ExperimentInstanceComponent>
<ExperimentInstanceComponent
experimentId={item[config['idInsProperty'] as keyof ExperimentInstance] as number}
instance={item}
></ExperimentInstanceComponent>
<div className={styles.operation}>
<Button
type="link"


+ 26
- 19
react-ui/src/pages/AutoML/components/ExperimentInstanceList/instance.tsx View File

@@ -3,38 +3,41 @@ import { ExperimentStatus } from '@/enums';
import { useSSE, type MessageHandler } from '@/hooks/useSSE';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance, NodeStatus } from '@/types';
import { getWorkflowStatus } from '@/utils';
import { ExperimentCompleted } from '@/utils/constant';
import { formatDate } from '@/utils/date';
import { getExperimentInstanceStatus, getWorkflowStatus } from '@/utils/experiment';
import { Typography } from 'antd';
import React, { useCallback } from 'react';
import styles from './index.less';

type ExperimentInstanceComponentProps = {
experimentId: number;
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 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(
(experimentInsId: number, status: string, finish_time: string) => {
(experimentId: number, experimentInsId: number, status: string, finishTime: string) => {
window.postMessage({
type: ExperimentCompleted,
payload: {
id: experimentInsId,
experimentId,
experimentInsId,
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 (
<React.Fragment>
@@ -47,15 +50,19 @@ function ExperimentInstanceComponent({ instance }: ExperimentInstanceComponentPr
</Typography.Text>
</div>
<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>
</React.Fragment>
);


+ 12
- 1
react-ui/src/pages/AutoML/components/ExperimentList/config.ts View File

@@ -8,6 +8,7 @@ import {
batchDeleteActiveLearnInsReq,
deleteActiveLearnInsReq,
deleteActiveLearnReq,
editActiveLearnInsReq,
getActiveLearnInsListReq,
getActiveLearnListReq,
runActiveLearnReq,
@@ -17,6 +18,7 @@ import {
batchDeleteExperimentInsReq,
deleteAutoMLReq,
deleteExperimentInsReq,
editExperimentInsReq,
getAutoMLListReq,
getExperimentInsListReq,
runAutoMLReq,
@@ -26,6 +28,7 @@ import {
batchDeleteRayInsReq,
deleteRayInsReq,
deleteRayReq,
editRayInsReq,
getRayInsListReq,
getRayListReq,
runRayReq,
@@ -40,17 +43,19 @@ export enum ExperimentListType {

type ExperimentListInfo = {
getListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取列表
getInsListReq: (params: any) => Promise<any>; // 获取实例列表
getInsListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取实例列表
deleteRecordReq: (params: any) => Promise<any>; // 删除
runRecordReq: (params: any) => Promise<any>; // 运行
deleteInsReq: (params: any) => Promise<any>; // 删除实例
batchDeleteInsReq: (params: any) => Promise<any>; // 批量删除实例
stopInsReq: (params: any) => Promise<any>; // 终止实例
editInsReq: (params: any) => Promise<any>; // 编辑实例
title: string; // 标题
pathPrefix: string; // 路由路径前缀
idProperty: string; // ID属性
nameProperty: string; // 名称属性
descProperty: string; // 描述属性
idInsProperty: string; // 实例返回的ID属性
};

export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo> = {
@@ -62,11 +67,13 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
deleteInsReq: deleteExperimentInsReq,
batchDeleteInsReq: batchDeleteExperimentInsReq,
stopInsReq: stopExperimentInsReq,
editInsReq: editExperimentInsReq,
title: '自主机器学习',
pathPrefix: 'automl',
nameProperty: 'name',
descProperty: 'description',
idProperty: 'machineLearnId',
idInsProperty: 'machine_learn_id',
},
[ExperimentListType.HyperParameter]: {
getListReq: getRayListReq,
@@ -76,11 +83,13 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
deleteInsReq: deleteRayInsReq,
batchDeleteInsReq: batchDeleteRayInsReq,
stopInsReq: stopRayInsReq,
editInsReq: editRayInsReq,
title: '超参数自动寻优',
pathPrefix: 'hyperparameter',
nameProperty: 'name',
descProperty: 'description',
idProperty: 'rayId',
idInsProperty: 'ray_id',
},
[ExperimentListType.ActiveLearn]: {
getListReq: getActiveLearnListReq,
@@ -90,10 +99,12 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
deleteInsReq: deleteActiveLearnInsReq,
batchDeleteInsReq: batchDeleteActiveLearnInsReq,
stopInsReq: stopActiveLearnInsReq,
editInsReq: editActiveLearnInsReq,
title: '自动学习',
pathPrefix: 'active-learn',
nameProperty: 'name',
descProperty: 'description',
idProperty: 'activeLearnId',
idInsProperty: 'active_learn_id',
},
};

+ 105
- 46
react-ui/src/pages/AutoML/components/ExperimentList/index.tsx View File

@@ -30,7 +30,7 @@ import {
} from 'antd';
import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import ExperimentInstanceList from '../ExperimentInstanceList';
import { ExperimentListType, experimentListConfig } from './config';
import styles from './index.less';
@@ -52,7 +52,6 @@ function ExperimentList({ type }: ExperimentListProps) {
const [experimentInsList, setExperimentInsList] = useState<ExperimentInstanceData[]>([]);
const [expandedRowKeys, setExpandedRowKeys] = useState<number[]>([]);
const [experimentInsTotal, setExperimentInsTotal] = useState(0);
const [now] = useServerTime();
const [pagination, setPagination] = useState<TablePaginationConfig>(
cacheState?.pagination ?? {
current: 1,
@@ -60,10 +59,10 @@ function ExperimentList({ type }: ExperimentListProps) {
},
);
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) => {
const params: Record<string, any> = {
page: pagination.current! - 1,
@@ -81,20 +80,16 @@ function ExperimentList({ type }: ExperimentListProps) {
[pagination, searchText, config],
);

useEffect(() => {
getAutoMLList();
}, [getAutoMLList]);

// 获取实验实例列表
const getExperimentInsList = useCallback(
async (recordId: number, page: number, size: number) => {
async (recordId: number, page: number, size: number, skipLoading: boolean = false) => {
const params = {
[config.idProperty]: recordId,
page: page,
size: size,
};
const request = config.getInsListReq;
const [res] = await to(request(params));
const [res] = await to(request(params, skipLoading));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
try {
@@ -116,60 +111,113 @@ function ExperimentList({ type }: ExperimentListProps) {
// TODO: 目前是直接刷新实验列表,后续需要优化,只刷新状态
const refreshExperimentList = useCallback(
(skipLoading: boolean = false) => {
getAutoMLList(skipLoading);
getExperimentList(skipLoading);
},
[getAutoMLList],
[getExperimentList],
);

// 刷新实验实例列表
const refreshExperimentIns = useCallback(
(experimentId: number) => {
(experimentId: number, skipLoading: boolean = false) => {
const length = experimentInsList.length;
getExperimentInsList(experimentId, 0, length);
getExperimentInsList(experimentId, 0, length, skipLoading);
},
[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(() => {
const handleMessage = (e: MessageEvent) => {
const { type, payload } = e.data;
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);
return () => {
window.removeEventListener('message', handleMessage);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}
};
}, [refreshExperimentList]);
}, [experimentInsList, editExperimentIns]);

// 搜索
const onSearch: SearchProps['onSearch'] = (value) => {
@@ -213,6 +261,7 @@ function ExperimentList({ type }: ExperimentListProps) {
setCacheState({
pagination,
searchText,
expandedRowKeys,
});

if (record) {
@@ -231,6 +280,7 @@ function ExperimentList({ type }: ExperimentListProps) {
setCacheState({
pagination,
searchText,
expandedRowKeys,
});

navigate(`info/${record.id}`);
@@ -243,8 +293,8 @@ function ExperimentList({ type }: ExperimentListProps) {
if (res) {
message.success('运行成功');
setExpandedRowKeys([record.id]);
refreshExperimentList();
getExperimentInsList(record.id, 0, 5);
// getExperimentInsList(record.id, 0, 5);
// refreshExperimentList();
}
};

@@ -254,8 +304,8 @@ function ExperimentList({ type }: ExperimentListProps) {
setExperimentInsList([]);
if (expanded) {
setExpandedRowKeys([record.id]);
getExperimentInsList(record.id, 0, 5);
refreshExperimentList();
// getExperimentInsList(record.id, 0, 5);
// refreshExperimentList();
} else {
setExpandedRowKeys([]);
}
@@ -263,6 +313,11 @@ function ExperimentList({ type }: ExperimentListProps) {

// 跳转到实验实例详情
const gotoInstanceInfo = (autoML: AutoMLData, record: ExperimentInstanceData) => {
setCacheState({
pagination,
searchText,
expandedRowKeys,
});
navigate(`instance/${autoML.id}/${record.id}`);
};

@@ -275,8 +330,7 @@ function ExperimentList({ type }: ExperimentListProps) {

// 实验实例终止
const handleInstanceTerminate = async (experimentIns: ExperimentInstanceData) => {
// 刷新实验列表
refreshExperimentList();
// 修改实例的状态和结束时间
setExperimentInsList((prevList) => {
return prevList.map((item) => {
if (item.id === experimentIns.id) {
@@ -289,6 +343,11 @@ function ExperimentList({ type }: ExperimentListProps) {
return item;
});
});
// 刷新实验列表
refreshExperimentList(true);
if (expandedRowKeys.length > 0) {
refreshExperimentIns(expandedRowKeys[0]);
}
};
// --------------------------- Table ---------------------------
// 分页切换


+ 18
- 15
react-ui/src/pages/AutoML/components/ExperimentRunBasic/index.tsx View File

@@ -3,58 +3,61 @@ import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { type NodeStatus } from '@/types';
import { getExperimentInstanceStatus } from '@/utils/experiment';
import { formatDate } from '@/utils/format';
import { Flex } from 'antd';
import { useMemo } from 'react';

type ExperimentRunBasicProps = {
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus;
};

function ExperimentRunBasic({ runStatus, instanceStatus }: ExperimentRunBasicProps) {
function ExperimentRunBasic({ workflowStatus, instanceStatus }: ExperimentRunBasicProps) {
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];

return [
{
label: '启动时间',
value: formatDate(runStatus.startedAt),
value: formatDate(workflowStatus?.startedAt),
},
{
label: '执行时长',
value: <RunDuration createTime={runStatus.startedAt} finishTime={runStatus.finishedAt} />,
value: (
<RunDuration
createTime={workflowStatus?.startedAt}
finishTime={workflowStatus?.finishedAt}
/>
),
},
{
label: '状态',
value: (
value: statusInfo ? (
<Flex align="center">
<img
style={{ width: '17px', marginRight: '7px' }}
src={statusInfo?.icon}
src={statusInfo.icon}
draggable={false}
alt=""
/>
<div
style={{
color: statusInfo?.color,
color: statusInfo.color,
fontSize: '15px',
lineHeight: 1.6,
}}
>
{statusInfo?.label}
{statusInfo.label}
</div>
</Flex>
) : (
'--'
),
},
];
}, [runStatus, instanceStatus]);
}, [workflowStatus, instanceStatus]);

return (
<ConfigInfo


+ 30
- 21
react-ui/src/pages/Experiment/Info/index.jsx View File

@@ -7,6 +7,7 @@ import { getWorkflowById } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { fittingString, parseJsonText } from '@/utils';
import { formatDate } from '@/utils/date';
import { getExperimentInstanceStatus } from '@/utils/experiment';
import { to } from '@/utils/promise';
import G6, { Util } from '@antv/g6';
import { Button } from 'antd';
@@ -34,6 +35,8 @@ function ExperimentText() {
const evtSourceRef = useRef();
const width = 110;
const height = 36;
const status = getExperimentInstanceStatus(experimentIns?.status, workflowStatus);
const statusInfo = experimentStatusInfo[status];

useEffect(() => {
initGraph();
@@ -92,16 +95,17 @@ function ExperimentText() {
const workflowData = workflowRef.current;
const experimentStatusObjs = parseJsonText(nodes_status);
if (experimentStatusObjs) {
// 更新各个节点
workflowData.nodes.forEach((item) => {
const experimentNode = experimentStatusObjs?.[item.id];
updateWorkflowNode(item, experimentNode);
});

// 处理workflow状态
// 设置 workflow 总状态
Object.keys(experimentStatusObjs).some((key) => {
if (key.startsWith(NodePrefix)) {
const workflowStatus = experimentStatusObjs[key];
setWorkflowStatus(workflowStatus);
const tempWorkflowStatus = experimentStatusObjs[key];
setWorkflowStatus(tempWorkflowStatus);
return true;
}
return false;
@@ -154,27 +158,30 @@ function ExperimentText() {
if (!statusData) {
return;
}
const { startedAt, finishedAt, phase, nodes = {} } = statusData;
const { finishedAt, phase, nodes = {} } = statusData;
// 更新实验实例状态和结束时间
setExperimentIns((prev) => ({
...prev,
finish_time: finishedAt,
status: phase,
}));

const workflowStatus = Object.values(nodes).find((node) =>
// 设置总 workflow 状态
const tempWorkflowStatus = Object.values(nodes).find((node) =>
node.displayName.startsWith(NodePrefix),
);

// 设置工作流状态
if (workflowStatus) {
setWorkflowStatus(workflowStatus);
if (tempWorkflowStatus) {
setWorkflowStatus(tempWorkflowStatus);
}

// 更新各个节点
const workflowData = workflowRef.current;
workflowData.nodes.forEach((item) => {
const experimentNode = Object.values(nodes).find((node) => node.displayName === item.id);
updateWorkflowNode(item, experimentNode);
});

// 绘制图
getGraphData(workflowData, false);

// 更新打开的抽屉数据
@@ -200,6 +207,7 @@ function ExperimentText() {
evtSourceRef.current = evtSource;
};

// 更新各个节点
function updateWorkflowNode(workflowNode, statusNode) {
if (!statusNode) {
return;
@@ -505,18 +513,19 @@ function ExperimentText() {
</div>
<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>
<Button
className={styles['pipeline-container__top__param-button']}


+ 0
- 4
react-ui/src/pages/Experiment/components/ExperimentInstanceList/index.less View File

@@ -55,10 +55,6 @@
display: flex;
align-items: center;
width: 160px;

.statusIcon {
visibility: visible;
}
}
}



+ 23
- 16
react-ui/src/pages/Experiment/components/ExperimentInstanceList/instance.tsx View File

@@ -3,9 +3,9 @@ import { ExperimentStatus } from '@/enums';
import { useSSE, type MessageHandler } from '@/hooks/useSSE';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance, NodeStatus } from '@/types';
import { getWorkflowStatus } from '@/utils';
import { ExperimentCompleted } from '@/utils/constant';
import { formatDate } from '@/utils/date';
import { getExperimentInstanceStatus, getWorkflowStatus } from '@/utils/experiment';
import { Typography } from 'antd';
import React, { useCallback } from 'react';
import styles from './index.less';
@@ -15,26 +15,29 @@ type 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 status = getExperimentInstanceStatus(instance.status as ExperimentStatus);
const createTime = workflowStatus?.startedAt ?? create_time;
const finishTime = workflowStatus?.finishedAt ?? finish_time;
const statusInfo = experimentStatusInfo[status];

const handleSSEMessage: MessageHandler = useCallback(
(experimentInsId: number, status: string, finish_time: string) => {
(experimentId: number, experimentInsId: number, status: string, finishTime: string) => {
window.postMessage({
type: ExperimentCompleted,
payload: {
id: experimentInsId,
experimentId,
experimentInsId,
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 (
<React.Fragment>
@@ -49,15 +52,19 @@ function ExperimentInstanceComponent({ instance }: ExperimentInstanceComponentPr
</div>
</div>
<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>
</React.Fragment>
);


+ 159
- 117
react-ui/src/pages/Experiment/index.jsx View File

@@ -5,6 +5,7 @@ import { useCacheState } from '@/hooks/useCacheState';
import { useServerTime } from '@/hooks/useServerTime';
import {
deleteExperimentById,
editExperimentInsReq,
getExperiment,
getExperimentById,
getQueryByExperimentId,
@@ -40,7 +41,7 @@ function Experiment() {
const [workflowList, setWorkflowList] = useState([]);
const [experimentId, setExperimentId] = useState(null);
const [experimentInsList, setExperimentInsList] = useState([]);
const [expandedRowKeys, setExpandedRowKeys] = useState(null);
const [expandedRowKeys, setExpandedRowKeys] = useState([]);
const [total, setTotal] = useState(0);
const [isAdd, setIsAdd] = useState(true);
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -61,7 +62,7 @@ function Experiment() {

// 获取实验列表
const getExperimentList = useCallback(
async (skipLoading) => {
async (skipLoading = false) => {
const params = {
page: pagination.current - 1,
size: pagination.pageSize,
@@ -84,12 +85,114 @@ function Experiment() {
// 刷新实验列表状态,
// 目前是直接刷新实验列表,后续需要优化,只刷新状态
const refreshExperimentList = useCallback(
(skipLoading) => {
(skipLoading = false) => {
getExperimentList(skipLoading);
},
[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(() => {
// 获取流水线列表
@@ -111,52 +214,66 @@ function Experiment() {
clearExperimentInTimers();
};
}, []);

// 获取实验列表
useEffect(() => {
getExperimentList();
}, [getExperimentList]);

// 新增,删除版本时,重置分页,然后刷新版本列表
// 更新实验实例状态
useEffect(() => {
const handleMessage = (e) => {
const { type, payload } = e.data;
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);
return () => {
window.removeEventListener('message', handleMessage);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}
};
}, [refreshExperimentList]);
}, [experimentInsList, editExperimentIns]);

// 搜索
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
const runTensorBoard = async (experimentIn) => {
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();
setExperimentInsList([]);
if (record.id === expandedRowKeys) {
setExpandedRowKeys(null);
} else {
getQueryByExperiment(record.id, 0, 5);
if (expanded) {
setExpandedRowKeys([record.id]);
getExperimentInsList(record.id, 0, 5);
refreshExperimentList();
} else {
setExpandedRowKeys([]);
}
};

@@ -344,8 +390,9 @@ function Experiment() {
const [res] = await to(runExperiments(id));
if (res) {
message.success('运行成功');
setExpandedRowKeys([id]);
refreshExperimentList();
getQueryByExperiment(id, 0, 5);
getExperimentInsList(id, 0, 5);
}
};

@@ -388,8 +435,6 @@ function Experiment() {

// 实验实例终止
const handleInstanceTerminate = async (experimentIn) => {
// 刷新实验列表
refreshExperimentList();
setExperimentInsList((prevList) => {
return prevList.map((item) => {
if (item.id === experimentIn.id) {
@@ -402,6 +447,9 @@ function Experiment() {
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 page = Math.round(experimentInsList.length / 5);
getQueryByExperiment(expandedRowKeys, page, 5);
getExperimentInsList(expandedRowKeys, page, 5);
};

// 处理删除
@@ -617,7 +659,7 @@ function Experiment() {
></ExperimentInstanceList>
),
onExpand: expandChange,
expandedRowKeys: [expandedRowKeys],
expandedRowKeys: expandedRowKeys,
}}
/>
</div>


+ 4
- 5
react-ui/src/pages/HyperParameter/Instance/index.tsx View File

@@ -120,18 +120,17 @@ function HyperParameterInstance() {
if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
// 节点
// 设置节点
setNodes(nodes);

// 设置总 workflow 状态
const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix),
) as NodeStatus;

// 设置工作流状态
if (workflowStatus) {
setWorkflowStatus(workflowStatus);

// 实验结束,关闭 SSE
// 实验结束,关闭 SSE,获取实验实例结果
if (
workflowStatus.phase !== ExperimentStatus.Pending &&
workflowStatus.phase !== ExperimentStatus.Running
@@ -166,7 +165,7 @@ function HyperParameterInstance() {
<HyperParameterBasic
className={styles['hyper-parameter-instance__basic']}
info={experimentInfo}
runStatus={workflowStatus}
workflowStatus={workflowStatus}
instanceStatus={instanceInfo?.status as ExperimentStatus}
isInstance
/>


+ 4
- 4
react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx View File

@@ -30,14 +30,14 @@ type HyperParameterBasicProps = {
info?: HyperParameterData;
className?: string;
isInstance?: boolean;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus;
};

function HyperParameterBasic({
info,
className,
runStatus,
workflowStatus,
instanceStatus,
isInstance = false,
}: HyperParameterBasicProps) {
@@ -144,8 +144,8 @@ function HyperParameterBasic({

return (
<div className={classNames(styles['hyper-parameter-basic'], className)}>
{isInstance && runStatus && (
<ExperimentRunBasic runStatus={runStatus} instanceStatus={instanceStatus} />
{isInstance && workflowStatus && (
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
)}
{!isInstance && (
<ConfigInfo


+ 5
- 5
react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx View File

@@ -16,7 +16,7 @@ function AssetsManagement() {
};
const [res] = await to(getWorkspaceAssetCountReq(params));
if (res && res.data) {
const { component, dataset, image, model, workflow } = res.data;
const { dataset, image, model, workflow } = res.data;
const items = [
{
title: '数据集',
@@ -30,10 +30,10 @@ function AssetsManagement() {
title: '镜像',
value: image,
},
{
title: '组件',
value: component,
},
// {
// title: '组件',
// value: component,
// },
// {
// title: '代码配置',
// value: 0,


+ 11
- 1
react-ui/src/services/activeLearn/index.js View File

@@ -55,10 +55,11 @@ export function runActiveLearnReq(id) {

// ----------------------- 实验实例 -----------------------
// 获取实验实例列表
export function getActiveLearnInsListReq(params) {
export function getActiveLearnInsListReq(params, skipLoading) {
return request(`/api/mmp/activeLearnIns`, {
method: 'GET',
params,
skipLoading
});
}

@@ -100,3 +101,12 @@ export function getExpMetricsReq(data) {
});
}

// 编辑实验实例
export function editActiveLearnInsReq(data) {
return request(`/api/mmp/activeLearnIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}


+ 12
- 2
react-ui/src/services/autoML/index.js View File

@@ -7,7 +7,7 @@
import { request } from '@umijs/max';

// 分页查询自动学习
export function getAutoMLListReq(params,skipLoading) {
export function getAutoMLListReq(params, skipLoading) {
return request(`/api/mmp/machineLearn`, {
method: 'GET',
params,
@@ -55,10 +55,11 @@ export function runAutoMLReq(id) {

// ----------------------- 实验实例 -----------------------
// 获取实验实例列表
export function getExperimentInsListReq(params) {
export function getExperimentInsListReq(params, skipLoading) {
return request(`/api/mmp/machineLearnIns`, {
method: 'GET',
params,
skipLoading,
});
}

@@ -90,3 +91,12 @@ export function batchDeleteExperimentInsReq(data) {
data,
});
}

// 编辑实验实例
export function editExperimentInsReq(data) {
return request(`/api/mmp/machineLearnIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}

+ 12
- 1
react-ui/src/services/experiment/index.js View File

@@ -29,10 +29,11 @@ export function deleteExperimentById(id) {
});
}
// 根据id查询实验实例
export function getQueryByExperimentId(params) {
export function getQueryByExperimentId(params, skipLoading) {
return request(`/api/mmp/experimentIns`, {
method: 'GET',
params,
skipLoading
});
}
// 根据id删除实验实例
@@ -54,6 +55,16 @@ export function putQueryByExperimentInsId(id) {
method: 'PUT',
});
}

// 编辑实验实例
export function editExperimentInsReq(data) {
return request(`/api/mmp/experimentIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}

// 查询实验实例实时日志
export function getQueryByExperimentLog(data) {
return request('/api/mmp/experimentIns/realTimeLog/', {


+ 12
- 1
react-ui/src/services/hyperParameter/index.js View File

@@ -55,10 +55,11 @@ export function runRayReq(id) {

// ----------------------- 实验实例 -----------------------
// 获取实验实例列表
export function getRayInsListReq(params) {
export function getRayInsListReq(params, skipLoading) {
return request(`/api/mmp/rayIns`, {
method: 'GET',
params,
skipLoading,
});
}

@@ -98,3 +99,13 @@ export function getExpMetricsReq(data) {
data,
});
}


// 编辑实验实例
export function editRayInsReq(data) {
return request(`/api/mmp/rayIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}

+ 5
- 2
react-ui/src/utils/date.ts View File

@@ -1,4 +1,3 @@
import { now } from '@/hooks/useServerTime';
import dayjs from 'dayjs';

/**
@@ -13,8 +12,12 @@ export const elapsedTime = (begin?: string | Date | null, end?: string | Date |
return '--';
}

if (end === undefined || end === null) {
return '--';
}

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()) {
return '--';
}


+ 60
- 0
react-ui/src/utils/experiment.ts View File

@@ -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);
};

+ 0
- 24
react-ui/src/utils/index.ts View File

@@ -352,27 +352,3 @@ export const trimCharacter = (str: string, ch: string): string => {
export const convertEmptyStringToUndefined = (value?: string): string | undefined => {
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;
};

Loading…
Cancel
Save