From 1f5c6acb98359151c8aedcf389b0427a5c56a76a Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Wed, 12 Jun 2024 16:09:06 +0800 Subject: [PATCH 1/9] =?UTF-8?q?fix:=20=E6=A8=A1=E5=9E=8B=E6=BC=94=E5=8C=96?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=95=B0=E6=8D=AE=E9=9B=86=E7=9A=84=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/mock/model.ts | 338 ++++++++++++++++++ react-ui/package.json | 2 +- .../Model/components/ModelEvolution/utils.tsx | 89 +++-- 3 files changed, 379 insertions(+), 50 deletions(-) create mode 100644 react-ui/mock/model.ts diff --git a/react-ui/mock/model.ts b/react-ui/mock/model.ts new file mode 100644 index 00000000..02054802 --- /dev/null +++ b/react-ui/mock/model.ts @@ -0,0 +1,338 @@ +import { defineMock } from 'umi'; + +export default defineMock({ + 'POST /api/mmp/modelDependency/queryModelAtlas': { + code: 200, + msg: '操作成功', + data: { + current_model_id: 29, + exp_ins_id: 229, + version: 'v0.2.0', + ref_item: null, + train_task: { + name: '模型训练测试导出0529', + ins_id: 229, + task_id: 'model-train-5d76f002', + }, + train_dataset: [ + { + dataset_id: 20, + dataset_version: 'v0.1.0', + dataset_name: '手写体识别模型依赖测试训练数据集', + }, + ], + train_params: ['256', '2'], + train_image: '172.20.32.187/machine-learning/pytorch:pytorch_1.9.1_cuda11.1_detection_aim', + test_dataset: [ + { + dataset_id: 20, + dataset_version: 'v0.1.0', + dataset_name: '手写体识别模型依赖测试训练数据集', + }, + ], + project_dependency: { + url: 'https://openi.pcl.ac.cn/somunslotus/somun202304241505581.git', + name: 'somun202304241505581', + branch: 'train_ci_test', + }, + parent_models_map: [ + { + model_id: 29, + model_version: 'v0.1.0', + model_name: 'mnist模型演化', + }, + ], + parent_models: [ + { + current_model_id: 29, + exp_ins_id: null, + version: 'v0.1.0', + ref_item: null, + train_task: {}, + train_dataset: [], + train_params: [], + train_image: null, + test_dataset: [], + project_dependency: {}, + parent_models_map: [], + parent_models: [], + children_models: null, + workflow_id: null, + model_version_dependcy_vo: { + name: 'mnist模型演化', + description: '手写体识别模型演化', + available_range: 0, + model_type: '37', + model_tag: '46', + model_type_name: 'PyTorch', + model_tag_name: '图像转文本', + url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', + file_name: 'mnist_epoch1_0.00.pkl', + file_size: '176.63 KB', + create_by: 'admin', + create_time: '2024-06-12T06:09:56.000+00:00', + }, + }, + ], + children_models: [ + { + current_model_id: 29, + exp_ins_id: null, + version: 'v0.3.0', + ref_item: null, + train_task: {}, + train_dataset: [], + train_params: [], + train_image: null, + test_dataset: [], + project_dependency: {}, + parent_models_map: [], + parent_models: [], + children_models: [], + workflow_id: null, + model_version_dependcy_vo: { + name: 'mnist模型演化', + description: '手写体识别模型演化', + available_range: 0, + model_type: '37', + model_tag: '46', + model_type_name: 'PyTorch', + model_tag_name: '图像转文本', + url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', + file_name: 'mnist_epoch1_0.00.pkl', + file_size: '176.63 KB', + create_by: 'admin', + create_time: '2024-06-12T06:09:56.000+00:00', + }, + }, + { + current_model_id: 29, + exp_ins_id: null, + version: 'v0.31.0', + ref_item: null, + train_task: {}, + train_dataset: [], + train_params: [], + train_image: null, + test_dataset: [], + project_dependency: {}, + parent_models_map: [], + parent_models: [], + children_models: [], + workflow_id: null, + model_version_dependcy_vo: { + name: 'mnist模型演化', + description: '手写体识别模型演化', + available_range: 0, + model_type: '37', + model_tag: '46', + model_type_name: 'PyTorch', + model_tag_name: '图像转文本', + url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', + file_name: 'mnist_epoch1_0.00.pkl', + file_size: '176.63 KB', + create_by: 'admin', + create_time: '2024-06-12T06:09:56.000+00:00', + }, + }, + { + current_model_id: 29, + exp_ins_id: null, + version: 'v0.4.0', + ref_item: null, + train_task: {}, + train_dataset: [], + train_params: [], + train_image: null, + test_dataset: [], + project_dependency: {}, + parent_models_map: [], + parent_models: [], + children_models: [ + { + current_model_id: 29, + exp_ins_id: null, + version: 'v0.6.0', + ref_item: null, + train_task: {}, + train_dataset: [], + train_params: [], + train_image: null, + test_dataset: [], + project_dependency: {}, + parent_models_map: [], + parent_models: [], + children_models: [], + workflow_id: null, + model_version_dependcy_vo: { + name: 'mnist模型演化', + description: '手写体识别模型演化', + available_range: 0, + model_type: '37', + model_tag: '46', + model_type_name: 'PyTorch', + model_tag_name: '图像转文本', + url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', + file_name: 'mnist_epoch1_0.00.pkl', + file_size: '176.63 KB', + create_by: 'admin', + create_time: '2024-06-12T06:09:56.000+00:00', + }, + }, + { + current_model_id: 29, + exp_ins_id: null, + version: 'v0.7.0', + ref_item: null, + train_task: {}, + train_dataset: [], + train_params: [], + train_image: null, + test_dataset: [], + project_dependency: {}, + parent_models_map: [], + parent_models: [], + children_models: [], + workflow_id: null, + model_version_dependcy_vo: { + name: 'mnist模型演化', + description: '手写体识别模型演化', + available_range: 0, + model_type: '37', + model_tag: '46', + model_type_name: 'PyTorch', + model_tag_name: '图像转文本', + url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', + file_name: 'mnist_epoch1_0.00.pkl', + file_size: '176.63 KB', + create_by: 'admin', + create_time: '2024-06-12T06:09:56.000+00:00', + }, + }, + ], + workflow_id: null, + model_version_dependcy_vo: { + name: 'mnist模型演化', + description: '手写体识别模型演化', + available_range: 0, + model_type: '37', + model_tag: '46', + model_type_name: 'PyTorch', + model_tag_name: '图像转文本', + url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', + file_name: 'mnist_epoch1_0.00.pkl', + file_size: '176.63 KB', + create_by: 'admin', + create_time: '2024-06-12T06:09:56.000+00:00', + }, + }, + { + current_model_id: 29, + exp_ins_id: null, + version: 'v0.5.0', + ref_item: null, + train_task: {}, + train_dataset: [], + train_params: [], + train_image: null, + test_dataset: [], + project_dependency: {}, + parent_models_map: [], + parent_models: [], + children_models: [ + { + current_model_id: 29, + exp_ins_id: null, + version: 'v0.10.0', + ref_item: null, + train_task: {}, + train_dataset: [], + train_params: [], + train_image: null, + test_dataset: [], + project_dependency: {}, + parent_models_map: [], + parent_models: [], + children_models: [], + workflow_id: null, + model_version_dependcy_vo: { + name: 'mnist模型演化', + description: '手写体识别模型演化', + available_range: 0, + model_type: '37', + model_tag: '46', + model_type_name: 'PyTorch', + model_tag_name: '图像转文本', + url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', + file_name: 'mnist_epoch1_0.00.pkl', + file_size: '176.63 KB', + create_by: 'admin', + create_time: '2024-06-12T06:09:56.000+00:00', + }, + }, + { + current_model_id: 29, + exp_ins_id: null, + version: 'v0.11.0', + ref_item: null, + train_task: {}, + train_dataset: [], + train_params: [], + train_image: null, + test_dataset: [], + project_dependency: {}, + parent_models_map: [], + parent_models: [], + children_models: [], + workflow_id: null, + model_version_dependcy_vo: { + name: 'mnist模型演化', + description: '手写体识别模型演化', + available_range: 0, + model_type: '37', + model_tag: '46', + model_type_name: 'PyTorch', + model_tag_name: '图像转文本', + url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', + file_name: 'mnist_epoch1_0.00.pkl', + file_size: '176.63 KB', + create_by: 'admin', + create_time: '2024-06-12T06:09:56.000+00:00', + }, + }, + ], + workflow_id: null, + model_version_dependcy_vo: { + name: 'mnist模型演化', + description: '手写体识别模型演化', + available_range: 0, + model_type: '37', + model_tag: '46', + model_type_name: 'PyTorch', + model_tag_name: '图像转文本', + url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', + file_name: 'mnist_epoch1_0.00.pkl', + file_size: '176.63 KB', + create_by: 'admin', + create_time: '2024-06-12T06:09:56.000+00:00', + }, + }, + ], + workflow_id: 144, + model_version_dependcy_vo: { + name: 'mnist模型演化', + description: '手写体识别模型演化', + available_range: 0, + model_type: '37', + model_tag: '46', + model_type_name: 'PyTorch', + model_tag_name: '图像转文本', + url: 'models/admin/1718172760650/mnist_cnn.pt', + file_name: 'mnist_cnn.pt', + file_size: '176.76 KB', + create_by: 'admin', + create_time: '2024-06-12T06:12:42.000+00:00', + }, + }, + }, +}); diff --git a/react-ui/package.json b/react-ui/package.json index ac2bc997..cc1a3278 100644 --- a/react-ui/package.json +++ b/react-ui/package.json @@ -33,7 +33,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 max dev", - "start:no-mock": "cross-env MOCK=none UMI_ENV=dev max dev", + "start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev 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", "test": "jest", diff --git a/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx b/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx index 4570b5de..78fdb87a 100644 --- a/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx +++ b/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx @@ -9,8 +9,8 @@ export const vGap = nodeHeight + 20; export const hGap = nodeHeight + 20; export const ellipseWidth = nodeWidth; -// 数据集节点矩形数组 -const datasetRects: Rect[] = []; +// 数据集节点 +const datasetNodes: NodeConfig[] = []; export enum NodeType { current = 'current', @@ -22,8 +22,8 @@ export enum NodeType { } export type Rect = { - x: number; - y: number; + x: number; // 矩形中心的 x 坐标 + y: number; // 矩形中心的 y 坐标 width: number; height: number; }; @@ -142,14 +142,6 @@ export function normalizeTreeData(apiData: ModelDepsAPIData): ModelDepsData { normalizedData.id = `$M_${normalizedData.current_model_id}_${normalizedData.version}`; normalizedData.label = getLabel(normalizedData); normalizedData.style = getStyle(NodeType.current); - // let first1 = { ...normalizedData.children[0] }; - // let first2 = { ...normalizedData.children[0] }; - // let first3 = { ...normalizedData.children[0] }; - // first1.current_model_id = 202020; - // first2.current_model_id = 202021; - // first3.current_model_id = 202022; - // normalizedData.children.push(first1, first2, first3); - normalizeChildren(normalizedData.children as ModelDepsData[]); // 将 parent_models 转换成树形结构 @@ -191,14 +183,12 @@ export function getGraphData(data: ModelDepsData): GraphData { const edges: EdgeConfig[] = []; Util.traverseTree(treeLayoutData, (node: NodeConfig, parent: NodeConfig) => { const data = node.data as ModelDepsData; - console.log('data', data); - // 当前模型显示数据集和项目 if (data.model_type === NodeType.current) { addDatasetDependency(data, node, nodes, edges); addProjectDependency(data, node, nodes, edges); } else if (data.model_type === NodeType.children) { - adjustChildrenPosition(node); + adjustDatasetPosition(node); } nodes.push({ ...data, @@ -224,17 +214,17 @@ const addDatasetDependency = ( ) => { const { train_dataset, test_dataset } = data; train_dataset.forEach((item) => { - item.id = `$DTrain_${item.dataset_id}`; + item.id = `$DTrain_${item.dataset_id}_${item.dataset_version}`; item.model_type = NodeType.trainDataset; item.style = getStyle(NodeType.trainDataset); }); test_dataset.forEach((item) => { - item.id = `$DTest_${item.dataset_id}`; + item.id = `$DTest_${item.dataset_id}_${item.dataset_version}`; item.model_type = NodeType.testDataset; item.style = getStyle(NodeType.testDataset); }); - datasetRects.length = 0; + datasetNodes.length = 0; const len = train_dataset.length + test_dataset.length; [...train_dataset, ...test_dataset].forEach((item, index) => { const node = { ...item }; @@ -246,9 +236,10 @@ const addDatasetDependency = ( fittingString(node.dataset_version, ellipseWidth - 12, 8); const half = len / 2 - 0.5; - node.x = currentNode.x! - (half - index) * (ellipseWidth + hGap); + node.x = currentNode.x! - (half - index) * (ellipseWidth + hGap / 2); node.y = currentNode.y! - nodeHeight - vGap; nodes.push(node); + datasetNodes.push(node); edges.push({ source: currentNode.id, target: node.id, @@ -256,12 +247,6 @@ const addDatasetDependency = ( targetAnchor: 3, type: 'cubic-vertical', }); - datasetRects.push({ - x: node.x - ellipseWidth / 2, - y: node.y - nodeHeight / 2, - width: ellipseWidth, - height: nodeHeight, - }); }); }; @@ -275,7 +260,7 @@ const addProjectDependency = ( const { project_dependency } = data; if (project_dependency?.url) { const node = { ...project_dependency }; - node.id = `$P_${node.url}`; + node.id = `$P_${node.url}_${node.branch}`; node.model_type = NodeType.project; node.type = 'rect'; node.label = fittingString(node.name, nodeWidth - 12, 8); @@ -296,42 +281,48 @@ const addProjectDependency = ( }; // 判断两个矩形是否相交 -function isRectanglesIntersect(rect1: Rect, rect2: Rect) { - return !( - rect1.x + rect1.width < rect2.x || - rect1.x > rect2.x + rect2.width || - rect1.y + rect1.height < rect2.y || - rect1.y > rect2.y + rect2.height - ); +function isRectanglesOverlap(rect1: Rect, rect2: Rect) { + const a2x = rect1.x + rect1.width / 2; + const a2y = rect1.y + rect1.height / 2; + const b1x = rect2.x - rect2.width / 2; + const b1y = rect2.y - rect2.height / 2; + return b1y <= a2y && b1x <= a2x; } // 判断子节点是否与数据集节点重叠 -function isChildrenIntersectDataset(rects: Rect[], childrenRect: Rect) { - for (const r of rects) { - if (isRectanglesIntersect(r, childrenRect)) { - return r; +function isChildrenOverlapDataset(nodes: NodeConfig[], childrenRect: Rect) { + for (const node of nodes) { + const rect = { x: node.x!, y: node.y!, width: nodeWidth, height: nodeHeight }; + if (isRectanglesOverlap(rect, childrenRect)) { + return childrenRect; } } return null; } -// 计算子节点位置 -function adjustChildrenPosition(node: NodeConfig) { +// 调整数据集位置 +function adjustDatasetPosition(node: NodeConfig) { const nodeRect = { - x: node.x! - nodeWidth / 2, - y: node.y! - nodeHeight / 2, + x: node.x!, + y: node.y!, width: nodeWidth, height: nodeHeight, }; - const overlapRect = isChildrenIntersectDataset(datasetRects, nodeRect); + const overlapRect = isChildrenOverlapDataset(datasetNodes, nodeRect); if (overlapRect) { - const offsetY = nodeRect.y - overlapRect.y; - const space = 10; //(vGap + Math.abs(offsetY) - nodeHeight) / 2; - if (offsetY >= 0) { - node.y = node.y! + (nodeHeight - offsetY + space); - } else { - node.y = node.y! - (nodeHeight - Math.abs(offsetY) + space); - } + console.log(node); + + const adjustRect = { + x: overlapRect.x - nodeWidth - hGap / 2, + y: overlapRect.y, + width: overlapRect.width, + height: overlapRect.height, + }; + const lastNode = datasetNodes[datasetNodes.length - 1] as NodeConfig; + const distance = lastNode.x! - adjustRect.x; + datasetNodes.forEach((item) => { + item.x = item.x! - distance; + }); } } From e7e381c17c886ca0bd1f2d4505cd15fe942a9957 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Thu, 13 Jun 2024 09:20:42 +0800 Subject: [PATCH 2/9] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=BD=91=E7=BB=9C?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E6=B2=A1=E6=9C=89=E6=8D=95=E8=8E=B7=E9=9D=9E?= =?UTF-8?q?200=E7=9A=84=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/requestConfig.ts | 48 +++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/react-ui/src/requestConfig.ts b/react-ui/src/requestConfig.ts index 8a976abc..93a30443 100644 --- a/react-ui/src/requestConfig.ts +++ b/react-ui/src/requestConfig.ts @@ -9,12 +9,16 @@ import { clearSessionToken, getAccessToken } from './access'; import { setRemoteMenu } from './services/session'; import { gotoLoginPage } from './utils/ui'; +// [antd: Notification] You are calling notice in render which will break in React 18 concurrent mode. Please trigger in effect instead. +const popupError = (error: string) => { + message.error(error); +}; + /** * Umi Max 网络请求配置 * @doc https://umijs.org/docs/max/request#配置 */ export const requestConfig: RequestConfig = { - errorConfig: {}, requestInterceptors: [ (url: string, options: AxiosRequestConfig) => { const headers = options.headers ?? {}; @@ -30,26 +34,32 @@ export const requestConfig: RequestConfig = { }, ], responseInterceptors: [ - (response: AxiosResponse) => { - const { status, data } = response || {}; - if (status >= 200 && status < 300) { - if (data && (data instanceof Blob || data.code === 200)) { - return response; - } else if (data && data.code === 401) { - clearSessionToken(); - setRemoteMenu(null); - gotoLoginPage(false); - message.error('请重新登录'); - return Promise.reject(response); + [ + (response: AxiosResponse) => { + const { status, data } = response || {}; + console.log(message, data); + if (status >= 200 && status < 300) { + if (data && (data instanceof Blob || data.code === 200)) { + return response; + } else if (data && data.code === 401) { + clearSessionToken(); + setRemoteMenu(null); + gotoLoginPage(false); + popupError('请重新登录'); + return Promise.reject(response); + } else { + popupError(data?.msg ?? '请求失败'); + return Promise.reject(response); + } } else { - console.log(message, data); - message.error(data?.msg ?? '请求失败'); + popupError('请求失败'); return Promise.reject(response); } - } else { - message.error('请求失败'); - return Promise.reject(response); - } - }, + }, + (error: Error) => { + popupError(error.message ?? '请求失败'); + return Promise.reject(error); + }, + ], ], }; From 3b84a36d4ccd11b83f1ac003ba3c6a812131c169 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Thu, 13 Jun 2024 09:28:49 +0800 Subject: [PATCH 3/9] =?UTF-8?q?fix:=20=E6=A8=A1=E5=9E=8B=E6=BC=94=E5=8C=96?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=BA=BF=E7=9A=84=E9=A2=9C=E8=89=B2=E3=80=81?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E8=BE=B9=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/components/ModelEvolution/index.tsx | 15 +++++++++++---- .../Model/components/ModelEvolution/utils.tsx | 18 ++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/react-ui/src/pages/Model/components/ModelEvolution/index.tsx b/react-ui/src/pages/Model/components/ModelEvolution/index.tsx index bd8286cf..1e0d48b6 100644 --- a/react-ui/src/pages/Model/components/ModelEvolution/index.tsx +++ b/react-ui/src/pages/Model/components/ModelEvolution/index.tsx @@ -12,7 +12,14 @@ import GraphLegend from '../GraphLegend'; import NodeTooltips from '../NodeTooltips'; import styles from './index.less'; import type { ModelDepsData, ProjectDependency, TrainDataset } from './utils'; -import { NodeType, getGraphData, nodeHeight, nodeWidth, normalizeTreeData } from './utils'; +import { + NodeType, + getGraphData, + nodeFontSize, + nodeHeight, + nodeWidth, + normalizeTreeData, +} from './utils'; type modeModelEvolutionProps = { resourceId: number; @@ -73,7 +80,7 @@ function ModelEvolution({ width: graphRef.current!.clientWidth, height: graphRef.current!.clientHeight, fitView: true, - fitViewPadding: [50, 100, 50, 100], + fitViewPadding: [100, 100, 100, 100], minZoom: 0.5, maxZoom: 5, defaultNode: { @@ -95,7 +102,7 @@ function ModelEvolution({ position: 'center', style: { fill: '#ffffff', - fontSize: 8, + fontSize: nodeFontSize, textAlign: 'center', cursor: 'pointer', }, @@ -107,7 +114,7 @@ function ModelEvolution({ autoRotate: true, }, style: { - stroke: '#a2c1ff', + stroke: '#DEE0E5', lineWidth: 1, }, }, diff --git a/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx b/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx index 78fdb87a..cc44f3db 100644 --- a/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx +++ b/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx @@ -6,8 +6,10 @@ import Hierarchy from '@antv/hierarchy'; export const nodeWidth = 110; export const nodeHeight = 50; export const vGap = nodeHeight + 20; -export const hGap = nodeHeight + 20; +export const hGap = nodeWidth; export const ellipseWidth = nodeWidth; +export const labelPadding = 30; +export const nodeFontSize = 8; // 数据集节点 const datasetNodes: NodeConfig[] = []; @@ -94,9 +96,13 @@ export function normalizeChildren(data: ModelDepsData[]) { // 获取 label export function getLabel(node: ModelDepsData | ModelDepsAPIData) { return ( - fittingString(`${node.model_version_dependcy_vo.name ?? ''}`, nodeWidth - 12, 8) + + fittingString( + `${node.model_version_dependcy_vo.name ?? ''}`, + nodeWidth - labelPadding, + nodeFontSize, + ) + '\n' + - fittingString(`${node.version}`, nodeWidth - 12, 8) + fittingString(`${node.version}`, nodeWidth - labelPadding, nodeFontSize) ); } @@ -231,9 +237,9 @@ const addDatasetDependency = ( node.type = 'ellipse'; node.size = [ellipseWidth, nodeHeight]; node.label = - fittingString(node.dataset_name, ellipseWidth - 12, 8) + + fittingString(node.dataset_name, ellipseWidth - labelPadding, nodeFontSize) + '\n' + - fittingString(node.dataset_version, ellipseWidth - 12, 8); + fittingString(node.dataset_version, ellipseWidth - labelPadding, nodeFontSize); const half = len / 2 - 0.5; node.x = currentNode.x! - (half - index) * (ellipseWidth + hGap / 2); @@ -263,7 +269,7 @@ const addProjectDependency = ( node.id = `$P_${node.url}_${node.branch}`; node.model_type = NodeType.project; node.type = 'rect'; - node.label = fittingString(node.name, nodeWidth - 12, 8); + node.label = fittingString(node.name, nodeWidth - labelPadding, nodeFontSize); node.style = getStyle(NodeType.project); node.style.radius = nodeHeight / 2; node.x = currentNode.x; From 00c74f7f41b40125b3d491ef4f61767951922039 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Thu, 13 Jun 2024 15:20:17 +0800 Subject: [PATCH 4/9] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=B5=81=E6=B0=B4?= =?UTF-8?q?=E7=BA=BF=E5=92=8C=E5=AE=9E=E9=AA=8C=E5=8E=86=E5=8F=B2=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=9C=89=E8=93=9D=E8=89=B2=E8=BE=B9=E6=A1=86=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/Experiment/training/index.jsx | 49 ++++--------------- .../Model/components/ModelEvolution/index.tsx | 12 +++-- .../Model/components/ModelEvolution/utils.tsx | 12 ++--- .../Model/components/NodeTooltips/index.tsx | 14 ++++-- .../src/pages/Pipeline/editPipeline/index.jsx | 36 ++++---------- 5 files changed, 42 insertions(+), 81 deletions(-) diff --git a/react-ui/src/pages/Experiment/training/index.jsx b/react-ui/src/pages/Experiment/training/index.jsx index 6cbabfe0..5b267b0e 100644 --- a/react-ui/src/pages/Experiment/training/index.jsx +++ b/react-ui/src/pages/Experiment/training/index.jsx @@ -23,24 +23,12 @@ function ExperimentText() { const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); const graphRef = useRef(); - // const onDragEnd = (val) => { - // console.log(val, 'eee'); - // const _x = val.x; - // const _y = val.y; - // const point = graph.getPointByClient(_x, _y); - // let model = {}; - // // 元模型 - // model = { - // ...val, - // x: point.x, - // y: point.y, - // id: val.component_name + '-' + s8(), - // isCluster: false, - // }; - // graph.addItem('node', model, true); - // }; const getGraphData = (data) => { if (graph) { + // 修改历史数据有蓝色边框的问题 + data.nodes.forEach((item) => { + item.style.stroke = '#fff'; + }); graph.data(data); graph.render(); } else { @@ -166,8 +154,12 @@ function ExperimentText() { width: graphRef.current.clientWidth || 500, height: graphRef.current.clientHeight || 760, animate: false, - groupByTypes: false, - enabledStack: true, + groupByTypes: true, + enabledStack: false, + fitView: true, + minZoom: 0.5, + maxZoom: 5, + fitViewPadding: 300, modes: { default: [ // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles @@ -181,15 +173,6 @@ function ExperimentText() { // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles 'drag-canvas', 'zoom-canvas', - // 'brush-select', - 'drag-combo', - ], - altSelect: [ - { - type: 'brush-select', - trigger: 'drag', - }, - 'drag-node', ], }, @@ -232,15 +215,8 @@ function ExperimentText() { stroke: '#a2a6b5', radius: 1, }, - nodeStateStyle: { - hover: { - opacity: 1, - stroke: '#8fe8ff', - }, - }, labelCfg: { autoRotate: true, - // refY: 10, style: { fontSize: 10, fill: '#FFF', @@ -257,11 +233,6 @@ function ExperimentText() { cursor: 'pointer', }, }, - // linkCenter: true, - fitView: true, - minZoom: 0.5, - maxZoom: 5, - fitViewPadding: 300, }); graph.on('node:click', (e) => { if (e.target.get('name') !== 'anchor-point' && e.item) { diff --git a/react-ui/src/pages/Model/components/ModelEvolution/index.tsx b/react-ui/src/pages/Model/components/ModelEvolution/index.tsx index 1e0d48b6..6a6e74e8 100644 --- a/react-ui/src/pages/Model/components/ModelEvolution/index.tsx +++ b/react-ui/src/pages/Model/components/ModelEvolution/index.tsx @@ -170,28 +170,32 @@ function ModelEvolution({ graph.on('node:click', (e: G6GraphEvent) => { const nodeItem = e.item; - const model = nodeItem.getModel(); + const model = nodeItem.getModel() as ModelDepsData | ProjectDependency | TrainDataset; const { model_type } = model; const { origin } = location; let url: string = ''; switch (model_type) { case NodeType.children: case NodeType.parent: { - const { current_model_id, version } = model as ModelDepsData; + const { current_model_id, version } = model; url = `${origin}/dataset/model/${current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${version}`; break; } case NodeType.project: { - const { url: projectUrl } = model as ProjectDependency; + const { url: projectUrl } = model; url = projectUrl; break; } case NodeType.trainDataset: case NodeType.testDataset: { - const { dataset_id, dataset_version } = model as TrainDataset; + const { dataset_id, dataset_version } = model; url = `${origin}/dataset/dataset/${dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${dataset_version}`; break; } + case NodeType.current: { + // TODO: 隐藏数据集和项目 + break; + } default: break; } diff --git a/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx b/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx index cc44f3db..3c3f4225 100644 --- a/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx +++ b/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx @@ -40,14 +40,14 @@ export interface TrainDataset extends NodeConfig { dataset_id: number; dataset_name: string; dataset_version: string; - model_type: NodeType; + model_type: NodeType.testDataset | NodeType.trainDataset; } export interface ProjectDependency extends NodeConfig { url: string; name: string; branch: string; - model_type: NodeType; + model_type: NodeType.project; } export type ModalDetail = { @@ -65,7 +65,7 @@ export interface ModelDepsAPIData { current_model_id: number; version: string; exp_ins_id: number; - model_type: NodeType; + model_type: NodeType.children | NodeType.current | NodeType.parent; current_model_name: string; project_dependency: ProjectDependency; test_dataset: TrainDataset[]; @@ -242,7 +242,7 @@ const addDatasetDependency = ( fittingString(node.dataset_version, ellipseWidth - labelPadding, nodeFontSize); const half = len / 2 - 0.5; - node.x = currentNode.x! - (half - index) * (ellipseWidth + hGap / 2); + node.x = currentNode.x! - (half - index) * (ellipseWidth + 20); node.y = currentNode.y! - nodeHeight - vGap; nodes.push(node); datasetNodes.push(node); @@ -317,15 +317,13 @@ function adjustDatasetPosition(node: NodeConfig) { }; const overlapRect = isChildrenOverlapDataset(datasetNodes, nodeRect); if (overlapRect) { - console.log(node); - const adjustRect = { x: overlapRect.x - nodeWidth - hGap / 2, y: overlapRect.y, width: overlapRect.width, height: overlapRect.height, }; - const lastNode = datasetNodes[datasetNodes.length - 1] as NodeConfig; + const lastNode = datasetNodes[datasetNodes.length - 1]; const distance = lastNode.x! - adjustRect.x; datasetNodes.forEach((item) => { item.x = item.x! - distance; diff --git a/react-ui/src/pages/Model/components/NodeTooltips/index.tsx b/react-ui/src/pages/Model/components/NodeTooltips/index.tsx index dc926114..a4b3f13b 100644 --- a/react-ui/src/pages/Model/components/NodeTooltips/index.tsx +++ b/react-ui/src/pages/Model/components/NodeTooltips/index.tsx @@ -118,14 +118,18 @@ function ProjectInfo({ data }: { data: ProjectDependency }) { function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsProps) { if (!data) return null; - let Component; + let Component = null; const { model_type } = data; if (model_type === NodeType.testDataset || model_type === NodeType.trainDataset) { - Component = ; + Component = ; } else if (model_type === NodeType.project) { - Component = ; - } else { - Component = ; + Component = ; + } else if ( + model_type === NodeType.children || + model_type === NodeType.parent || + model_type === NodeType.current + ) { + Component = ; } return (
{ id: val.component_name + '-' + s8(), isCluster: false, }; - console.log('model', model); + // console.log('model', model); graph.addItem('node', model, true); }; const formChange = (val) => { @@ -110,6 +110,10 @@ const EditPipeline = () => { }; const getGraphData = (data) => { if (graph) { + // 修改历史数据有蓝色边框的问题 + data.nodes.forEach((item) => { + item.style.stroke = '#fff'; + }); graph.data(data); graph.render(); } else { @@ -407,10 +411,14 @@ const EditPipeline = () => { width: graphRef.current.clientWidth || 500, height: graphRef.current.clientHeight || '100%', animate: false, - groupByTypes: false, + groupByTypes: true, fitView: true, plugins: [contextMenu], enabledStack: true, + fitView: true, + minZoom: 0.5, + maxZoom: 5, + fitViewPadding: 300, modes: { default: [ // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles @@ -450,22 +458,11 @@ const EditPipeline = () => { }, 'drag-canvas', 'zoom-canvas', - // 'brush-select', - 'drag-combo', - ], - altSelect: [ - { - type: 'brush-select', - trigger: 'drag', - }, - 'drag-node', ], }, - defaultNode: { type: 'rect-node', size: [110, 36], - labelCfg: { style: { fill: 'transparent', @@ -489,7 +486,6 @@ const EditPipeline = () => { }, defaultEdge: { //type: 'cubic-vertical', - style: { endArrow: { // 设置终点箭头 @@ -503,15 +499,8 @@ const EditPipeline = () => { stroke: '#CDD0DC', radius: 1, }, - nodeStateStyle: { - hover: { - opacity: 1, - stroke: '#8fe8ff', - }, - }, labelCfg: { autoRotate: true, - // refY: 10, style: { fontSize: 10, fill: '#FFF', @@ -528,11 +517,6 @@ const EditPipeline = () => { cursor: 'pointer', }, }, - // linkCenter: true, - fitView: true, - minZoom: 0.5, - maxZoom: 5, - fitViewPadding: 300, }); graph.on('node:click', (e) => { e.stopPropagation(); From cce6ac5bebe07dd5afc4c65e451e17126c8c67de Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Thu, 13 Jun 2024 16:15:47 +0800 Subject: [PATCH 5/9] =?UTF-8?q?fix:=20=E7=A6=81=E6=AD=A2=E6=B5=81=E6=B0=B4?= =?UTF-8?q?=E7=BA=BF=E5=8F=AF=E4=BB=A5=E5=A4=8D=E5=88=B6=E8=BE=B9=E7=9A=84?= =?UTF-8?q?=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/pages/Pipeline/editPipeline/index.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/react-ui/src/pages/Pipeline/editPipeline/index.jsx b/react-ui/src/pages/Pipeline/editPipeline/index.jsx index 38741f1c..11287676 100644 --- a/react-ui/src/pages/Pipeline/editPipeline/index.jsx +++ b/react-ui/src/pages/Pipeline/editPipeline/index.jsx @@ -267,6 +267,8 @@ const EditPipeline = () => { // const selectedNodes = this.selectedNodes; contextMenu = new G6.Menu({ getContent(evt) { + const type = evt.item.getType(); + const cloneDisplay = type === 'node' ? 'block' : 'none'; return `
    - -
  • 复制
  • +
  • 复制
  • 删除
`; }, From 603a91db9e64fa62557459b298ef6ee56c99c158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A5=BF=E5=A4=A7=E9=94=90?= <1070211640@qq.com> Date: Thu, 13 Jun 2024 17:22:51 +0800 Subject: [PATCH 6/9] =?UTF-8?q?feat:=E6=96=B0=E5=A2=9Ejupyter=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E7=8A=B6=E6=80=81=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/platform/service/JupyterService.java | 2 +- .../platform/service/impl/JupyterServiceImpl.java | 11 ++++------- .../managementPlatform/ModelDependencyDaoMapper.xml | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/JupyterService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/JupyterService.java index f1aeb2cf..09a7fc68 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/JupyterService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/JupyterService.java @@ -12,7 +12,7 @@ public interface JupyterService { void mlflow(); - String runJupyterService(Integer id); + String runJupyterService(Integer id) throws Exception; String stopJupyterService(Integer id) throws Exception; diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java index b3eb4bdf..c57b498a 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java @@ -76,13 +76,11 @@ public class JupyterServiceImpl implements JupyterService { } @Override - public String runJupyterService(Integer id) { + public String runJupyterService(Integer id) throws Exception { DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id); -// if(devEnvironment == null){ -// -// } - String envName = devEnvironment.getName(); - //TODO 设置环境变量 + if(devEnvironment == null){ + throw new Exception("开发环境配置不存在"); + } // 提取数据集,模型信息,得到数据集模型的path Map dataset = JacksonUtil.parseJSONStr2Map(devEnvironment.getDataset()); @@ -132,7 +130,6 @@ public class JupyterServiceImpl implements JupyterService { if(StringUtils.isEmpty(frameLogPathVo.getPath())){ return JupyterStatusVo; } - LoginUser loginUser = SecurityUtils.getLoginUser(); String podName = loginUser.getUsername().toLowerCase() + "-editor-pod"; diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ModelDependencyDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ModelDependencyDaoMapper.xml index 939df96e..1275528f 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ModelDependencyDaoMapper.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ModelDependencyDaoMapper.xml @@ -321,7 +321,7 @@ state = #{state}, - where id = #{id} and state = 1 + where id = #{id} From 966581c394106ce60a6f8b19ff29ece3e48f1ba5 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Fri, 14 Jun 2024 10:56:46 +0800 Subject: [PATCH 7/9] =?UTF-8?q?fix:=20=E6=B5=81=E6=B0=B4=E7=BA=BF=E6=B7=BB?= =?UTF-8?q?=E5=8A=A04=E4=B8=AA=E9=94=9A=E7=82=B9=20&=20=E5=8E=BB=E6=8E=89?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E8=AF=A6=E6=83=85=E7=9A=84=E5=9C=B0?= =?UTF-8?q?=E5=9D=80=E6=A0=8F=E7=9A=84name=20&&=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=96=B0=E5=BB=BA=E6=B5=81=E6=B0=B4=E7=BA=BF=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/pages/Experiment/index.jsx | 2 +- .../src/pages/Experiment/training/index.jsx | 7 +- .../src/pages/Pipeline/editPipeline/index.jsx | 118 +++++++++++++----- react-ui/src/pages/Pipeline/index.jsx | 14 +-- 4 files changed, 97 insertions(+), 44 deletions(-) diff --git a/react-ui/src/pages/Experiment/index.jsx b/react-ui/src/pages/Experiment/index.jsx index aa6349d8..69648b49 100644 --- a/react-ui/src/pages/Experiment/index.jsx +++ b/react-ui/src/pages/Experiment/index.jsx @@ -198,7 +198,7 @@ function Experiment() { }; const routeToEdit = (e, record) => { e.stopPropagation(); - navgite({ pathname: `/pipeline/template/${record.workflow_id}/${record.workflow_name}` }); + navgite({ pathname: `/pipeline/template/${record.workflow_id}` }); }; // 创建或者编辑实验接口请求 const handleAddExperiment = async (values) => { diff --git a/react-ui/src/pages/Experiment/training/index.jsx b/react-ui/src/pages/Experiment/training/index.jsx index 5b267b0e..991e9855 100644 --- a/react-ui/src/pages/Experiment/training/index.jsx +++ b/react-ui/src/pages/Experiment/training/index.jsx @@ -80,9 +80,11 @@ function ExperimentText() { getAnchorPoints(cfg) { return ( cfg.anchorPoints || [ - // 上下各3,左右各1 + // 四个,上下左右 [0.5, 0], [0.5, 1], + [0, 0.5], + [1, 0.5], ] ); }, @@ -108,6 +110,7 @@ function ExperimentText() { textAlign: 'left', textBaseline: 'middle', fill: '#000', + cursor: 'pointer', }, name: 'text-shape', draggable: true, @@ -200,7 +203,7 @@ function ExperimentText() { }, defaultEdge: { // type: 'quadratic', - type: 'cubic-vertical', + // type: 'cubic-vertical', style: { endArrow: { diff --git a/react-ui/src/pages/Pipeline/editPipeline/index.jsx b/react-ui/src/pages/Pipeline/editPipeline/index.jsx index 11287676..cbe8b9d9 100644 --- a/react-ui/src/pages/Pipeline/editPipeline/index.jsx +++ b/react-ui/src/pages/Pipeline/editPipeline/index.jsx @@ -27,7 +27,8 @@ const EditPipeline = () => { const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useVisible(false); const [globalParam, setGlobalParam, globalParamRef] = useStateRef([]); const { message } = App.useApp(); - let sourceAnchorIdx, targetAnchorIdx; + let sourceAnchorIdx, targetAnchorIdx, dropAnchorIdx; + let sourceNode; useEffect(() => { initMenu(); @@ -35,9 +36,8 @@ const EditPipeline = () => { }, []); const onDragEnd = (val) => { - const _x = val.x; - const _y = val.y; - const point = graph.getPointByClient(_x, _y); + const { x, y } = val; + const point = graph.getPointByClient(x, y); // 元模型 const model = { ...val, @@ -47,7 +47,7 @@ const EditPipeline = () => { isCluster: false, }; // console.log('model', model); - graph.addItem('node', model, true); + graph.addItem('node', model, false); }; const formChange = (val) => { if (graph) { @@ -122,7 +122,6 @@ const EditPipeline = () => { }, 500); } }; - const processParallelEdgesOnAnchorPoint = ( edges, offsetDiff = 15, @@ -234,6 +233,15 @@ const EditPipeline = () => { } return edges; }; + // 判断两个节点之间是否有边 + const hasEdge = (source, target) => { + const neighbors = source.getNeighbors(); + for (const node of neighbors) { + // 新建边的时候,获取的 neighbors 的数据有问题,不全是 INode 类型,可能没有 getID 方法 + if (node.getID?.() === target.getID?.()) return true; + } + return false; + }; const cloneElement = (item) => { console.log(item); let data = graph.save(); @@ -319,11 +327,11 @@ const EditPipeline = () => { getAnchorPoints(cfg) { return ( cfg.anchorPoints || [ - // 四个 + // 四个,上下左右 [0.5, 0], [0.5, 1], - // [0, 0.5], - // [1, 0.5], + [0, 0.5], + [1, 0.5], ] ); }, @@ -349,6 +357,7 @@ const EditPipeline = () => { textAlign: 'left', textBaseline: 'middle', fill: '#000', + cursor: 'pointer', }, name: 'text-shape', draggable: true, @@ -385,10 +394,9 @@ const EditPipeline = () => { // if (value || point.get('links') > 0) point.show(); // else point.hide(); // }); - const group = item.getContainer(); const shape = group.get('children')[0]; - const anchorPoints = group.findAll((ele) => ele.get('name') === 'anchor-point'); + const anchorPoints = group.findAll((item) => item.get('name') === 'anchor-point'); if (name === 'hover') { if (value) { shape.attr('stroke', themes['primaryColor']); @@ -401,6 +409,24 @@ const EditPipeline = () => { point.hide(); }); } + } else if (name === 'drag') { + if (sourceAnchorIdx === null || sourceAnchorIdx === undefined) { + return; + } + const anchorPoint = anchorPoints[sourceAnchorIdx]; + anchorPoint.attr('stroke', value ? themes['primaryColor'] : '#a4a4a5'); + } else if (name === 'drop') { + if (dropAnchorIdx === null || dropAnchorIdx === undefined) { + return; + } + const anchorPoint = anchorPoints[dropAnchorIdx]; + if (value) { + anchorPoint.attr('stroke', themes['primaryColor']); + } else { + anchorPoints.forEach((point) => { + anchorPoint.attr('stroke', '#a4a4a5'); + }); + } } }, }, @@ -415,7 +441,7 @@ const EditPipeline = () => { groupByTypes: true, fitView: true, plugins: [contextMenu], - enabledStack: true, + enabledStack: false, fitView: true, minZoom: 0.5, maxZoom: 5, @@ -429,10 +455,6 @@ const EditPipeline = () => { if (e.target.get('name') === 'anchor-point') return false; return true; }, - // shouldEnd: e => { - // console.log(e); - // return false; - // }, }, // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles { @@ -443,11 +465,17 @@ const EditPipeline = () => { if (e.target && e.target.get('name') !== 'anchor-point') return false; sourceAnchorIdx = e.target.get('anchorPointIdx'); e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle + sourceNode = e.item; return true; }, shouldEnd: (e) => { // avoid ending at other shapes on the node if (e.target && e.target.get('name') !== 'anchor-point') return false; + if (!sourceNode || !e.item) return false; + // 不允许连接自己 + if (sourceNode.getID() === e.item.getID()) return false; + // 两个节点不允许多条边 + if (hasEdge(sourceNode, e.item)) return false; if (e.target) { targetAnchorIdx = e.target.get('anchorPointIdx'); e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle @@ -486,7 +514,7 @@ const EditPipeline = () => { }, }, defaultEdge: { - //type: 'cubic-vertical', + // type: 'cubic-vertical', style: { endArrow: { // 设置终点箭头 @@ -522,31 +550,36 @@ const EditPipeline = () => { graph.on('node:click', (e) => { e.stopPropagation(); if (e.target.get('name') !== 'anchor-point' && e.item) { - // graph.setItemState(e.item, 'nodeClicked', true); + // 获取所有的上游节点 const parentNodes = findAllParentNodes(graph, e.item); // 如果没有打开过全局参数抽屉,获取不到全局参数 const globalParams = paramsDrawerRef.current.getFieldsValue().global_param || globalParamRef.current; + // 打开节点编辑抽屉 propsRef.current.showDrawer(e, globalParams, parentNodes); } }); graph.on('aftercreateedge', (e) => { + console.log('aftercreateedge', e); + // update the sourceAnchor and targetAnchor for the newly added edge graph.updateItem(e.edge, { sourceAnchor: sourceAnchorIdx, targetAnchor: targetAnchorIdx, + type: + targetAnchorIdx === 0 || targetAnchorIdx === 1 ? 'cubic-vertical' : 'cubic-horizontal', }); - // update the curveOffset for parallel edges - const edges = graph.save().edges; - processParallelEdgesOnAnchorPoint(edges); - graph.getEdges().forEach((edge, i) => { - graph.updateItem(edge, { - curveOffset: edges[i].curveOffset, - curvePosition: edges[i].curvePosition, - }); - }); + // const edges = graph.save().edges; + // processParallelEdgesOnAnchorPoint(edges); + // graph.getEdges().forEach((edge, i) => { + // graph.updateItem(edge, { + // curveOffset: edges[i].curveOffset, + // curvePosition: edges[i].curvePosition, + // }); + // }); }); + // 删除边时,修改 anchor-point 的 links 值 graph.on('afterremoveitem', (e) => { if (e.item && e.item.source && e.item.target) { const sourceNode = graph.findById(e.item.source); @@ -572,7 +605,7 @@ const EditPipeline = () => { } } }); - // after clicking on the first node, the edge is created, update the sourceAnchor + // after drag on the first node, the edge is created, update the sourceAnchor graph.on('afteradditem', (e) => { if (e.item && e.item.getType() === 'edge') { graph.updateItem(e.item, { @@ -586,17 +619,36 @@ const EditPipeline = () => { graph.on('node:mouseleave', (e) => { graph.setItemState(e.item, 'hover', false); }); - graph.on('node:dragenter', (e) => { + + graph.on('node:dragstart', (e) => { graph.setItemState(e.item, 'hover', true); + graph.setItemState(e.item, 'drag', true); }); - graph.on('node:dragleave', (e) => { + graph.on('node:drag', (e) => { + graph.setItemState(e.item, 'hover', true); + }); + graph.on('node:dragend', (e) => { graph.setItemState(e.item, 'hover', false); + graph.setItemState(e.item, 'drag', false); }); - graph.on('node:dragstart', (e) => { + graph.on('node:dragenter', (e) => { graph.setItemState(e.item, 'hover', true); + if (e.target.get('name') === 'anchor-point') { + dropAnchorIdx = e.target.get('anchorPointIdx'); + graph.setItemState(e.item, 'drop', true); + } else { + graph.setItemState(e.item, 'drop', false); + } }); - graph.on('node:drag', (e) => { - graph.setItemState(e.item, 'hover', true); + graph.on('node:dragleave', (e) => { + graph.setItemState(e.item, 'hover', false); + graph.setItemState(e.item, 'drop', false); + dropAnchorIdx = undefined; + }); + graph.on('node:drop', (e) => { + graph.setItemState(e.item, 'hover', false); + graph.setItemState(e.item, 'drop', false); + dropAnchorIdx = undefined; }); window.onresize = () => { if (!graph || graph.get('destroyed')) return; diff --git a/react-ui/src/pages/Pipeline/index.jsx b/react-ui/src/pages/Pipeline/index.jsx index cf5fb571..12dbaae8 100644 --- a/react-ui/src/pages/Pipeline/index.jsx +++ b/react-ui/src/pages/Pipeline/index.jsx @@ -43,32 +43,30 @@ const Pipeline = () => { }; const routeToEdit = (e, record) => { e.stopPropagation(); - navgite({ pathname: `/pipeline/template/${record.id}/${record.name}` }); + navgite({ pathname: `/pipeline/template/${record.id}` }); }; const showModal = () => { form.resetFields(); + setFormId(null); setDialogTitle('新建流水线'); setIsModalOpen(true); }; - const handleOk = () => { - console.log(1111); - setIsModalOpen(false); - }; const handleCancel = () => { setIsModalOpen(false); }; const onFinish = (values) => { if (formId) { editWorkflow({ ...values, id: formId }).then((ret) => { + setIsModalOpen(false); message.success('编辑成功'); getList(); - setIsModalOpen(false); }); } else { addWorkflow(values).then((ret) => { - console.log(ret); + setIsModalOpen(false); + message.success('新建成功'); if (ret.code === 200) { - navgite({ pathname: `/pipeline/template/${ret.data.id}/${ret.data.name}` }); + navgite({ pathname: `/pipeline/template/${ret.data.id}` }); } }); } From f9919d69b24330a5555977f173373fafe532ee35 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Fri, 14 Jun 2024 14:08:10 +0800 Subject: [PATCH 8/9] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=B5=81=E6=B0=B4?= =?UTF-8?q?=E7=BA=BF=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/config/routes.ts | 2 +- react-ui/src/locales/zh-CN/pages.ts | 6 +++--- react-ui/src/locales/zh-TW/pages.ts | 6 +++--- react-ui/src/pages/User/Login/index.tsx | 4 ++-- react-ui/src/requestConfig.ts | 5 ++++- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index 200bbd20..1351cd2c 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -77,7 +77,7 @@ export default [ }, { name: '流水线详情', - path: ':id/:name', + path: ':id', component: './Pipeline/editPipeline/index', }, ], diff --git a/react-ui/src/locales/zh-CN/pages.ts b/react-ui/src/locales/zh-CN/pages.ts index 5e4565d5..ed9afc5e 100644 --- a/react-ui/src/locales/zh-CN/pages.ts +++ b/react-ui/src/locales/zh-CN/pages.ts @@ -1,12 +1,12 @@ export default { 'pages.layouts.userLayout.title': 'Ant Design 是西湖区最具影响力的 Web 设计规范', 'pages.login.accountLogin.tab': '账户密码登录', - 'pages.login.accountLogin.errorMessage': '错误的用户名和密码(admin/admin123)', + 'pages.login.accountLogin.errorMessage': '错误的用户名和密码', 'pages.login.failure': '登录失败,请重试!', 'pages.login.success': '登录成功!', - 'pages.login.username.placeholder': '用户名: admin', + 'pages.login.username.placeholder': '用户名', 'pages.login.username.required': '用户名是必填项!', - 'pages.login.password.placeholder': '密码: admin123', + 'pages.login.password.placeholder': '密码', 'pages.login.password.required': '密码是必填项!', 'pages.login.phoneLogin.tab': '手机号登录', 'pages.login.phoneLogin.errorMessage': '验证码错误', diff --git a/react-ui/src/locales/zh-TW/pages.ts b/react-ui/src/locales/zh-TW/pages.ts index f9e265b0..b3e37ef5 100644 --- a/react-ui/src/locales/zh-TW/pages.ts +++ b/react-ui/src/locales/zh-TW/pages.ts @@ -1,12 +1,12 @@ export default { 'pages.layouts.userLayout.title': 'Ant Design 是西湖區最具影響力的 Web 設計規範', 'pages.login.accountLogin.tab': '賬戶密碼登錄', - 'pages.login.accountLogin.errorMessage': '錯誤的用戶名和密碼(admin/admin123)', + 'pages.login.accountLogin.errorMessage': '錯誤的用戶名和密碼', 'pages.login.failure': '登錄失敗,請重試!', 'pages.login.success': '登錄成功!', - 'pages.login.username.placeholder': '用戶名: admin', + 'pages.login.username.placeholder': '用戶名', 'pages.login.username.required': '用戶名是必填項!', - 'pages.login.password.placeholder': '密碼: admin123', + 'pages.login.password.placeholder': '密碼', 'pages.login.password.required': '密碼是必填項!', 'pages.login.phoneLogin.tab': '手機號登錄', 'pages.login.phoneLogin.errorMessage': '驗證碼錯誤', diff --git a/react-ui/src/pages/User/Login/index.tsx b/react-ui/src/pages/User/Login/index.tsx index f531cbad..220a2b17 100644 --- a/react-ui/src/pages/User/Login/index.tsx +++ b/react-ui/src/pages/User/Login/index.tsx @@ -344,7 +344,7 @@ const Login: React.FC = () => { }} placeholder={intl.formatMessage({ id: 'pages.login.password.placeholder', - defaultMessage: '密码: admin123', + defaultMessage: '请输入密码', })} rules={[ { @@ -374,7 +374,7 @@ const Login: React.FC = () => { required: true, message: ( ), diff --git a/react-ui/src/requestConfig.ts b/react-ui/src/requestConfig.ts index 93a30443..4934396c 100644 --- a/react-ui/src/requestConfig.ts +++ b/react-ui/src/requestConfig.ts @@ -11,7 +11,10 @@ import { gotoLoginPage } from './utils/ui'; // [antd: Notification] You are calling notice in render which will break in React 18 concurrent mode. Please trigger in effect instead. const popupError = (error: string) => { - message.error(error); + // 直接调用 message.error 有时候不弹出来 + setTimeout(() => { + message.error(error); + }, 100); }; /** From 2705d63ed3456bc95b63f1959564b21946069d88 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Fri, 14 Jun 2024 15:59:42 +0800 Subject: [PATCH 9/9] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E8=8A=82=E7=82=B9=E5=90=8E=E9=94=9A?= =?UTF-8?q?=E7=82=B9=E5=8F=91=E7=94=9F=E6=94=B9=E5=8F=98=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/Pipeline/editPipeline/index.jsx | 62 +++++++------------ 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/react-ui/src/pages/Pipeline/editPipeline/index.jsx b/react-ui/src/pages/Pipeline/editPipeline/index.jsx index cbe8b9d9..01bec32f 100644 --- a/react-ui/src/pages/Pipeline/editPipeline/index.jsx +++ b/react-ui/src/pages/Pipeline/editPipeline/index.jsx @@ -28,7 +28,7 @@ const EditPipeline = () => { const [globalParam, setGlobalParam, globalParamRef] = useStateRef([]); const { message } = App.useApp(); let sourceAnchorIdx, targetAnchorIdx, dropAnchorIdx; - let sourceNode; + let dragSourceNode; useEffect(() => { initMenu(); @@ -83,11 +83,11 @@ const EditPipeline = () => { return; } - const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); - if (propsError) { - message.error('基本信息必填项需配置'); - return; - } + // const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); + // if (propsError) { + // message.error('基本信息必填项需配置'); + // return; + // } propsRef.current.propClose(); setTimeout(() => { const data = graph.save(); @@ -266,10 +266,6 @@ const EditPipeline = () => { } }); }; - const handlerContextMenu = (e) => { - e.stopPropagation(); - // this.menuType = e.item._cfg.type; - }; // 上下文菜单 const initMenu = () => { // const selectedNodes = this.selectedNodes; @@ -410,22 +406,14 @@ const EditPipeline = () => { }); } } else if (name === 'drag') { - if (sourceAnchorIdx === null || sourceAnchorIdx === undefined) { - return; + if (sourceAnchorIdx !== null && sourceAnchorIdx !== undefined) { + const anchorPoint = anchorPoints[sourceAnchorIdx]; + anchorPoint.attr('stroke', value ? themes['primaryColor'] : '#a4a4a5'); } - const anchorPoint = anchorPoints[sourceAnchorIdx]; - anchorPoint.attr('stroke', value ? themes['primaryColor'] : '#a4a4a5'); } else if (name === 'drop') { - if (dropAnchorIdx === null || dropAnchorIdx === undefined) { - return; - } - const anchorPoint = anchorPoints[dropAnchorIdx]; - if (value) { - anchorPoint.attr('stroke', themes['primaryColor']); - } else { - anchorPoints.forEach((point) => { - anchorPoint.attr('stroke', '#a4a4a5'); - }); + if (dropAnchorIdx !== null && dropAnchorIdx !== undefined) { + const anchorPoint = anchorPoints[dropAnchorIdx]; + anchorPoint.attr('stroke', value ? themes['primaryColor'] : '#a4a4a5'); } } }, @@ -465,17 +453,17 @@ const EditPipeline = () => { if (e.target && e.target.get('name') !== 'anchor-point') return false; sourceAnchorIdx = e.target.get('anchorPointIdx'); e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle - sourceNode = e.item; + dragSourceNode = e.item; return true; }, shouldEnd: (e) => { // avoid ending at other shapes on the node if (e.target && e.target.get('name') !== 'anchor-point') return false; - if (!sourceNode || !e.item) return false; + if (!dragSourceNode || !e.item) return false; // 不允许连接自己 - if (sourceNode.getID() === e.item.getID()) return false; + if (dragSourceNode.getID() === e.item.getID()) return false; // 两个节点不允许多条边 - if (hasEdge(sourceNode, e.item)) return false; + if (hasEdge(dragSourceNode, e.item)) return false; if (e.target) { targetAnchorIdx = e.target.get('anchorPointIdx'); e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle @@ -560,8 +548,6 @@ const EditPipeline = () => { } }); graph.on('aftercreateedge', (e) => { - console.log('aftercreateedge', e); - // update the sourceAnchor and targetAnchor for the newly added edge graph.updateItem(e.edge, { sourceAnchor: sourceAnchorIdx, @@ -569,6 +555,7 @@ const EditPipeline = () => { type: targetAnchorIdx === 0 || targetAnchorIdx === 1 ? 'cubic-vertical' : 'cubic-horizontal', }); + // update the curveOffset for parallel edges // const edges = graph.save().edges; // processParallelEdgesOnAnchorPoint(edges); @@ -582,9 +569,9 @@ const EditPipeline = () => { // 删除边时,修改 anchor-point 的 links 值 graph.on('afterremoveitem', (e) => { if (e.item && e.item.source && e.item.target) { - const sourceNode = graph.findById(e.item.source); - const targetNode = graph.findById(e.item.target); - const { sourceAnchor, targetAnchor } = e.item; + const { source, target, sourceAnchor, targetAnchor } = e.item; + const sourceNode = graph.findById(source); + const targetNode = graph.findById(target); if (sourceNode && !isNaN(sourceAnchor)) { const sourceAnchorShape = sourceNode .getContainer() @@ -607,7 +594,8 @@ const EditPipeline = () => { }); // after drag on the first node, the edge is created, update the sourceAnchor graph.on('afteradditem', (e) => { - if (e.item && e.item.getType() === 'edge') { + const sourceAnchor = e.item.getModel().sourceAnchor; + if (e.item && e.item.getType() === 'edge' && !sourceAnchor) { graph.updateItem(e.item, { sourceAnchor: sourceAnchorIdx, }); @@ -619,19 +607,16 @@ const EditPipeline = () => { graph.on('node:mouseleave', (e) => { graph.setItemState(e.item, 'hover', false); }); - graph.on('node:dragstart', (e) => { graph.setItemState(e.item, 'hover', true); graph.setItemState(e.item, 'drag', true); }); - graph.on('node:drag', (e) => { - graph.setItemState(e.item, 'hover', true); - }); graph.on('node:dragend', (e) => { graph.setItemState(e.item, 'hover', false); graph.setItemState(e.item, 'drag', false); }); graph.on('node:dragenter', (e) => { + if (e.item?.getID() === dragSourceNode?.getID()) return; graph.setItemState(e.item, 'hover', true); if (e.target.get('name') === 'anchor-point') { dropAnchorIdx = e.target.get('anchorPointIdx'); @@ -641,6 +626,7 @@ const EditPipeline = () => { } }); graph.on('node:dragleave', (e) => { + if (e.item?.getID() === dragSourceNode?.getID()) return; graph.setItemState(e.item, 'hover', false); graph.setItemState(e.item, 'drop', false); dropAnchorIdx = undefined;