Browse Source

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

pull/221/head
cp3hnu 9 months ago
parent
commit
f3c14ce66e
29 changed files with 634 additions and 325 deletions
  1. +2
    -0
      react-ui/src/app.tsx
  2. +35
    -0
      react-ui/src/components/RunDuration/index.tsx
  3. +16
    -23
      react-ui/src/hooks/useSSE.ts
  4. +54
    -0
      react-ui/src/hooks/useServerTime.ts
  5. +4
    -3
      react-ui/src/pages/ActiveLearn/Instance/index.tsx
  6. +14
    -52
      react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx
  7. +4
    -3
      react-ui/src/pages/AutoML/Instance/index.tsx
  8. +14
    -48
      react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx
  9. +0
    -0
      react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.less
  10. +14
    -29
      react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.tsx
  11. +68
    -0
      react-ui/src/pages/AutoML/components/ExperimentInstanceList/instance.tsx
  12. +95
    -42
      react-ui/src/pages/AutoML/components/ExperimentList/index.tsx
  13. +70
    -0
      react-ui/src/pages/AutoML/components/ExperimentRunBasic/index.tsx
  14. +0
    -2
      react-ui/src/pages/Dataset/components/ResourceItem/index.less
  15. +3
    -16
      react-ui/src/pages/Experiment/Info/index.jsx
  16. +4
    -19
      react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx
  17. +0
    -0
      react-ui/src/pages/Experiment/components/ExperimentInstanceList/index.less
  18. +16
    -29
      react-ui/src/pages/Experiment/components/ExperimentInstanceList/index.tsx
  19. +70
    -0
      react-ui/src/pages/Experiment/components/ExperimentInstanceList/instance.tsx
  20. +91
    -42
      react-ui/src/pages/Experiment/index.jsx
  21. +5
    -4
      react-ui/src/pages/HyperParameter/Instance/index.tsx
  22. +10
    -8
      react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx
  23. +1
    -0
      react-ui/src/pages/Points/components/Statistics/index.less
  24. +10
    -3
      react-ui/src/pages/Points/components/Statistics/index.tsx
  25. +5
    -2
      react-ui/src/pages/Workspace/components/UserPoints/index.tsx
  26. +8
    -0
      react-ui/src/services/experiment/index.js
  27. +3
    -0
      react-ui/src/utils/constant.ts
  28. +16
    -0
      react-ui/src/utils/format.ts
  29. +2
    -0
      react-ui/src/utils/index.ts

+ 2
- 0
react-ui/src/app.tsx View File

