Browse Source

feat: 流水线选择代码配置

pull/126/head
cp3hnu 1 year ago
parent
commit
959450079d
9 changed files with 323 additions and 13 deletions
  1. +2
    -2
      react-ui/src/pages/CodeConfig/List/index.tsx
  2. +2
    -2
      react-ui/src/pages/CodeConfig/components/AddCodeConfigModal/index.tsx
  3. +15
    -2
      react-ui/src/pages/CodeConfig/components/CodeConfigItem/index.tsx
  4. +4
    -1
      react-ui/src/pages/Pipeline/Info/utils.tsx
  5. +68
    -0
      react-ui/src/pages/Pipeline/components/CodeConfigItem/index.less
  6. +39
    -0
      react-ui/src/pages/Pipeline/components/CodeConfigItem/index.tsx
  7. +29
    -0
      react-ui/src/pages/Pipeline/components/CodeSelectorModal/index.less
  8. +114
    -0
      react-ui/src/pages/Pipeline/components/CodeSelectorModal/index.tsx
  9. +50
    -6
      react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx

+ 2
- 2
react-ui/src/pages/CodeConfig/List/index.tsx View File

@@ -84,7 +84,7 @@ function CodeConfigList() {
};

// 修改
const handleClick = (record: CodeConfigData) => {
const handleEdit = (record: CodeConfigData) => {
const { close } = openAntdModal(AddCodeConfigModal, {
opType: OperationType.Update,
codeConfigData: record,
@@ -147,7 +147,7 @@ function CodeConfigList() {
item={item}
key={item.id}
onRemove={handleRemove}
onClick={handleClick}
onEdit={handleEdit}
/>
))}
</div>


+ 2
- 2
react-ui/src/pages/CodeConfig/components/AddCodeConfigModal/index.tsx View File

@@ -250,8 +250,8 @@ function AddCodeConfigModal({ opType, codeConfigData, onOk, ...rest }: AddCodeCo
<Input.TextArea
placeholder="请输入 SSH Key"
showCount
maxLength={1024}
autoSize={{ minRows: 3, maxRows: 6 }}
maxLength={4096}
autoSize={{ minRows: 4, maxRows: 8 }}
allowClear
/>
</Form.Item>


+ 15
- 2
react-ui/src/pages/CodeConfig/components/CodeConfigItem/index.tsx View File

@@ -10,10 +10,11 @@ import styles from './index.less';
type CodeConfigItemProps = {
item: CodeConfigData;
onClick?: (item: CodeConfigData) => void;
onEdit?: (item: CodeConfigData) => void;
onRemove?: (item: CodeConfigData) => void;
};

function CodeConfigItem({ item, onClick, onRemove }: CodeConfigItemProps) {
function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps) {
return (
<div className={styles['code-config-item']} onClick={() => onClick?.(item)}>
<Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}>
@@ -26,10 +27,22 @@ function CodeConfigItem({ item, onClick, onRemove }: CodeConfigItemProps) {
<div className={styles['code-config-item__tag']}>
{item.code_repo_vis === AvailableRange.Public ? '公开' : '私有'}
</div>

<Button
type="text"
shape="circle"
style={{ marginLeft: 'auto', marginRight: '4px' }}
onClick={(e) => {
e.stopPropagation();
onEdit?.(item);
}}
>
<KFIcon type="icon-bianji" font={17} />
</Button>
<Button
type="text"
shape="circle"
style={{ marginLeft: 'auto', right: 0 }}
style={{ marginRight: '-4px' }}
onClick={(e) => {
e.stopPropagation();
onRemove?.(item);


+ 4
- 1
react-ui/src/pages/Pipeline/Info/utils.tsx View File

@@ -86,6 +86,9 @@ export function canInput(parameter: PipelineNodeModelParameter) {
const { type, item_type } = parameter;
return !(
type === 'ref' &&
(item_type === 'dataset' || item_type === 'model' || item_type === 'image')
(item_type === 'dataset' ||
item_type === 'model' ||
item_type === 'image' ||
item_type === 'code')
);
}

+ 68
- 0
react-ui/src/pages/Pipeline/components/CodeConfigItem/index.less View File

@@ -0,0 +1,68 @@
.code-config-item {
position: relative;
padding: 20px;
background: white;
border: 1px solid #eaeaea;
border-radius: 4px;
cursor: pointer;

&__name {
position: relative;
display: inline-block;
height: 24px;
margin: 0 10px 0 0 !important;
color: @text-color;
font-size: 16px;
}

&__tag {
padding: 4px;
color: @primary-color;
font-size: 12px;
background-color: .addAlpha(@primary-color, 0.1) [];
border-radius: 4px;
}

&__url {
color: @text-color-secondary;
font-size: 14px;
}

&__description {
height: 44px;
margin-bottom: 20px;
color: @text-color-secondary;
font-size: 14px;
.multiLine(2);
}

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

&:hover {
border-color: @primary-color;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1);

.resource-item__name {
color: @primary-color;
}
}
}

.resource-item__name {
&::after {
position: absolute;
top: 14px;
left: 0;
width: 100%;
height: 6px;
background: linear-gradient(to right, rgba(22, 100, 255, 0.3) 0, rgba(22, 100, 255, 0) 100%);
content: '';
}
}

+ 39
- 0
react-ui/src/pages/Pipeline/components/CodeConfigItem/index.tsx View File

@@ -0,0 +1,39 @@
import { AvailableRange } from '@/enums';
import { type CodeConfigData } from '@/pages/CodeConfig/List';
import { Flex, Typography } from 'antd';
import styles from './index.less';

type CodeConfigItemProps = {
item: CodeConfigData;
onClick?: (item: CodeConfigData) => void;
};

function CodeConfigItem({ item, onClick }: CodeConfigItemProps) {
return (
<div className={styles['code-config-item']} onClick={() => onClick?.(item)}>
<Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}>
<Typography.Paragraph
className={styles['code-config-item__name']}
ellipsis={{ tooltip: item.code_repo_name }}
>
{item.code_repo_name}
</Typography.Paragraph>
<div className={styles['code-config-item__tag']}>
{item.code_repo_vis === AvailableRange.Public ? '公开' : '私有'}
</div>
</Flex>
<Typography.Paragraph
className={styles['code-config-item__url']}
ellipsis={{ tooltip: item.git_url }}
style={{ marginBottom: '8px' }}
>
{item.git_url}
</Typography.Paragraph>
<div className={styles['code-config-item__url']} style={{ marginBottom: '20px' }}>
{item.git_branch}
</div>
</div>
);
}

export default CodeConfigItem;

+ 29
- 0
react-ui/src/pages/Pipeline/components/CodeSelectorModal/index.less View File

@@ -0,0 +1,29 @@
.code-selector {
width: 100%;
height: 100%;

:global {
.ant-input-affix-wrapper .ant-input-prefix {
margin-inline-end: 12px;
}

.ant-pagination {
text-align: right;
}

.ant-input-group-addon {
display: none;
}
}

&__content {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 20px;
width: 100%;
margin-top: 30px;
margin-bottom: 30px;
overflow-x: hidden;
overflow-y: auto;
}
}

+ 114
- 0
react-ui/src/pages/Pipeline/components/CodeSelectorModal/index.tsx View File

@@ -0,0 +1,114 @@
/*
* @Author: 赵伟
* @Date: 2024-04-11 16:31:18
* @Description: 选择代码
*/

import KFModal from '@/components/KFModal';
import { type CodeConfigData } from '@/pages/CodeConfig/List';
import { getCodeConfigListReq } from '@/services/codeConfig';
import { to } from '@/utils/promise';
import type { ModalProps, PaginationProps } from 'antd';
import { Empty, Input, Pagination } from 'antd';
import { useEffect, useState } from 'react';
import CodeConfigItem from '../CodeConfigItem';
import styles from './index.less';

export interface CodeSelectorModalProps extends Omit<ModalProps, 'onOk'> {
onOk?: (params: CodeConfigData | undefined) => void;
}

function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
const [dataList, setDataList] = useState<CodeConfigData[]>([]);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<PaginationProps>({
current: 1,
pageSize: 20,
});
const [searchText, setSearchText] = useState<string | undefined>(undefined);
const [inputText, setInputText] = useState<string | undefined>(undefined);

useEffect(() => {
getDataList();
}, [pagination, searchText]);

// 获取数据请求
const getDataList = async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
code_repo_name: searchText !== '' ? searchText : undefined,
};
const [res] = await to(getCodeConfigListReq(params));
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
}
};

// 搜索
const handleSearch = (value: string) => {
setSearchText(value);
};

const handleClick = (item: CodeConfigData) => {
onOk?.(item);
};

// 分页切换
const handlePageChange: PaginationProps['onChange'] = (page, pageSize) => {
setPagination({
current: page,
pageSize: pageSize,
});
};

return (
<KFModal
{...rest}
title="选择代码配置"
image={require('@/assets/img/edit-experiment.png')}
width={920}
footer={null}
destroyOnClose
>
<div className={styles['code-selector']}>
<Input.Search
placeholder="按代码仓库名称筛选"
allowClear
onSearch={handleSearch}
style={{
width: '100%',
}}
onChange={(e) => setInputText(e.target.value)}
suffix={null}
value={inputText}
/>
{dataList?.length !== 0 ? (
<>
<div className={styles['code-selector__content']}>
{dataList?.map((item) => (
<CodeConfigItem item={item} key={item.id} onClick={handleClick} />
))}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={handlePageChange}
{...pagination}
/>
</>
) : (
<div className={styles['code-selector__empty']}>
<Empty></Empty>
</div>
)}
</div>
</KFModal>
);
}

export default CodeSelectorModal;

+ 50
- 6
react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx View File

@@ -17,6 +17,7 @@ import { INode } from '@antv/g6';
import { Button, Drawer, Form, Input, MenuProps, Select } from 'antd';
import { NamePath } from 'antd/es/form/interface';
import { forwardRef, useImperativeHandle, useState } from 'react';
import CodeSelectorModal from '../CodeSelectorModal';
import PropsLabel from '../PropsLabel';
import ResourceSelectorModal, {
ResourceSelectorType,
@@ -57,7 +58,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
formError: !!error,
};

// console.log('res', res);
console.log('res', res);
onFormChange(res);
}
};
@@ -79,7 +80,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
out_parameters: JSON.parse(model.out_parameters),
control_strategy: JSON.parse(model.control_strategy),
};
// console.log('model', nodeData);
console.log('model', nodeData);
setStagingItem({
...nodeData,
});
@@ -115,6 +116,48 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
},
}));

