| @@ -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) => { | |||
| @@ -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 { | |||
| @@ -92,9 +80,11 @@ function ExperimentText() { | |||
| getAnchorPoints(cfg) { | |||
| return ( | |||
| cfg.anchorPoints || [ | |||
| // 上下各3,左右各1 | |||
| // 四个,上下左右 | |||
| [0.5, 0], | |||
| [0.5, 1], | |||
| [0, 0.5], | |||
| [1, 0.5], | |||
| ] | |||
| ); | |||
| }, | |||
| @@ -120,6 +110,7 @@ function ExperimentText() { | |||
| textAlign: 'left', | |||
| textBaseline: 'middle', | |||
| fill: '#000', | |||
| cursor: 'pointer', | |||
| }, | |||
| name: 'text-shape', | |||
| draggable: true, | |||
| @@ -166,8 +157,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 +176,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', | |||
| ], | |||
| }, | |||
| @@ -217,7 +203,7 @@ function ExperimentText() { | |||
| }, | |||
| defaultEdge: { | |||
| // type: 'quadratic', | |||
| type: 'cubic-vertical', | |||
| // type: 'cubic-vertical', | |||
| style: { | |||
| endArrow: { | |||
| @@ -232,15 +218,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 +236,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) { | |||
| @@ -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, | |||
| }, | |||
| }, | |||
| @@ -163,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; | |||
| } | |||
| @@ -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[] = []; | |||
| @@ -38,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 = { | |||
| @@ -63,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[]; | |||
| @@ -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,12 +237,12 @@ 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); | |||
| node.x = currentNode.x! - (half - index) * (ellipseWidth + 20); | |||
| node.y = currentNode.y! - nodeHeight - vGap; | |||
| nodes.push(node); | |||
| datasetNodes.push(node); | |||
| @@ -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; | |||
| @@ -311,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; | |||
| @@ -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 = <DatasetInfo data={data as TrainDataset} />; | |||
| Component = <DatasetInfo data={data} />; | |||
| } else if (model_type === NodeType.project) { | |||
| Component = <ProjectInfo data={data as ProjectDependency} />; | |||
| } else { | |||
| Component = <ModelInfo data={data as ModelDepsData} />; | |||
| Component = <ProjectInfo data={data} />; | |||
| } else if ( | |||
| model_type === NodeType.children || | |||
| model_type === NodeType.parent || | |||
| model_type === NodeType.current | |||
| ) { | |||
| Component = <ModelInfo data={data} />; | |||
| } | |||
| return ( | |||
| <div | |||
| @@ -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, | |||
| @@ -46,8 +46,8 @@ const EditPipeline = () => { | |||
| id: val.component_name + '-' + s8(), | |||
| isCluster: false, | |||
| }; | |||
| console.log('model', model); | |||
| graph.addItem('node', model, true); | |||
| // console.log('model', model); | |||
| graph.addItem('node', model, false); | |||
| }; | |||
| const formChange = (val) => { | |||
| if (graph) { | |||
| @@ -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 { | |||
| @@ -118,7 +122,6 @@ const EditPipeline = () => { | |||
| }, 500); | |||
| } | |||
| }; | |||
| const processParallelEdgesOnAnchorPoint = ( | |||
| edges, | |||
| offsetDiff = 15, | |||
| @@ -230,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(); | |||
| @@ -263,6 +275,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 ` | |||
| <ul style="position: absolute; | |||
| width: 100px; | |||
| @@ -276,8 +290,7 @@ const EditPipeline = () => { | |||
| font-size: 14px; | |||
| color: #333333; | |||
| overflow-y: auto;"> | |||
| <li style="padding: 10px 20px;cursor: pointer;" code="clone">复制</li> | |||
| <li style="padding: 10px 20px;cursor: pointer; display: ${cloneDisplay}" code="clone">复制</li> | |||
| <li style="padding: 10px 20px;cursor: pointer;" code="delete">删除</li> | |||
| </ul>`; | |||
| }, | |||
| @@ -314,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], | |||
| ] | |||
| ); | |||
| }, | |||
| @@ -344,6 +357,7 @@ const EditPipeline = () => { | |||
| textAlign: 'left', | |||
| textBaseline: 'middle', | |||
| fill: '#000', | |||
| cursor: 'pointer', | |||
| }, | |||
| name: 'text-shape', | |||
| draggable: true, | |||
| @@ -380,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']); | |||
| @@ -396,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'); | |||
| }); | |||
| } | |||
| } | |||
| }, | |||
| }, | |||
| @@ -407,10 +438,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, | |||
| 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 | |||
| @@ -420,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 | |||
| { | |||
| @@ -434,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 | |||
| @@ -450,22 +487,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', | |||
| @@ -488,8 +514,7 @@ const EditPipeline = () => { | |||
| }, | |||
| }, | |||
| defaultEdge: { | |||
| //type: 'cubic-vertical', | |||
| // type: 'cubic-vertical', | |||
| style: { | |||
| endArrow: { | |||
| // 设置终点箭头 | |||
| @@ -503,15 +528,8 @@ const EditPipeline = () => { | |||
| stroke: '#CDD0DC', | |||
| radius: 1, | |||
| }, | |||
| nodeStateStyle: { | |||
| hover: { | |||
| opacity: 1, | |||
| stroke: '#8fe8ff', | |||
| }, | |||
| }, | |||
| labelCfg: { | |||
| autoRotate: true, | |||
| // refY: 10, | |||
| style: { | |||
| fontSize: 10, | |||
| fill: '#FFF', | |||
| @@ -528,40 +546,40 @@ const EditPipeline = () => { | |||
| cursor: 'pointer', | |||
| }, | |||
| }, | |||
| // linkCenter: true, | |||
| fitView: true, | |||
| minZoom: 0.5, | |||
| maxZoom: 5, | |||
| fitViewPadding: 300, | |||
| }); | |||
| 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); | |||
| @@ -587,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, { | |||
| @@ -601,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; | |||
| @@ -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}` }); | |||
| } | |||
| }); | |||
| } | |||
| @@ -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); | |||
| }, | |||
| ], | |||
| ], | |||
| }; | |||
| @@ -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; | |||
| @@ -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<String, Object> 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"; | |||