@@ -11,6 +11,7 @@ import { getAccessToken } from './access';
import ErrorBoundary from './components/ErrorBoundary';
import './dayjsConfig';
import { removeAllPageCacheState } from './hooks/useCacheState';
import { globalGetSeverTime } from './hooks/useServerTime';
import {
getRemoteMenu,
getRoutersInfo,
@@ -29,6 +30,7 @@ export { requestConfig as request } from './requestConfig';
export async function getInitialState(): Promise<GlobalInitialState> {
const fetchUserInfo = async () => {
try {
globalGetSeverTime();
const response = await getUserInfo();
return {
...response.user,


+ 35
- 0
react-ui/src/components/RunDuration/index.tsx View File

@@ -0,0 +1,35 @@
import { useServerTime } from '@/hooks/useServerTime';
import { elapsedTime } from '@/utils/date';
import React, { useEffect, useState } from 'react';

type RunDurationProps = {
createTime?: string;
finishTime?: string;
className?: string;
style?: React.CSSProperties;
};
function RunDuration({ createTime, finishTime, className, style }: RunDurationProps) {
const [now] = useServerTime();
const [currentTime, setCurrentTime] = useState<Date>(now());

// 定时刷新耗时
useEffect(() => {
if (finishTime) {
setCurrentTime(new Date(finishTime));
} else {
const timer = setInterval(() => {
setCurrentTime(now());
}, 1000);
return () => {
clearInterval(timer);
};
}
}, [finishTime, now]);
return (
<span className={className} style={style}>
{elapsedTime(createTime, currentTime)}
</span>
);
}

export default RunDuration;

+ 16
- 23
react-ui/src/hooks/useSSE.ts View File

@@ -1,11 +1,12 @@
import { parseJsonText } from '@/utils';
import { useCallback, useRef } from 'react';
import { useEffect } from 'react';
import { ExperimentStatus } from '@/enums';
import { NodeStatus } from '@/types';

export const useSSE = (onMessage: (data: any) => void) => {
const evtSourceRef = useRef<EventSource | null>(null);

const setupSSE = useCallback(
(name: string, namespace: string) => {
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) => {
useEffect(() => {
if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) {
const { origin } = location;
const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`);
const evtSource = new EventSource(
@@ -18,11 +19,10 @@ export const useSSE = (onMessage: (data: any) => void) => {
return;
}
const dataJson = parseJsonText(data);
if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
onMessage(nodes);
}
const statusData = dataJson?.result?.object?.status;
if (statusData) {
const { finishedAt, phase, nodes } = statusData;
onMessage(experimentInsId, phase, finishedAt, nodes);
}
};

@@ -30,17 +30,10 @@ export const useSSE = (onMessage: (data: any) => void) => {
console.error('SSE error: ', error);
};

evtSourceRef.current = evtSource;
},
[onMessage],
);

const closeSSE = useCallback(() => {
if (evtSourceRef.current) {
evtSourceRef.current.close();
evtSourceRef.current = null;
return () => {
evtSource.close();
}
}
}, []);

return [setupSSE, closeSSE];
}, [experimentInsId, status, name, namespace, onMessage]);
};

+ 54
- 0
react-ui/src/hooks/useServerTime.ts View File

@@ -0,0 +1,54 @@
/*
* @Author: 赵伟
* @Date: 2024-10-10 08:51:41
* @Description: 服务器时间 hook
*/

import { getSeverTimeReq } from '@/services/experiment';
import { to } from '@/utils/promise';
import { useCallback, useEffect, useState } from 'react';

let globalTimeOffset: number | undefined = undefined;

export const globalGetSeverTime = async () => {
const requestStartTime = Date.now()
const [res] = await to(getSeverTimeReq());
const requestEndTime = Date.now()
const requestDuration = (requestEndTime - requestStartTime) / 2;
if (res && res.data) {
const serverDate = new Date(res.data);
const timeOffset = serverDate.getTime() + requestDuration - requestEndTime ;
globalTimeOffset = timeOffset;
return timeOffset
}
};

export const now = () => {
return new Date(Date.now() + (globalTimeOffset ?? 0))
}

/** 获取服务器时间 */
export function useServerTime() {
const [timeOffset, setTimeOffset] = useState<number>(globalTimeOffset ?? 0);

useEffect(() => {
// 获取服务器时间
const getSeverTime = async () => {
const [res] = await to(globalGetSeverTime());
if (res) {
setTimeOffset(res)
}
};

if (!globalTimeOffset) {
getSeverTime();
}
}, []);

const now = useCallback(() => {
return new Date(Date.now() + timeOffset)
}, [timeOffset])


return [now, timeOffset] as const;
}

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

@@ -106,13 +106,13 @@ function ActiveLearnInstance() {
if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
// 节点
setNodes(nodes);

const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix),
) as NodeStatus;

// 节点
setNodes(nodes);

// 设置工作流状态
if (workflowStatus) {
setWorkflowStatus(workflowStatus);
@@ -153,6 +153,7 @@ function ActiveLearnInstance() {
className={styles['active-learn-instance__basic']}
info={experimentInfo}
runStatus={workflowStatus}
instanceStatus={instanceInfo?.status}
isInstance
/>
),


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

@@ -1,5 +1,5 @@
import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo';
import { AutoMLTaskType, autoMLTaskTypeOptions } from '@/enums';
import { AutoMLTaskType, autoMLTaskTypeOptions, ExperimentStatus } from '@/enums';
import { useComputingResource } from '@/hooks/useComputingResource';
import {
classifierAlgorithms,
@@ -9,9 +9,8 @@ import {
regressorAlgorithms,
} from '@/pages/ActiveLearn/components/CreateForm/utils';
import { ActiveLearnData } from '@/pages/ActiveLearn/types';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import ExperimentRunBasic from '@/pages/AutoML/components/ExperimentRunBasic';
import { type NodeStatus } from '@/types';
import { elapsedTime } from '@/utils/date';
import {
formatBoolean,
formatCodeConfig,
@@ -21,7 +20,6 @@ import {
formatMirror,
formatModel,
} from '@/utils/format';
import { Flex } from 'antd';
import classNames from 'classnames';
import { useMemo } from 'react';
import styles from './index.less';
@@ -31,9 +29,16 @@ type BasicInfoProps = {
className?: string;
isInstance?: boolean;
runStatus?: NodeStatus;
instanceStatus?: ExperimentStatus;
};

function BasicInfo({ info, className, runStatus, isInstance = false }: BasicInfoProps) {
function BasicInfo({
info,
className,
runStatus,
instanceStatus,
isInstance = false,
}: BasicInfoProps) {
const getResourceDescription = useComputingResource()[1];
const basicDatas: BasicInfoData[] = useMemo(() => {
if (!info) {
@@ -205,56 +210,13 @@ function BasicInfo({ info, className, runStatus, isInstance = false }: BasicInfo
];
}, [info, getResourceDescription]);

const instanceDatas = useMemo(() => {
if (!info || !runStatus) {
return [];
}

return [
{
label: '启动时间',
value: formatDate(info.create_time),
ellipsis: true,
},
{
label: '执行时长',
value: elapsedTime(info.create_time, runStatus.finishedAt),
ellipsis: true,
},
{
label: '状态',
value: (
<Flex align="center">
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[runStatus.phase]?.icon}
draggable={false}
alt=""
/>
<div
style={{
color: experimentStatusInfo[runStatus?.phase]?.color,
fontSize: '15px',
lineHeight: 1.6,
}}
>
{experimentStatusInfo[runStatus?.phase]?.label}
</div>
</Flex>
),
ellipsis: true,
},
];
}, [runStatus, info]);

return (
<div className={classNames(styles['active-learn-basic'], className)}>
{isInstance && runStatus && (
<ConfigInfo
title="运行信息"
datas={instanceDatas}
labelWidth={70}
style={{ marginBottom: '20px' }}
<ExperimentRunBasic
create_time={info?.create_time}
runStatus={runStatus}
instanceStatus={instanceStatus}
/>
)}
{!isInstance && (


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

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

const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix),
) as NodeStatus;

// 节点
setNodes(nodes);

if (workflowStatus) {
setWorkflowStatus(workflowStatus);

@@ -156,6 +156,7 @@ function AutoMLInstance() {
className={styles['auto-ml-instance__basic']}
info={autoMLInfo}
runStatus={workflowStatus}
instanceStatus={instanceInfo?.status}
isInstance
/>
),


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

@@ -2,19 +2,18 @@ import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo';
import {
AutoMLTaskType,
AutoMLType,
ExperimentStatus,
autoMLEnsembleClassOptions,
autoMLTaskTypeOptions,
} from '@/enums';
import { useComputingResource } from '@/hooks/useComputingResource';
import { AutoMLData } from '@/pages/AutoML/types';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { type NodeStatus } from '@/types';
import { parseJsonText } from '@/utils';
import { elapsedTime } from '@/utils/date';
import { formatBoolean, formatDataset, formatDate, formatEnum } from '@/utils/format';
import { Flex } from 'antd';
import classNames from 'classnames';
import { useMemo } from 'react';
import ExperimentRunBasic from '../ExperimentRunBasic';
import styles from './index.less';

// 格式化优化方向
@@ -40,9 +39,16 @@ type AutoMLBasicProps = {
className?: string;
isInstance?: boolean;
runStatus?: NodeStatus;
instanceStatus?: ExperimentStatus;
};

function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLBasicProps) {
function AutoMLBasic({
info,
className,
runStatus,
instanceStatus,
isInstance = false,
}: AutoMLBasicProps) {
const getResourceDescription = useComputingResource()[1];
const basicDatas: BasicInfoData[] = useMemo(() => {
if (!info) {
@@ -284,53 +290,13 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB
];
}, [info]);

const instanceDatas = useMemo(() => {
if (!info || !runStatus) {
return [];
}

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

return (
<div className={classNames(styles['auto-ml-basic'], className)}>
{isInstance && runStatus && (
<ConfigInfo
title="运行信息"
datas={instanceDatas}
labelWidth={70}
style={{ marginBottom: '20px' }}
<ExperimentRunBasic
create_time={info?.create_time}
runStatus={runStatus}
instanceStatus={instanceStatus}
/>
)}
{!isInstance && (


react-ui/src/pages/AutoML/components/ExperimentInstance/index.less → react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.less View File


react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx → react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.tsx View File

@@ -1,20 +1,19 @@
import KFIcon from '@/components/KFIcon';
import { ExperimentStatus } from '@/enums';
import { useCheck } from '@/hooks/useCheck';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import themes from '@/styles/theme.less';
import { type ExperimentInstance } from '@/types';
import { elapsedTime, formatDate } from '@/utils/date';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { DoubleRightOutlined } from '@ant-design/icons';
import { App, Button, Checkbox, ConfigProvider, Typography } from 'antd';
import { App, Button, Checkbox, ConfigProvider } from 'antd';
import classNames from 'classnames';
import { useEffect, useMemo } from 'react';
import { ExperimentListType, experimentListConfig } from '../ExperimentList/config';
import styles from './index.less';
import ExperimentInstanceComponent from './instance';

type ExperimentInstanceProps = {
type ExperimentInstanceListProps = {
type: ExperimentListType;
experimentInsList?: ExperimentInstance[];
experimentInsTotal: number;
@@ -24,7 +23,7 @@ type ExperimentInstanceProps = {
onLoadMore?: () => void;
};

function ExperimentInstanceComponent({
function ExperimentInstanceList({
type,
experimentInsList,
experimentInsTotal,
@@ -32,7 +31,7 @@ function ExperimentInstanceComponent({
onRemove,
onTerminate,
onLoadMore,
}: ExperimentInstanceProps) {
}: ExperimentInstanceListProps) {
const { message } = App.useApp();
const allIntanceIds = useMemo(() => {
return experimentInsList?.map((item) => item.id) || [];
@@ -171,28 +170,14 @@ function ExperimentInstanceComponent({
>
{index + 1}
</a>
<div className={styles.description}>
{elapsedTime(item.create_time, item.finish_time)}
</div>
<div className={styles.startTime}>
<Typography.Text ellipsis={{ tooltip: formatDate(item.create_time) }}>
{formatDate(item.create_time)}
</Typography.Text>
</div>
<div className={styles.statusBox}>
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[item.status as ExperimentStatus]?.icon}
draggable={false}
alt=""
/>
<span
style={{ color: experimentStatusInfo[item.status as ExperimentStatus]?.color }}
className={styles.statusIcon}
>
{experimentStatusInfo[item.status as ExperimentStatus]?.label}
</span>
</div>
<ExperimentInstanceComponent
create_time={item.create_time}
finish_time={item.finish_time}
status={item.status as ExperimentStatus}
argo_ins_name={item.argo_ins_name}
argo_ins_ns={item.argo_ins_ns}
experimentInsId={item.id}
></ExperimentInstanceComponent>
<div className={styles.operation}>
<Button
type="link"
@@ -244,4 +229,4 @@ function ExperimentInstanceComponent({
);
}

export default ExperimentInstanceComponent;
export default ExperimentInstanceList;

+ 68
- 0
react-ui/src/pages/AutoML/components/ExperimentInstanceList/instance.tsx View File

@@ -0,0 +1,68 @@
import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums';
import { useSSE, type MessageHandler } from '@/hooks/useSSE';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentCompleted } from '@/utils/constant';
import { formatDate } from '@/utils/date';
import { Typography } from 'antd';
import React, { useCallback } from 'react';
import styles from './index.less';

type ExperimentInstanceProps = {
create_time?: string;
finish_time?: string;
status: ExperimentStatus;
argo_ins_name: string;
argo_ins_ns: string;
experimentInsId: number;
};

function ExperimentInstance({
create_time,
finish_time,
status,
argo_ins_name,
argo_ins_ns,
experimentInsId,
}: ExperimentInstanceProps) {
const handleSSEMessage: MessageHandler = useCallback(
(experimentInsId: number, status: string, finish_time: string) => {
window.postMessage({
type: ExperimentCompleted,
payload: {
id: experimentInsId,
status,
finish_time,
},
});
},
[],
);
useSSE(experimentInsId, status, argo_ins_name, argo_ins_ns, handleSSEMessage);

return (
<React.Fragment>
<div className={styles.description}>
<RunDuration createTime={create_time} finishTime={finish_time} />
</div>
<div className={styles.startTime}>
<Typography.Text ellipsis={{ tooltip: formatDate(create_time) }}>
{formatDate(create_time)}
</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>
</div>
</React.Fragment>
);
}

export default ExperimentInstance;

+ 95
- 42
react-ui/src/pages/AutoML/components/ExperimentList/index.tsx View File

@@ -8,10 +8,12 @@ import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { ExperimentStatus, autoMLTypeOptions } from '@/enums';
import { useCacheState } from '@/hooks/useCacheState';
import { useServerTime } from '@/hooks/useServerTime';
import { AutoMLData } from '@/pages/AutoML/types';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import themes from '@/styles/theme.less';
import { type ExperimentInstance as ExperimentInstanceData } from '@/types';
import { ExperimentCompleted } from '@/utils/constant';
import { to } from '@/utils/promise';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
@@ -28,8 +30,8 @@ import {
} from 'antd';
import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames';
import { useCallback, useEffect, useState } from 'react';
import ExperimentInstance from '../ExperimentInstance';
import { useCallback, useEffect, useRef, useState } from 'react';
import ExperimentInstanceList from '../ExperimentInstanceList';
import { ExperimentListType, experimentListConfig } from './config';
import styles from './index.less';

@@ -50,6 +52,7 @@ 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,
@@ -57,6 +60,7 @@ function ExperimentList({ type }: ExperimentListProps) {
},
);
const config = experimentListConfig[type];
const timerRef = useRef<ReturnType<typeof window.setTimeout> | undefined>();

// 获取自主机器学习或超参数自动优化列表
const getAutoMLList = useCallback(async () => {
@@ -78,6 +82,89 @@ function ExperimentList({ type }: ExperimentListProps) {
getAutoMLList();
}, [getAutoMLList]);

// 获取实验实例列表
const getExperimentInsList = useCallback(
async (recordId: number, page: number, size: number) => {
const params = {
[config.idProperty]: recordId,
page: page,
size: size,
};
const request = config.getInsListReq;
const [res] = await to(request(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
try {
if (page === 0) {
setExperimentInsList(content);
} else {
setExperimentInsList((prev) => [...prev, ...content]);
}
setExperimentInsTotal(totalElements);
} catch (error) {
console.error('JSON parse error: ', error);
}
}
},
[config.getInsListReq, config.idProperty],
);

// 刷新实验列表状态,
// TODO: 目前是直接刷新实验列表,后续需要优化,只刷新状态
const refreshExperimentList = useCallback(() => {
getAutoMLList();
}, [getAutoMLList]);

// 刷新实验实例列表
const refreshExperimentIns = useCallback(
(experimentId: number) => {
const length = experimentInsList.length;
getExperimentInsList(experimentId, 0, length);
},
[getExperimentInsList, experimentInsList],
);

// 新增,删除版本时,重置分页,然后刷新版本列表
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,
),
);

if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}

timerRef.current = setTimeout(() => {
refreshExperimentList();
}, 10000);
}
};

window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}
};
}, [refreshExperimentList]);

// 搜索
const onSearch: SearchProps['onSearch'] = (value) => {
setSearchText(value);
@@ -151,40 +238,17 @@ function ExperimentList({ type }: ExperimentListProps) {
message.success('运行成功');
setExpandedRowKeys([record.id]);
refreshExperimentList();
refreshExperimentIns(record.id);
getExperimentInsList(record.id, 0, 5);
}
};

// --------------------------- 实验实例 ---------------------------
// 获取实验实例列表
const getExperimentInsList = async (recordId: number, page: number) => {
const params = {
[config.idProperty]: recordId,
page: page,
size: 5,
};
const request = config.getInsListReq;
const [res] = await to(request(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
try {
if (page === 0) {
setExperimentInsList(content);
} else {
setExperimentInsList((prev) => [...prev, ...content]);
}
setExperimentInsTotal(totalElements);
} catch (error) {
console.error('JSON parse error: ', error);
}
}
};
// 展开实例
const handleExpandChange = (expanded: boolean, record: AutoMLData) => {
setExperimentInsList([]);
if (expanded) {
setExpandedRowKeys([record.id]);
getExperimentInsList(record.id, 0);
getExperimentInsList(record.id, 0, 5);
refreshExperimentList();
} else {
setExpandedRowKeys([]);
@@ -196,16 +260,11 @@ function ExperimentList({ type }: ExperimentListProps) {
navigate(`instance/${autoML.id}/${record.id}`);
};

// 刷新实验实例列表
const refreshExperimentIns = (experimentId: number) => {
getExperimentInsList(experimentId, 0);
};

// 加载更多实验实例
const loadMoreExperimentIns = () => {
const page = Math.round(experimentInsList.length / 5);
const recordId = expandedRowKeys[0];
getExperimentInsList(recordId, page);
getExperimentInsList(recordId, page, 5);
};

// 实验实例终止
@@ -218,19 +277,13 @@ function ExperimentList({ type }: ExperimentListProps) {
return {
...item,
status: ExperimentStatus.Terminated,
finish_time: now().toISOString(),
};
}
return item;
});
});
};

// 刷新实验列表状态,
// 目前是直接刷新实验列表,后续需要优化,只刷新状态
const refreshExperimentList = () => {
getAutoMLList();
};

// --------------------------- Table ---------------------------
// 分页切换
const handleTableChange: TableProps<AutoMLData>['onChange'] = (
@@ -409,7 +462,7 @@ function ExperimentList({ type }: ExperimentListProps) {
onChange={handleTableChange}
expandable={{
expandedRowRender: (record) => (
<ExperimentInstance
<ExperimentInstanceList
type={type}
experimentInsList={experimentInsList}
experimentInsTotal={experimentInsTotal}
@@ -420,7 +473,7 @@ function ExperimentList({ type }: ExperimentListProps) {
}}
onTerminate={handleInstanceTerminate}
onLoadMore={() => loadMoreExperimentIns()}
></ExperimentInstance>
></ExperimentInstanceList>
),
onExpand: handleExpandChange,
expandedRowKeys: expandedRowKeys,


+ 70
- 0
react-ui/src/pages/AutoML/components/ExperimentRunBasic/index.tsx View File

@@ -0,0 +1,70 @@
import ConfigInfo from '@/components/ConfigInfo';
import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { type NodeStatus } from '@/types';
import { formatDate } from '@/utils/format';
import { Flex } from 'antd';
import { useMemo } from 'react';

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

function ExperimentRunBasic({ create_time, runStatus, instanceStatus }: ExperimentRunBasicProps) {
const instanceDatas = useMemo(() => {
if (!runStatus) {
return [];
}

const status =
instanceStatus === ExperimentStatus.Terminated ? instanceStatus : runStatus.phase;
const statusInfo = experimentStatusInfo[status];

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

return (
<ConfigInfo
title="运行信息"
datas={instanceDatas}
labelWidth={70}
style={{ marginBottom: '20px' }}
/>
);
}

export default ExperimentRunBasic;

+ 0
- 2
react-ui/src/pages/Dataset/components/ResourceItem/index.less View File

@@ -55,9 +55,7 @@
}
&__time {
display: flex;
flex: 0 1 content;
align-items: center;
width: 100%;
color: #808080;
font-size: 13px;
}


+ 3
- 16
react-ui/src/pages/Experiment/Info/index.jsx View File

@@ -15,6 +15,8 @@ import ExperimentDrawer from '../components/ExperimentDrawer';
import ParamsModal from '../components/ViewParamsModal';
import { experimentStatusInfo } from '../status';
import styles from './index.less';
import { useServerTime } from '@/hooks/useServerTime';
import RunDuration from '@/components/RunDuration';

let graph = null;

@@ -27,7 +29,6 @@ function ExperimentText() {
const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false);
const [propsDrawerOpen, openPropsDrawer, closePropsDrawer, propsDrawerOpenRef] =
useVisible(false);
const [currentDate, setCurrentDate] = useState();
const navigate = useNavigate();
const evtSourceRef = useRef();
const width = 110;
@@ -60,19 +61,6 @@ function ExperimentText() {
};
}, []);

// 定时刷新耗时
useEffect(() => {
if (experimentIns && !experimentIns.finish_time) {
const timer = setInterval(() => {
setCurrentDate(new Date());
console.log('定时刷新');
}, 1000);
return () => {
clearInterval(timer);
};
}
}, [experimentIns]);

// 获取流水线模版
const getWorkflow = async () => {
const [res] = await to(getWorkflowById(locationParams.workflowId));
@@ -100,7 +88,6 @@ function ExperimentText() {
if (res && res.data && workflowRef.current) {
setExperimentIns(res.data);
const { status, nodes_status, argo_ins_ns, argo_ins_name, finish_time } = res.data;
setCurrentDate(new Date(finish_time));
const workflowData = workflowRef.current;
const experimentStatusObjs = parseJsonText(nodes_status);
workflowData.nodes.forEach((item) => {
@@ -489,7 +476,7 @@ function ExperimentText() {
</div>
<div className={styles['pipeline-container__top__info']}>
执行时长:
{elapsedTime(experimentIns?.create_time, experimentIns?.finish_time)}
<RunDuration createTime={experimentIns?.create_time} finishTime={experimentIns?.finish_time} />
</div>
<div className={styles['pipeline-container__top__info']}>
状态:


+ 4
- 19
react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx View File

@@ -1,10 +1,11 @@
import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { PipelineNodeModelSerialize } from '@/types';
import { elapsedTime, formatDate } from '@/utils/date';
import { formatDate } from '@/utils/date';
import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons';
import { Drawer, Tabs, Typography } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import { useMemo } from 'react';
import ExperimentParameter from '../ExperimentParameter';
import ExperimentResult from '../ExperimentResult';
import LogList from '../LogList';
@@ -41,22 +42,6 @@ const ExperimentDrawer = ({
instanceNodeStartTime,
instanceNodeEndTime,
}: ExperimentDrawerProps) => {
const [currentDate, setCurrentDate] = useState(
instanceNodeEndTime ? new Date(instanceNodeEndTime) : new Date(),
);

// 定时刷新耗时
useEffect(() => {
if (!instanceNodeEndTime) {
const timer = setInterval(() => {
setCurrentDate(new Date());
}, 1000);
return () => {
clearInterval(timer);
};
}
}, [instanceNodeEndTime]);

// 如果性能有问题,可以进一步拆解
const items = useMemo(
() => [
@@ -158,7 +143,7 @@ const ExperimentDrawer = ({
</div>
<div className={styles['experiment-drawer__info']}>
耗时:
{elapsedTime(instanceNodeStartTime, currentDate)}
<RunDuration createTime={instanceNodeStartTime} finishTime={instanceNodeEndTime} />
</div>
</div>
<Tabs


react-ui/src/pages/Experiment/components/ExperimentInstance/index.less → react-ui/src/pages/Experiment/components/ExperimentInstanceList/index.less View File


react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx → react-ui/src/pages/Experiment/components/ExperimentInstanceList/index.tsx View File

@@ -1,7 +1,6 @@
import KFIcon from '@/components/KFIcon';
import { ExperimentStatus } from '@/enums';
import { useCheck } from '@/hooks/useCheck';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import {
deleteManyExperimentIns,
deleteQueryByExperimentInsId,
@@ -9,17 +8,17 @@ import {
} from '@/services/experiment/index.js';
import themes from '@/styles/theme.less';
import { type ExperimentInstance } from '@/types';
import { elapsedTime, formatDate } from '@/utils/date';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { DoubleRightOutlined } from '@ant-design/icons';
import { App, Button, Checkbox, ConfigProvider, Typography } from 'antd';
import { App, Button, Checkbox, ConfigProvider } from 'antd';
import classNames from 'classnames';
import { useEffect, useMemo } from 'react';
import TensorBoardStatusCell from '../TensorBoardStatus';
import styles from './index.less';
import ExperimentInstanceComponent from './instance';

type ExperimentInstanceProps = {
type ExperimentInstanceListProps = {
experimentInsList?: ExperimentInstance[];
experimentInsTotal: number;
onClickInstance?: (instance: ExperimentInstance) => void;
@@ -29,7 +28,7 @@ type ExperimentInstanceProps = {
onLoadMore?: () => void;
};

function ExperimentInstanceComponent({
function ExperimentInstanceList({
experimentInsList,
experimentInsTotal,
onClickInstance,
@@ -37,7 +36,7 @@ function ExperimentInstanceComponent({
onRemove,
onTerminate,
onLoadMore,
}: ExperimentInstanceProps) {
}: ExperimentInstanceListProps) {
const { message } = App.useApp();
const allIntanceIds = useMemo(() => {
return experimentInsList?.map((item) => item.id) || [];
@@ -185,28 +184,16 @@ function ExperimentInstanceComponent({
'--'
)}
</div>
<div className={styles.description}>
<div style={{ width: '50%' }}>{elapsedTime(item.create_time, item.finish_time)}</div>
<div style={{ width: '50%' }} className={styles.startTime}>
<Typography.Text ellipsis={{ tooltip: formatDate(item.create_time) }}>
{formatDate(item.create_time)}
</Typography.Text>
</div>
</div>
<div className={styles.statusBox}>
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[item.status as ExperimentStatus]?.icon}
draggable={false}
alt=""
/>
<span
style={{ color: experimentStatusInfo[item.status as ExperimentStatus]?.color }}
className={styles.statusIcon}
>
{experimentStatusInfo[item.status as ExperimentStatus]?.label}
</span>
</div>

<ExperimentInstanceComponent
create_time={item.create_time}
finish_time={item.finish_time}
status={item.status as ExperimentStatus}
argo_ins_name={item.argo_ins_name}
argo_ins_ns={item.argo_ins_ns}
experimentInsId={item.id}
></ExperimentInstanceComponent>

<div className={styles.operation}>
<Button
type="link"
@@ -258,4 +245,4 @@ function ExperimentInstanceComponent({
);
}

export default ExperimentInstanceComponent;
export default ExperimentInstanceList;

+ 70
- 0
react-ui/src/pages/Experiment/components/ExperimentInstanceList/instance.tsx View File

@@ -0,0 +1,70 @@
import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums';
import { useSSE, type MessageHandler } from '@/hooks/useSSE';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentCompleted } from '@/utils/constant';
import { formatDate } from '@/utils/date';
import { Typography } from 'antd';
import React, { useCallback } from 'react';
import styles from './index.less';

type ExperimentInstanceProps = {
create_time?: string;
finish_time?: string;
status: ExperimentStatus;
argo_ins_name: string;
argo_ins_ns: string;
experimentInsId: number;
};

function ExperimentInstance({
create_time,
finish_time,
status,
argo_ins_name,
argo_ins_ns,
experimentInsId,
}: ExperimentInstanceProps) {
const handleSSEMessage: MessageHandler = useCallback(
(experimentInsId: number, status: string, finish_time: string) => {
window.postMessage({
type: ExperimentCompleted,
payload: {
id: experimentInsId,
status,
finish_time,
},
});
},
[],
);
useSSE(experimentInsId, status, argo_ins_name, argo_ins_ns, handleSSEMessage);

return (
<React.Fragment>
<div className={styles.description}>
<div style={{ width: '50%' }}>
<RunDuration createTime={create_time} finishTime={finish_time} />
</div>
<div style={{ width: '50%' }} className={styles.startTime}>
<Typography.Text ellipsis={{ tooltip: formatDate(create_time) }}>
{formatDate(create_time)}
</Typography.Text>
</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>
</div>
</React.Fragment>
);
}

export default ExperimentInstance;

+ 91
- 42
react-ui/src/pages/Experiment/index.jsx View File

@@ -15,18 +15,20 @@ import {
} from '@/services/experiment/index.js';
import { getWorkflow } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { ExperimentCompleted } from '@/utils/constant';
import { to } from '@/utils/promise';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { App, Button, ConfigProvider, Dropdown, Input, Space, Table, Tooltip } from 'antd';
import classNames from 'classnames';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { ComparisonType } from './Comparison/config';
import AddExperimentModal from './components/AddExperimentModal';
import ExperimentInstance from './components/ExperimentInstance';
import ExperimentInstanceList from './components/ExperimentInstanceList';
import styles from './index.less';
import { experimentStatusInfo } from './status';
import { useServerTime } from '@/hooks/useServerTime';

// 定时器
const timerIds = new Map();
@@ -36,7 +38,7 @@ function Experiment() {
const [experimentList, setExperimentList] = useState([]);
const [workflowList, setWorkflowList] = useState([]);
const [experimentId, setExperimentId] = useState(null);
const [experimentInList, setExperimentInList] = useState([]);
const [experimentInsList, setExperimentInsList] = useState([]);
const [expandedRowKeys, setExpandedRowKeys] = useState(null);
const [total, setTotal] = useState(0);
const [isAdd, setIsAdd] = useState(true);
@@ -46,6 +48,7 @@ function Experiment() {
const [cacheState, setCacheState] = useCacheState();
const [searchText, setSearchText] = useState(cacheState?.searchText);
const [inputText, setInputText] = useState(cacheState?.searchText);
const [now] = useServerTime();
const [pagination, setPagination] = useState(
cacheState?.pagination ?? {
current: 1,
@@ -53,7 +56,34 @@ function Experiment() {
},
);
const { message } = App.useApp();
const timerRef = useRef();

// 获取实验列表
const getExperimentList = useCallback(async () => {
const params = {
page: pagination.current - 1,
size: pagination.pageSize,
name: searchText || undefined,
};
const [res] = await to(getExperiment(params));
if (res && res.data && Array.isArray(res.data.content)) {
setExperimentList(
res.data.content.map((item) => {
return { ...item, key: item.id };
}),
);

setTotal(res.data.totalElements);
}
}, [pagination, searchText]);

// 刷新实验列表状态,
// 目前是直接刷新实验列表,后续需要优化,只刷新状态
const refreshExperimentList = useCallback(() => {
getExperimentList();
}, [getExperimentList]);

// 获取流水线列表
useEffect(() => {
// 获取流水线列表
const getWorkflowList = async () => {
@@ -76,28 +106,51 @@ function Experiment() {
}, []);

// 获取实验列表
const getExperimentList = useCallback(async () => {
const params = {
page: pagination.current - 1,
size: pagination.pageSize,
name: searchText || undefined,
};
const [res] = await to(getExperiment(params));
if (res && res.data && Array.isArray(res.data.content)) {
setExperimentList(
res.data.content.map((item) => {
return { ...item, key: item.id };
}),
);

setTotal(res.data.totalElements);
}
}, [pagination, searchText]);

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,
),
);

if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}

timerRef.current = setTimeout(() => {
refreshExperimentList();
}, 10000);
}
};

window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}
};
}, [refreshExperimentList]);

// 搜索
const onSearch = (value) => {
setSearchText(value);
@@ -108,11 +161,11 @@ function Experiment() {
};

// 获取实验实例列表
const getQueryByExperiment = async (experimentId, page) => {
const getQueryByExperiment = async (experimentId, page, size = 5) => {
const params = {
experimentId: experimentId,
page: page,
size: 5,
size: size,
};
const [res, error] = await to(getQueryByExperimentId(params));
if (res && res.data) {
@@ -127,10 +180,10 @@ function Experiment() {
};
});
if (page === 0) {
setExperimentInList(list);
setExperimentInsList(list);
clearExperimentInTimers();
} else {
setExperimentInList((prev) => [...prev, ...list]);
setExperimentInsList((prev) => [...prev, ...list]);
}
setExperimentInsTotal(totalElements);
// 获取 TensorBoard 状态
@@ -173,7 +226,7 @@ function Experiment() {
};
const [res] = await to(getTensorBoardStatusReq(params));
if (res && res.data) {
setExperimentInList((prevList) => {
setExperimentInsList((prevList) => {
return prevList.map((item) => {
if (item.id === experimentIn.id) {
return {
@@ -201,11 +254,11 @@ function Experiment() {
// 展开实例
const expandChange = (e, record) => {
clearExperimentInTimers();
setExperimentInList([]);
setExperimentInsList([]);
if (record.id === expandedRowKeys) {
setExpandedRowKeys(null);
} else {
getQueryByExperiment(record.id, 0);
getQueryByExperiment(record.id, 0, 5);
refreshExperimentList();
}
};
@@ -285,7 +338,7 @@ function Experiment() {
if (res) {
message.success('运行成功');
refreshExperimentList();
refreshExperimentIns(id);
getQueryByExperiment(id, 0, 5);
}
};

@@ -323,22 +376,17 @@ function Experiment() {
}
};

// 刷新实验列表状态,
// 目前是直接刷新实验列表,后续需要优化,只刷新状态
const refreshExperimentList = () => {
getExperimentList();
};

// 实验实例终止
const handleInstanceTerminate = async (experimentIn) => {
// 刷新实验列表
refreshExperimentList();
setExperimentInList((prevList) => {
setExperimentInsList((prevList) => {
return prevList.map((item) => {
if (item.id === experimentIn.id) {
return {
...item,
status: ExperimentStatus.Terminated,
finish_time: now().toISOString(),
};
}
return item;
@@ -367,13 +415,14 @@ function Experiment() {

// 刷新实验实例列表
const refreshExperimentIns = (experimentId) => {
getQueryByExperiment(experimentId, 0);
const length = experimentInsList.length;
getQueryByExperiment(experimentId, 0, length);
};

// 加载更多实验实例
const loadMoreExperimentIns = () => {
const page = Math.round(experimentInList.length / 5);
getQueryByExperiment(expandedRowKeys, page);
const page = Math.round(experimentInsList.length / 5);
getQueryByExperiment(expandedRowKeys, page, 5);
};

// 处理删除
@@ -544,8 +593,8 @@ function Experiment() {
scroll={{ y: 'calc(100% - 55px)' }}
expandable={{
expandedRowRender: (record) => (
<ExperimentInstance
experimentInsList={experimentInList}
<ExperimentInstanceList
experimentInsList={experimentInsList}
experimentInsTotal={experimentInsTotal}
onClickInstance={(item) => gotoInstanceInfo(item, record)}
onClickTensorBoard={handleTensorboard}
@@ -555,7 +604,7 @@ function Experiment() {
}}
onTerminate={handleInstanceTerminate}
onLoadMore={() => loadMoreExperimentIns()}
></ExperimentInstance>
></ExperimentInstanceList>
),
onExpand: expandChange,
expandedRowKeys: [expandedRowKeys],


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

@@ -53,7 +53,7 @@ function HyperParameterInstance() {
const info = res.data as HyperParameterInstanceData;
const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info;
// 解析配置参数
const paramJson = parseJsonText(param);
const paramJson = parseJsonText(param).data;
if (paramJson) {
// 实例详情返回的参数是字符串,需要转换
if (typeof paramJson.parameters === 'string') {
@@ -121,13 +121,13 @@ function HyperParameterInstance() {
if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
// 节点
setNodes(nodes);

const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix),
) as NodeStatus;

// 节点
setNodes(nodes);

// 设置工作流状态
if (workflowStatus) {
setWorkflowStatus(workflowStatus);
@@ -168,6 +168,7 @@ function HyperParameterInstance() {
className={styles['hyper-parameter-instance__basic']}
info={experimentInfo}
runStatus={workflowStatus}
instanceStatus={instanceInfo?.status}
isInstance
/>
),


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

@@ -1,6 +1,8 @@
import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo';
import { hyperParameterOptimizedMode } from '@/enums';
import RunDuration from '@/components/RunDuration';
import { ExperimentStatus, hyperParameterOptimizedMode } from '@/enums';
import { useComputingResource } from '@/hooks/useComputingResource';
import ExperimentRunBasic from '@/pages/AutoML/components/ExperimentRunBasic';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import {
schedulerAlgorithms,
@@ -8,7 +10,6 @@ import {
} from '@/pages/HyperParameter/components/CreateForm/utils';
import { HyperParameterData } from '@/pages/HyperParameter/types';
import { type NodeStatus } from '@/types';
import { elapsedTime } from '@/utils/date';
import {
formatCodeConfig,
formatDataset,
@@ -33,12 +34,14 @@ type HyperParameterBasicProps = {
className?: string;
isInstance?: boolean;
runStatus?: NodeStatus;
instanceStatus?: ExperimentStatus;
};

function HyperParameterBasic({
info,
className,
runStatus,
instanceStatus,
isInstance = false,
}: HyperParameterBasicProps) {
const getResourceDescription = useComputingResource()[1];
@@ -155,7 +158,7 @@ function HyperParameterBasic({
},
{
label: '执行时长',
value: elapsedTime(info.create_time, runStatus.finishedAt),
value: <RunDuration createTime={info.create_time} finishTime={runStatus.finishedAt} />,
ellipsis: true,
},
{
@@ -187,11 +190,10 @@ function HyperParameterBasic({
return (
<div className={classNames(styles['hyper-parameter-basic'], className)}>
{isInstance && runStatus && (
<ConfigInfo
title="运行信息"
datas={instanceDatas}
labelWidth={70}
style={{ marginBottom: '20px' }}
<ExperimentRunBasic
create_time={info?.create_time}
runStatus={runStatus}
instanceStatus={instanceStatus}
/>
)}
{!isInstance && (


+ 1
- 0
react-ui/src/pages/Points/components/Statistics/index.less View File

@@ -11,6 +11,7 @@
flex: 1;
flex-direction: column;
align-items: center;
padding: 0 20px;

&--border {
border-right: 1px solid @border-color;


+ 10
- 3
react-ui/src/pages/Points/components/Statistics/index.tsx View File

@@ -1,3 +1,5 @@
import { formatNumber } from '@/utils/format';
import { Typography } from 'antd';
import classNames from 'classnames';
import styles from './index.less';

@@ -10,11 +12,11 @@ function Statistics({ remaining, consuming }: StatisticsProps) {
const items = [
{
title: '当前可用算力积分(分)',
value: remaining ?? '-',
value: remaining,
},
{
title: '总消耗算力积分(分)',
value: consuming ?? '-',
value: consuming,
},
];

@@ -27,7 +29,12 @@ function Statistics({ remaining, consuming }: StatisticsProps) {
[styles['statistics__item--border']]: index === 0,
})}
>
<span className={styles['statistics__item__value']}>{item.value}</span>
<Typography.Paragraph
ellipsis={{ tooltip: formatNumber(item.value) }}
className={styles['statistics__item__value']}
>
{formatNumber(item.value)}
</Typography.Paragraph>
<span className={styles['statistics__item__title']}>{item.title}</span>
</div>
))}


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

@@ -1,5 +1,6 @@
import { PointsStatistics } from '@/pages/Points/index';
import { getPointsStatisticsReq } from '@/services/points';
import { formatNumber } from '@/utils/format';
import { to } from '@/utils/promise';
import { useNavigate } from '@umijs/max';
import { Typography } from 'antd';
@@ -22,14 +23,16 @@ function UserPoints() {
getPointsStatistics();
}, []);

const userCredit = formatNumber(statistics?.userCredit);

return (
<div className={styles['user-points']}>
<div className={styles['user-points__label']}>当前可用算力积分</div>
<Typography.Paragraph
className={styles['user-points__value']}
ellipsis={{ tooltip: statistics?.userCredit ?? '--' }}
ellipsis={{ tooltip: userCredit }}
>
{statistics?.userCredit ?? '--'}
{userCredit}
</Typography.Paragraph>
<div
className={styles['user-points__button']}


+ 8
- 0
react-ui/src/services/experiment/index.js View File

@@ -151,3 +151,11 @@ export function getExpMetricsReq(data) {
data,
});
}

// 获取服务器的当前时间
export function getSeverTimeReq(data) {
return request(`/api/mmp/experimentIns/time`, {
method: 'GET',
});
}


+ 3
- 0
react-ui/src/utils/constant.ts View File

@@ -17,3 +17,6 @@ export const VersionChangedMessage = 'versionChanged';

// 创建服务成功消息,去创建服务版本
export const ServiceCreatedMessage = 'serviceCreated';

// 实验完成
export const ExperimentCompleted = 'ExperimentCompleted';

+ 16
- 0
react-ui/src/utils/format.ts View File

@@ -193,3 +193,19 @@ export const formatEnum = (options: EnumOptions[]): FormatEnumFunc => {
return option && option.label ? option.label : '--';
};
};


/**
* 格式化数字
*
* @param value - 值、
* @param toFixed - 保留几位小数
* @return 格式化的数字,如果不是数字,返回 '--'
*/
export const formatNumber = (value?: number | null, toFixed?: number) : number | string => {
if (typeof value !== "number") {
return '--'
}

return toFixed ? Number(value).toFixed(toFixed) : value
}

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

@@ -6,6 +6,7 @@

import { PageEnum } from '@/enums/pagesEnums';
import G6 from '@antv/g6';
import { number } from 'echarts';

/**
* 生成 8 位随机数
@@ -346,3 +347,4 @@ export const trimCharacter = (str: string, ch: string): string => {
export const convertEmptyStringToUndefined = (value?: string): string | undefined => {
return value === '' ? undefined : value;
};


Loading…
Cancel
Save