// ref 类型选择
const selectRefData = (
formItemName: NamePath,
item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>,
) => {
if (item.item_type === 'code') {
selectCodeConfig(formItemName, item);
} else {
selectResource(formItemName, item);
}
};

// 选择代码配置
const selectCodeConfig = (
formItemName: NamePath,
item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>,
) => {
const { close } = openAntdModal(CodeSelectorModal, {
onOk: (res) => {
if (res) {
console.log('res', res);
const value = JSON.stringify({
id: res.id,
name: res.code_repo_name,
code_path: res.git_url,
branch: res.git_branch,
username: res.git_user_name,
password: res.git_password,
ssh_private_key: res.ssh_key,
});
form.setFieldValue(formItemName, {
...item,
value,
showValue: res.code_repo_name,
fromSelect: true,
});
}
close();
},
});
};

// 选择数据集、模型、镜像
const selectResource = (
formItemName: NamePath,
@@ -146,8 +189,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
if (type === ResourceSelectorType.Mirror) {
const { activeTab, id, version, path } = res;
if (formItemName === 'image') {
// 单独的选择镜像
form.setFieldValue(formItemName, path);
} else {
// 输入参数选择镜像
form.setFieldValue(formItemName, {
...item,
value: path,
@@ -160,12 +205,11 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
}
} else {
const { activeTab, id, name, version, path } = res;
const jsonObj = {
const value = JSON.stringify({
id,
version,
path,
};
const value = JSON.stringify(jsonObj);
});
const showValue = `${name}:${version}`;
form.setFieldValue(formItemName, {
...item,
@@ -467,7 +511,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
size="small"
type="link"
icon={getSelectBtnIcon(item.value)}
onClick={() => selectResource(['in_parameters', item.key], item.value)}
onClick={() => selectRefData(['in_parameters', item.key], item.value)}
className={styles['pipeline-drawer__ref-row__select-button']}
>
{item.value.label}


Loading…
Cancel
Save