Browse Source

feat: 流水线组件重构

pull/273/head
zhaowei 8 months ago
parent
commit
55f86f3e77
13 changed files with 1692 additions and 300 deletions
  1. +1286
    -0
      react-ui/mock/components.ts
  2. +2
    -2
      react-ui/package.json
  3. +4
    -1
      react-ui/src/components/ParameterInput/index.tsx
  4. +29
    -44
      react-ui/src/components/ResourceSelect/index.tsx
  5. +4
    -5
      react-ui/src/components/ResourceSelectorModal/config.tsx
  6. +24
    -16
      react-ui/src/components/ResourceSelectorModal/index.tsx
  7. +8
    -0
      react-ui/src/enums/index.ts
  8. +1
    -0
      react-ui/src/pages/Mirror/Info/index.tsx
  9. +3
    -6
      react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx
  10. +1
    -1
      react-ui/src/pages/Pipeline/Info/index.jsx
  11. +25
    -4
      react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.less
  12. +296
    -207
      react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx
  13. +9
    -14
      react-ui/src/types.ts

+ 1286
- 0
react-ui/mock/components.ts
File diff suppressed because it is too large
View File


+ 2
- 2
react-ui/package.json View File

@@ -8,7 +8,7 @@
"build": "max build",
"deploy": "npm run build && npm run gh-pages",
"dev": "npm run start:dev",
"dev-no-sso": "cross-env NO_SSO=true npm run start:dev",
"dev-no-sso": "cross-env NO_SSO=true npm run start:mock",
"docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./",
"docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build",
"docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up",
@@ -35,7 +35,7 @@
"serve": "umi-serve",
"start": "cross-env UMI_ENV=dev max dev",
"start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev UMI_DEV_SERVER_COMPRESS=none max dev",
"start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev max dev",
"start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev UMI_DEV_SERVER_COMPRESS=none max dev",
"start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev",
"start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev",
"storybook": "storybook dev -p 6006",


+ 4
- 1
react-ui/src/components/ParameterInput/index.tsx View File

@@ -9,6 +9,7 @@ import { CloseOutlined } from '@ant-design/icons';
import { ConfigProvider, Form, Input, Typography } from 'antd';
import { RuleObject } from 'antd/es/form';
import classNames from 'classnames';
import { ReactNode } from 'react';
import './index.less';

// 如果值是对象时的类型
@@ -55,6 +56,8 @@ export interface ParameterInputProps {
disabled?: boolean;
/** 元素 id */
id?: string;
/** 带标签的 input,设置后置标签 */
addonAfter?: ReactNode;
}

function ParameterInput({
@@ -75,7 +78,7 @@ function ParameterInput({
const valueObj =
typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value;
if (valueObj && !valueObj.showValue) {
valueObj.showValue = valueObj.value;
valueObj.showValue = typeof valueObj.value === 'string' ? valueObj.value : '';
}
const isSelect = valueObj?.fromSelect;
const placeholder = valueObj?.placeholder || rest?.placeholder;


+ 29
- 44
react-ui/src/components/ResourceSelect/index.tsx View File

@@ -10,10 +10,10 @@ import ResourceSelectorModal, {
ResourceSelectorType,
selectorTypeConfig,
} from '@/components/ResourceSelectorModal';
import { CommonTabKeys } from '@/enums';
import { openAntdModal } from '@/utils/modal';
import { Button, ConfigProvider } from 'antd';
import classNames from 'classnames';
import { pick } from 'lodash';
import ParameterInput, { type ParameterInputProps } from '../ParameterInput';
import './index.less';

@@ -27,6 +27,8 @@ export { ResourceSelectorType, selectorTypeConfig, type ResourceSelectorResponse
interface ResourceSelectProps extends ParameterInputProps {
/** 类型,数据集、模型、镜像 */
type: ResourceSelectorType;
/** 值 */
value?: ResourceSelectorResponse;
}

// 获取选择数据集、模型、镜像后面按钮 icon
@@ -47,68 +49,51 @@ function ResourceSelect({
}: ResourceSelectProps) {
const { componentSize } = ConfigProvider.useConfig();
const mySize = size || componentSize;
let selectedResource: ResourceSelectorResponse | undefined = undefined;
if (
value &&
typeof value === 'object' &&
value.activeTab &&
value.id &&
value.name &&
value.version &&
value.path &&
(type === ResourceSelectorType.Mirror || (value.identifier && value.owner))
) {
selectedResource = pick(value, [
'activeTab',
'id',
'identifier',
'name',
'owner',
'version',
'path',
]) as ResourceSelectorResponse;
let defaultActiveTab: CommonTabKeys | undefined,
defaultExpandedKeys: string[] = [],
defaultCheckedKeys: string[] = [];
if (value && typeof value === 'object') {
defaultActiveTab = value.activeTab;
if (type === ResourceSelectorType.Mirror) {
if (value.image_id && value.id) {
defaultExpandedKeys = [`${value.image_id}`];
defaultCheckedKeys = [`${value.image_id}-${value.id}`];
}
} else if (value.id && value.version) {
defaultExpandedKeys = [value.id];
defaultCheckedKeys = [`${value.id}-${value.version}`];
}
}

// 选择数据集、模型、镜像
const selectResource = () => {
const { close } = openAntdModal(ResourceSelectorModal, {
type,
defaultExpandedKeys: selectedResource ? [selectedResource.id] : [],
defaultCheckedKeys: selectedResource
? [`${selectedResource.id}-${selectedResource.version}`]
: [],
defaultActiveTab: selectedResource?.activeTab,
defaultExpandedKeys: defaultExpandedKeys,
defaultCheckedKeys: defaultCheckedKeys,
defaultActiveTab: defaultActiveTab,
onOk: (res) => {
if (res) {
const { activeTab, id, name, version, path, identifier, owner } = res;
if (type === ResourceSelectorType.Mirror) {
const { activeTab, ...rest } = res;
const { url } = rest;
onChange?.({
value: path,
showValue: path,
...rest,
value: url,
showValue: url,
fromSelect: true,
activeTab,
id,
name,
version,
path,
});
} else {
const jsonObj = {
id,
name,
version,
path,
identifier,
owner,
};
const jsonObjStr = JSON.stringify(jsonObj);
const { activeTab, ...rest } = res;
const { name, version } = rest;
const showValue = `${name}:${version}`;
onChange?.({
value: jsonObjStr,
...rest,
value: showValue,
showValue,
fromSelect: true,
activeTab,
...jsonObj,
});
}
} else {


+ 4
- 5
react-ui/src/components/ResourceSelectorModal/config.tsx View File

@@ -52,9 +52,9 @@ const convertResourceVersionToTreeData = (
): TreeDataNode[] => {
return list.map((item: ResourceVersionData) => ({
...pick(info, ['id', 'name', 'owner', 'identifier', 'is_public']),
version: item.name,
title: item.name,
key: `${parentId}-${item.name}`,
title: item.name,
version: item.name,
isLeaf: true,
checkable: true,
}));
@@ -66,12 +66,11 @@ const convertMirrorVersionToTreeData = (
list: MirrorVersionData[],
): TreeDataNode[] => {
return list.map((item: MirrorVersionData) => ({
url: item.url,
title: item.tag_name,
...item,
key: `${parentId}-${item.id}`,
title: item.tag_name,
isLeaf: true,
checkable: true,
description: item.description,
}));
};



+ 24
- 16
react-ui/src/components/ResourceSelectorModal/index.tsx View File

@@ -8,6 +8,7 @@ import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { CommonTabKeys } from '@/enums';
import { ResourceFileData } from '@/pages/Dataset/config';
import { type MirrorVersionData } from '@/pages/Mirror/Info';
import { to } from '@/utils/promise';
import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd';
import { Input, Tabs, Tree } from 'antd';
@@ -19,13 +20,13 @@ export { ResourceSelectorType, selectorTypeConfig };
// 选择数据集、模型、镜像的返回类型
export type ResourceSelectorResponse = {
activeTab: CommonTabKeys; // 是我的还是公开的
id: string; // 数据集\模型\镜像 id
name: string; // 数据集\模型\镜像 name
version: string; // 数据集\模型\镜像版本
path: string; // 数据集\模型\镜像版本路径
identifier: string; // 数据集\模型 identifier,镜像这个字段为空
owner: string; // 数据集\模型 owner,镜像这个字段为空
};
id: string; // 数据集\模型 id
name: string; // 数据集\模型 name
identifier: string; // 数据集\模型 identifier
owner: string; // 数据集\模型 owner
version: string; // 数据集\模型 version
path: string; // 数据集\模型 版本路径
} & MirrorVersionData;

export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> {
/** 类型,数据集、模型、镜像 */
@@ -241,15 +242,22 @@ function ResourceSelectorModal({
const name = (treeNode?.title ?? '') as string;
const identifier = (treeNode?.identifier ?? '') as string;
const owner = (treeNode?.owner ?? '') as string;
const res = {
id,
name,
path: versionPath,
version,
identifier,
owner,
activeTab: activeTab,
};
const childNode = treeNode.children.filter((v: TreeDataNode) => v.key === last)[0];
const res =
type === ResourceSelectorType.Mirror
? {
activeTab: activeTab,
...childNode,
}
: {
activeTab: activeTab,
id,
name,
path: versionPath,
version,
identifier,
owner,
};
onOk?.(res);
} else {
onOk?.(undefined);


+ 8
- 0
react-ui/src/enums/index.ts View File

@@ -163,3 +163,11 @@ export enum AutoMLTrailStatus {
CANCELLED = 'CANCELLED', // 取消
MEMOUT = 'MEMOUT', // 内存溢出
}

// 流水线组件类型
export enum ComponentType {
Ref = 'ref',
Select = 'select',
Map = 'map',
Str = 'str',
}

+ 1
- 0
react-ui/src/pages/Mirror/Info/index.tsx View File

@@ -46,6 +46,7 @@ export type MirrorInfoData = {
};

export type MirrorVersionData = {
image_id: number;
id: number;
version: string;
url: string;


+ 3
- 6
react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx View File

@@ -23,7 +23,7 @@ import { removeFormListItem } from '@/utils/ui';
import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { useNavigate, useParams } from '@umijs/max';
import { App, Button, Col, Flex, Form, Input, InputNumber, Row } from 'antd';
import { omit, pick } from 'lodash';
import { omit } from 'lodash';
import { useEffect, useState } from 'react';
import { CreateServiceVersionFrom, ServiceOperationType, ServiceVersionData } from '../types';
import styles from './index.less';
@@ -79,7 +79,7 @@ function CreateServiceVersion() {
if (res.model && typeof res.model === 'object') {
model = changePropertyName(res.model, { show_value: 'showValue' });
// 接口返回是数据没有 value 值,但是 form 需要 value
model.value = model.showValue;
// model.value = model.showValue;
}
// 环境变量
if (res.env_variables && typeof res.env_variables === 'object') {
@@ -128,10 +128,7 @@ function CreateServiceVersion() {
...omit(formData, ['replicas', 'env_variables', 'model']),
replicas: Number(formData.replicas),
env_variables: envVariables,
model: changePropertyName(
pick(model, ['id', 'name', 'version', 'path', 'identifier', 'owner', 'showValue']),
{ showValue: 'show_value' },
),
model: changePropertyName(model, { showValue: 'show_value' }),
service_id: serviceId,
};



+ 1
- 1
react-ui/src/pages/Pipeline/Info/index.jsx View File

@@ -290,7 +290,7 @@ const EditPipeline = () => {
const { global_param, dag } = res.data;
setGlobalParam(global_param || []);
if (dag) {
getGraphData(parseJsonText(dag));
getGraphData(dag);
}
}
};


+ 25
- 4
react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.less View File

@@ -21,10 +21,7 @@
background: #f8fbff;
}

&__ref-row {
display: flex;
align-items: center;

&__component {
&__select-button {
display: flex;
flex: none;
@@ -34,5 +31,29 @@
padding-right: 0;
padding-left: 0;
}

&__list-row {
:global {
.ant-row {
padding: 0 !important;
}
}

&:last-child {
:global {
.ant-form-item {
margin-bottom: 0 !important;
}
}
}
}

&__add-button {
border-color: .addAlpha(@primary-color, 0.5) [];
box-shadow: none !important;
&:hover {
border-style: solid;
}
}
}
}

+ 296
- 207
react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx View File

@@ -8,7 +8,7 @@ import ResourceSelectorModal, {
selectorTypeConfig,
} from '@/components/ResourceSelectorModal';
import SubAreaTitle from '@/components/SubAreaTitle';
import { CommonTabKeys } from '@/enums';
import { CommonTabKeys, ComponentType } from '@/enums';
import { canInput, createMenuItems } from '@/pages/Pipeline/Info/utils';
import {
PipelineGlobalParam,
@@ -19,14 +19,21 @@ import {
import { parseJsonText } from '@/utils';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import { removeFormListItem } from '@/utils/ui';
import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { INode } from '@antv/g6';
import { Button, Drawer, Form, Input, MenuProps } from 'antd';
import { Button, Drawer, Flex, Form, Input, MenuProps } from 'antd';
import { RuleObject } from 'antd/es/form';
import { NamePath } from 'antd/es/form/interface';
import { forwardRef, useImperativeHandle, useState } from 'react';
import PropsLabel from '../PropsLabel';
import styles from './index.less';
const { TextArea } = Input;

// 表单列表数据
export type FormListVariable = {
name: string; // 参数名
value: string; // 参数值
};

type PipelineNodeParameterProps = {
onFormChange: (data: PipelineNodeModelSerialize) => void;
@@ -37,12 +44,6 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>(
{} as PipelineNodeModelSerialize,
);
const nodeId = Form.useWatch('id', form) as string;
const hasTaskInfo =
nodeId &&
!nodeId.startsWith('git-clone') &&
!nodeId.startsWith('dataset-export') &&
!nodeId.startsWith('model-export');
const [open, setOpen] = useState(false);
const [menuItems, setMenuItems] = useState<MenuProps['items']>([]);

@@ -53,11 +54,16 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
// 不管是否验证成功,都需要获取表单数据
const fields = form.getFieldsValue();

// 保存字段顺序
// const control_strategy = {
// ...stagingItem.control_strategy,
// ...fields.control_strategy,
// };
// 保持原有字段和顺序
const task_info = {
...stagingItem.task_info,
...fields.task_info,
};

const control_strategy = {
...stagingItem.control_strategy,
...fields.control_strategy,
};

const in_parameters = {
...stagingItem.in_parameters,
@@ -68,17 +74,18 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
...fields.out_parameters,
};

// console.log('getFieldsValue', fields);
console.log('getFieldsValue', fields);

const res = {
...stagingItem,
...fields,
// control_strategy: JSON.stringify(control_strategy),
in_parameters: JSON.stringify(in_parameters),
out_parameters: JSON.stringify(out_parameters),
task_info: task_info,
control_strategy: control_strategy,
in_parameters: in_parameters,
out_parameters: out_parameters,
formError: !!error,
};
// console.log('res', res);
console.log('res', res);
onFormChange(res);
}
};
@@ -96,19 +103,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
validate: boolean = false,
) {
try {
const nodeData: PipelineNodeModelSerialize = {
...model,
in_parameters: JSON.parse(model.in_parameters),
out_parameters: JSON.parse(model.out_parameters),
// control_strategy: JSON.parse(model.control_strategy),
};
// console.log('model', nodeData);
setStagingItem({
...nodeData,
...model,
});
form.resetFields();
form.setFieldsValue({
...nodeData,
...model,
});
if (validate) {
form.validateFields();
@@ -237,32 +237,27 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
onOk: (res) => {
if (res) {
if (type === ResourceSelectorType.Mirror) {
const { activeTab, id, version, path } = res;
if (formItemName === 'image') {
// 单独的选择镜像
form.setFieldValue(formItemName, path);
} else {
// 输入参数选择镜像
form.setFieldValue(formItemName, {
...item,
value: path,
showValue: path,
fromSelect: true,
activeTab,
expandedKeys: [id],
checkedKeys: [`${id}-${version}`],
});
}
const { activeTab, ...rest } = res;
const { url, id, image_id } = rest;
form.setFieldValue(formItemName, {
...item,
value: rest,
showValue: url,
fromSelect: true,
activeTab,
expandedKeys: [`${image_id}`],
checkedKeys: [`${image_id}-${id}`],
});
} else {
const { activeTab, id, name, version, path, identifier, owner } = res;
const value = JSON.stringify({
const value = {
id,
name,
version,
path,
identifier,
owner,
});
};
const showValue = `${name}:${version}`;
form.setFieldValue(formItemName, {
...item,
@@ -275,19 +270,15 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
});
}
} else {
if (type === ResourceSelectorType.Mirror && formItemName === 'image') {
form.setFieldValue(formItemName, undefined);
} else {
form.setFieldValue(formItemName, {
...item,
value: undefined,
showValue: undefined,
fromSelect: false,
activeTab: undefined,
expandedKeys: [],
checkedKeys: [],
});
}
form.setFieldValue(formItemName, {
...item,
value: undefined,
showValue: undefined,
fromSelect: false,
activeTab: undefined,
expandedKeys: [],
checkedKeys: [],
});
}
form.validateFields([formItemName]);
close();
@@ -323,16 +314,16 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
// form item label
const getLabel = (
item: { key: string; value: PipelineNodeModelParameter },
namePrefix: string,
parentName: string,
) => {
return item.value.type === 'select' ? (
return item.value.type === ComponentType.Select || item.value.type === ComponentType.Map ? (
item.value.label + '(' + item.key + ')'
) : (
<PropsLabel
menuItems={menuItems}
title={item.value.label + '(' + item.key + ')'}
onClick={(value) => {
handleParameterClick([namePrefix, item.key], {
handleParameterClick([parentName, item.key], {
...item.value,
value,
fromSelect: true,
@@ -380,21 +371,229 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
return rules;
};

// 表单组件
const getFormComponent = (
item: { key: string; value: PipelineNodeModelParameter },
parentName: string,
) => {
return (
<>
{item.value.type === ComponentType.Ref && (
<Flex align="center">
<Form.Item name={[parentName, item.key]} rules={getFormRules(item)} noStyle>
<ParameterInput
canInput={canInput(item.value)}
placeholder={item.value.placeholder}
allowClear
></ParameterInput>
</Form.Item>
<Form.Item noStyle>
<Button
size="small"
type="link"
icon={getSelectBtnIcon(item.value)}
onClick={() => selectRefData([parentName, item.key], item.value)}
className={styles['pipeline-drawer__component__select-button']}
>
{item.value.label}
</Button>
</Form.Item>
</Flex>
)}
{item.value.type === ComponentType.Select &&
(['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? (
<Form.Item name={[parentName, item.key]} rules={getFormRules(item)} noStyle>
<ParameterSelect
isPipeline
dataType={item.value.item_type as ParameterSelectDataType}
placeholder={item.value.placeholder}
/>
</Form.Item>
) : null)}
{item.value.type === ComponentType.Map && (
<Form.Item name={[parentName, item.key]} rules={getFormRules(item)} noStyle>
{' '}
<Form.List name={[parentName, item.key, 'value']}>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }, index) => (
<Flex
key={key}
gap="0 8px"
style={{ width: '100%' }}
className={styles['pipeline-drawer__component__list-row']}
>
<Form.Item
{...restField}
name={[name, 'name']}
style={{ flex: 1 }}
dependencies={fields.map((_, i) => [
parentName,
item.key,
'value',
i,
'name',
])}
rules={[
{
validator: (_, value) => {
if (!value) {
return Promise.reject(new Error('请输入变量名'));
}
if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value)) {
return Promise.reject(
new Error(
'变量名只支持字母、数字、下划线、中横线并且必须以字母或下划线开头',
),
);
}
// 判断不能重名
const list = form
.getFieldValue([parentName, item.key, 'value'])
.filter(
(item: FormListVariable | undefined) =>
item !== undefined && item !== null,
);

const names = list.map((item: FormListVariable) => item.name);
if (new Set(names).size !== names.length) {
return Promise.reject(new Error('变量名不能重复'));
}
return Promise.resolve();
},
},
]}
>
<Input placeholder="请输入变量名" allowClear />
</Form.Item>
<span style={{ lineHeight: '32px' }}>=</span>
<Form.Item
{...restField}
name={[name, 'value']}
style={{ flex: 1 }}
rules={[
{
validator: requiredValidator,
message: '请输入变量值',
},
]}
>
{/* <Input placeholder="请输入变量值" allowClear /> */}
<ParameterInput
placeholder="请输入变量值"
allowClear
addonAfter={
<PropsLabel
menuItems={menuItems}
title=""
onClick={(value) => {
handleParameterClick(
[parentName, item.key, 'list', name, 'value'],
{
...item.value,
value,
fromSelect: true,
showValue: value,
},
);
}}
/>
}
></ParameterInput>
</Form.Item>
<Flex
style={{
width: '76px',
height: '32px',
}}
align="center"
>
<Button
style={{
marginRight: '3px',
}}
shape="circle"
size="middle"
type="text"
icon={<MinusCircleOutlined />}
onClick={() => {
removeFormListItem(
form,
'env_variables',
name,
remove,
['key', 'value'],
'删除后,该变量将不可恢复',
);
}}
></Button>
{index === fields.length - 1 && (
<Button
shape="circle"
size="middle"
type="text"
icon={<PlusCircleOutlined />}
onClick={() => add()}
></Button>
)}
</Flex>
</Flex>
))}
{fields.length === 0 && (
<Button
className={styles['pipeline-drawer__component__add-button']}
color="primary"
variant="dashed"
icon={<PlusOutlined />}
block
onClick={() => add()}
>
新增
</Button>
)}
</>
)}
</Form.List>
</Form.Item>
)}
{item.value.type === ComponentType.Str && (
<Form.Item name={[parentName, item.key]} rules={getFormRules(item)} noStyle>
<ParameterInput
canInput={canInput(item.value)}
placeholder={item.value.placeholder}
allowClear
></ParameterInput>
</Form.Item>
)}
</>
);
};

// 基本参数
const basicParametersList = Object.entries(stagingItem.task_info ?? {})
.map(([key, value]) => ({
key,
value,
}))
.filter((v) => v.value.visible === true);

// 控制策略
// const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map(
// ([key, value]) => ({ key, value }),
// );
const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {})
.map(([key, value]) => ({ key, value }))
.filter((v) => v.value.visible === true);

// 输入参数
const inParametersList = Object.entries(stagingItem.in_parameters ?? {}).map(([key, value]) => ({
key,
value,
}));
const inParametersList = Object.entries(stagingItem.in_parameters ?? {})
.map(([key, value]) => ({
key,
value,
}))
.filter((v) => v.value.visible === true);

// 输出参数
const outParametersList = Object.entries(stagingItem.out_parameters ?? {}).map(
([key, value]) => ({ key, value }),
);
const outParametersList = Object.entries(stagingItem.out_parameters ?? {})
.map(([key, value]) => ({ key, value }))
.filter((v) => v.value.visible === true);

return (
<Drawer
@@ -406,7 +605,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
onClose={onClose}
afterOpenChange={afterOpenChange}
open={open}
width={520}
width={620}
className={styles['pipeline-drawer']}
>
<Form
@@ -455,114 +654,35 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
>
<Input disabled />
</Form.Item>
{hasTaskInfo && (
<>
<div className={styles['pipeline-drawer__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div>
<Form.Item label="镜像" required>
<div className={styles['pipeline-drawer__ref-row']}>
<Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}>
<Input placeholder="请输入或选择镜像" allowClear />
</Form.Item>
<Form.Item noStyle>
<Button
type="link"
size="small"
icon={getSelectBtnIcon({ item_type: 'image' })}
onClick={() => selectResource('image', { item_type: 'image' })}
className={styles['pipeline-drawer__ref-row__select-button']}
>
选择镜像
</Button>
</Form.Item>
</div>
</Form.Item>
<Form.Item
name="working_directory"
label={
<PropsLabel
menuItems={menuItems}
title="工作目录"
onClick={(value) => {
handleParameterClick('working_directory', value);
}}
/>
}
>
<Input placeholder="请输入工作目录" allowClear />
</Form.Item>
<Form.Item
name="command"
label={
<PropsLabel
menuItems={menuItems}
title="启动命令"
onClick={(value) => {
handleParameterClick('command', value);
}}
/>
}
>
<TextArea placeholder="请输入启动命令" allowClear />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请选择资源规格',
},
]}
>
<ParameterSelect dataType="resource" placeholder="请选择资源规格" />
</Form.Item>
{/* <Form.Item
name="mount_path"
label={
<PropsLabel
menuItems={menuItems}
title="挂载路径"
onClick={(value) => {
handleParameterClick('mount_path', value);
}}
/>
}
>
<Input placeholder="请输入挂载路径" allowClear />
</Form.Item> */}
<Form.Item
name="env_variables"
label={
<PropsLabel
menuItems={menuItems}
title="环境变量"
onClick={(value) => {
handleParameterClick('env_variables', value);
}}
/>
}
>
<TextArea placeholder="请输入环境变量" allowClear />
</Form.Item>
{/* 控制参数 */}
{/* {controlStrategyList.map((item) => (

<div className={styles['pipeline-drawer__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div>

{/* 基本参数 */}
{basicParametersList.map((item) => (
<Form.Item
key={item.key}
name={['control_strategy', item.key]}
label={getLabel(item, 'task_info')}
required={item.value.require ? true : false}
>
{getFormComponent(item, 'task_info')}
</Form.Item>
))}

{/* 控制参数 */}
{controlStrategyList.map((item) => (
<Form.Item
key={item.key}
label={getLabel(item, 'control_strategy')}
rules={getFormRules(item)}
required={item.value.require ? true : false}
>
<ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput>
{getFormComponent(item, 'control_strategy')}
</Form.Item>
))} */}
</>
)}
))}

{/* 输入参数 */}
{inParametersList.length > 0 && (
@@ -579,38 +699,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
label={getLabel(item, 'in_parameters')}
required={item.value.require ? true : false}
>
<div className={styles['pipeline-drawer__ref-row']}>
<Form.Item name={['in_parameters', item.key]} rules={getFormRules(item)} noStyle>
{item.value.type === 'select' ? (
['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? (
<ParameterSelect
isPipeline
dataType={item.value.item_type as ParameterSelectDataType}
placeholder={item.value.placeholder}
/>
) : null
) : (
<ParameterInput
canInput={canInput(item.value)}
placeholder={item.value.placeholder}
allowClear
></ParameterInput>
)}
</Form.Item>
{item.value.type === 'ref' && (
<Form.Item noStyle>
<Button
size="small"
type="link"
icon={getSelectBtnIcon(item.value)}
onClick={() => selectRefData(['in_parameters', item.key], item.value)}
className={styles['pipeline-drawer__ref-row__select-button']}
>
{item.value.label}
</Button>
</Form.Item>
)}
</div>
{getFormComponent(item, 'in_parameters')}
</Form.Item>
))}
</>


+ 9
- 14
react-ui/src/types.ts View File

@@ -66,9 +66,10 @@ export type PipelineNodeModel = {
label: string;
experimentStartTime: string;
experimentEndTime?: string | null;
control_strategy: string;
in_parameters: string;
out_parameters: string;
control_strategy: Record<string, PipelineNodeModelParameter>;
in_parameters: Record<string, PipelineNodeModelParameter>;
task_info: Record<string, PipelineNodeModelParameter>;
out_parameters: Record<string, PipelineNodeModelParameter>;
component_label: string;
icon_path: string;
workflowId?: string;
@@ -82,11 +83,12 @@ export type PipelineNodeModelParameter = {
label: string;
value: any;
require?: number;
visible: boolean;
placeholder?: string;
describe?: string;
fromSelect?: boolean;
showValue?: any;
editable?: number;
editable?: boolean;
showValue?: any; // 前端显示用
fromSelect?: boolean; // 前端显示用
activeTab?: string; // ResourceSelectorModal tab
expandedKeys?: string[]; // ResourceSelectorModal expandedKeys
checkedKeys?: string[]; // ResourceSelectorModal checkedKeys
@@ -110,14 +112,7 @@ export type KeysToCamelCase<T> = {
};

// 序列化后的流水线节点
export type PipelineNodeModelSerialize = Omit<
PipelineNodeModel,
'in_parameters' | 'out_parameters'
> & {
// control_strategy: Record<string, PipelineNodeModelParameter>;
in_parameters: Record<string, PipelineNodeModelParameter>;
out_parameters: Record<string, PipelineNodeModelParameter>;
};
export type PipelineNodeModelSerialize = PipelineNodeModel;

// 资源规格
export type ComputingResource = {


Loading…
Cancel
Save