diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.scss b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.scss index bcf6a36b4d..ea15323d40 100644 --- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.scss +++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.scss @@ -34,14 +34,35 @@ .minimap { position: absolute; - width: 300px; - height: 200px; - right: 10px; - bottom: 10px; + right: 0px; + bottom: 0px; border: dashed 1px #e4e4e4; z-index: 9; } + .scale-slider{ + position: absolute; + height: 140px; + width: 70px; + right: 0px; + bottom: 140px; + z-index: 9; + display: flex; + justify-content: center; + + ::v-deep .el-slider__runway{ + background-color: #fff; + } + + .scale-title{ + position: absolute; + top: -30px; + left: 22px; + font-size: 12px; + color: #666; + } + } + .context-menu{ position: absolute; left: 100px; diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.vue index 2e4bc44bf4..67c56a62df 100644 --- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.vue +++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.vue @@ -27,8 +27,21 @@ >
+
+ {{$t('dagScale')}} + +
+ @@ -37,23 +50,25 @@ import { Graph, DataUri } from '@antv/x6' import dagTaskbar from './taskbar.vue' import contextMenu from './contextMenu.vue' + import layoutConfigModal, { LAYOUT_TYPE, DEFAULT_LAYOUT_CONFIG } from './layoutConfigModal.vue' import { - NODE_PROPS, - EDGE_PROPS, - PORT_PROPS, + NODE, + EDGE, X6_NODE_NAME, - X6_PORT_OUT_NAME, - X6_PORT_IN_NAME, X6_EDGE_NAME, - NODE_HIGHLIGHT_PROPS, - PORT_HIGHLIGHT_PROPS, - EDGE_HIGHLIGHT_PROPS, NODE_STATUS_MARKUP } from './x6-helper' - import { DagreLayout } from '@antv/layout' + import { DagreLayout, GridLayout } from '@antv/layout' import { tasksType, tasksState } from '../config' import { mapActions, mapMutations, mapState } from 'vuex' import nodeStatus from './nodeStatus' + import x6StyleMixin from './x6-style-mixin' + + const SCALE_MARKS = { + 0.2: '0.2', + 1: '1', + 2: '2' + } export default { name: 'dag-canvas', @@ -71,7 +86,10 @@ x: 0, y: 0, type: '' - } + }, + // The canvas scale + scale: 1, + SCALE_MARKS } }, provide () { @@ -79,10 +97,12 @@ dagCanvas: this } }, + mixins: [x6StyleMixin], inject: ['dagChart'], components: { dagTaskbar, - contextMenu + contextMenu, + layoutConfigModal }, computed: { ...mapState('dag', ['tasks']) @@ -118,6 +138,14 @@ movable: true, showNodeSelectionBox: false }, + scaling: { + min: 0.2, + max: 2 + }, + mousewheel: { + enabled: true, + modifiers: ['ctrl', 'meta'] + }, scroller: true, grid: { size: 10, @@ -126,7 +154,10 @@ snapline: true, minimap: { enabled: true, - container: minimap + container: minimap, + scalable: false, + width: 200, + height: 120 }, interacting: { edgeLabelMovable: false, @@ -134,9 +165,6 @@ magnetConnectable: !!editable }, connecting: { - snap: { - radius: 30 - }, // Whether multiple edges can be created between the same start node and end allowMulti: false, // Whether a point is allowed to connect to a blank position on the canvas @@ -148,32 +176,14 @@ // Whether edges are allowed to link to nodes allowNode: true, // Whether to allow edge links to ports - allowPort: true, + allowPort: false, // Whether all available ports or nodes are highlighted when you drag the edge highlight: true, createEdge () { return graph.createEdge({ shape: X6_EDGE_NAME }) }, - validateMagnet ({ magnet }) { - return magnet.getAttribute('port-group') !== X6_PORT_IN_NAME - }, validateConnection (data) { - const { sourceCell, targetCell, sourceMagnet, targetMagnet } = data - // Connections can only be created from the output link post - if ( - !sourceMagnet || - sourceMagnet.getAttribute('port-group') !== X6_PORT_OUT_NAME - ) { - return false - } - - // Can only be connected to the input link post - if ( - !targetMagnet || - targetMagnet.getAttribute('port-group') !== X6_PORT_IN_NAME - ) { - return false - } + const { sourceCell, targetCell } = data if ( sourceCell && @@ -214,6 +224,7 @@ } } })) + this.registerX6Shape() this.bindGraphEvent() this.originalScrollPosition = graph.getScrollbarPosition() @@ -224,37 +235,17 @@ registerX6Shape () { Graph.unregisterNode(X6_NODE_NAME) Graph.unregisterEdge(X6_EDGE_NAME) - Graph.registerNode(X6_NODE_NAME, { ...NODE_PROPS }) - Graph.registerEdge(X6_EDGE_NAME, { ...EDGE_PROPS }) + Graph.registerNode(X6_NODE_NAME, { ...NODE }) + Graph.registerEdge(X6_EDGE_NAME, { ...EDGE }) }, /** * Bind grap event */ bindGraphEvent () { - // nodes and edges hover - this.graph.on('cell:mouseenter', (data) => { - const { cell, e } = data - const isStatusIcon = (tagName) => - tagName && - (tagName.toLocaleLowerCase() === 'em' || - tagName.toLocaleLowerCase() === 'body') - if (!isStatusIcon(e.target.tagName)) { - this.setHighlight(cell) - } - }) - this.graph.on('cell:mouseleave', ({ cell }) => { - if (!this.graph.isSelected(cell)) { - this.resetHighlight(cell) - } - }) - // select - this.graph.on('cell:selected', ({ cell }) => { - this.setHighlight(cell) - }) - this.graph.on('cell:unselected', ({ cell }) => { - if (!this.graph.isSelected(cell)) { - this.resetHighlight(cell) - } + this.bindStyleEvent(this.graph) + // update scale bar + this.graph.on('scale', ({ sx }) => { + this.scale = sx }) // right click this.graph.on('node:contextmenu', ({ x, y, cell }) => { @@ -279,6 +270,13 @@ label: labelName }) }) + // Make sure the edge starts with node, not port + this.graph.on('edge:connected', ({ isNew, edge }) => { + if (isNew) { + const sourceNode = edge.getSourceNode() + edge.setSource(sourceNode) + } + }) }, /** * @param {Edge|string} edge @@ -297,9 +295,6 @@ setEdgeLabel (id, label) { const edge = this.graph.getCellById(id) edge.setLabels(label) - if (this.graph.isSelected(edge)) { - this.setEdgeHighlight(edge) - } }, /** * @param {number} limit @@ -348,94 +343,6 @@ node.setData({ taskName: name }) } }, - /** - * Set node highlight - * @param {Node} node - */ - setNodeHighlight (node) { - const url = require(`../images/task-icos/${node.data.taskType.toLocaleLowerCase()}_hover.png`) - node.setAttrs(NODE_HIGHLIGHT_PROPS.attrs) - node.setAttrByPath('image/xlink:href', url) - node.setPortProp( - X6_PORT_OUT_NAME, - 'attrs', - PORT_HIGHLIGHT_PROPS[X6_PORT_OUT_NAME].attrs - ) - }, - /** - * Reset node style - * @param {Node} node - */ - resetNodeStyle (node) { - const url = require(`../images/task-icos/${node.data.taskType.toLocaleLowerCase()}.png`) - node.setAttrs(NODE_PROPS.attrs) - node.setAttrByPath('image/xlink:href', url) - node.setPortProp( - X6_PORT_OUT_NAME, - 'attrs', - PORT_PROPS.groups[X6_PORT_OUT_NAME].attrs - ) - }, - /** - * Set edge highlight - * @param {Edge} edge - */ - setEdgeHighlight (edge) { - const labelName = this.getEdgeLabelName(edge) - edge.setAttrs(EDGE_HIGHLIGHT_PROPS.attrs) - edge.setLabels([ - _.merge( - { - attrs: _.cloneDeep(EDGE_HIGHLIGHT_PROPS.defaultLabel.attrs) - }, - { - attrs: { label: { text: labelName } } - } - ) - ]) - }, - /** - * Reset edge style - * @param {Edge} edge - */ - resetEdgeStyle (edge) { - const labelName = this.getEdgeLabelName(edge) - edge.setAttrs(EDGE_PROPS.attrs) - edge.setLabels([ - { - ..._.merge( - { - attrs: _.cloneDeep(EDGE_PROPS.defaultLabel.attrs) - }, - { - attrs: { label: { text: labelName } } - } - ) - } - ]) - }, - /** - * Set cell highlight - * @param {Cell} cell - */ - setHighlight (cell) { - if (cell.isEdge()) { - this.setEdgeHighlight(cell) - } else if (cell.isNode()) { - this.setNodeHighlight(cell) - } - }, - /** - * Reset cell highlight - * @param {Cell} cell - */ - resetHighlight (cell) { - if (cell.isEdge()) { - this.resetEdgeStyle(cell) - } else if (cell.isNode()) { - this.resetNodeStyle(cell) - } - }, /** * Convert the graph to JSON * @return {{cells:Cell[]}} @@ -512,38 +419,70 @@ } ) }, + showLayoutModal () { + const layoutModal = this.$refs.layoutModal + if (layoutModal) { + layoutModal.show() + } + }, /** * format * @desc Auto layout use @antv/layout */ - format () { - const dagreLayout = new DagreLayout({ - type: 'dagre', - rankdir: 'LR', - align: 'UL', - // Calculate the node spacing based on the edge label length - ranksepFunc: (d) => { - const edges = this.graph.getOutgoingEdges(d.id) - let max = 0 - if (edges && edges.length > 0) { - edges.forEach((edge) => { - const edgeView = this.graph.findViewByCell(edge) - const labelWidth = +edgeView.findAttr( - 'width', - _.get(edgeView, ['labelSelectors', '0', 'body'], null) - ) - max = Math.max(max, labelWidth) - }) - } - return 50 + max - }, - nodesep: 50, - controlPoints: true - }) + format (layoutConfig) { + if (!layoutConfig) { + layoutConfig = DEFAULT_LAYOUT_CONFIG + } + this.graph.cleanSelection() + + let layoutFunc = null + if (layoutConfig.type === LAYOUT_TYPE.DAGRE) { + layoutFunc = new DagreLayout({ + type: LAYOUT_TYPE.DAGRE, + rankdir: 'LR', + align: 'UL', + // Calculate the node spacing based on the edge label length + ranksepFunc: (d) => { + const edges = this.graph.getOutgoingEdges(d.id) + let max = 0 + if (edges && edges.length > 0) { + edges.forEach((edge) => { + const edgeView = this.graph.findViewByCell(edge) + const labelWidth = +edgeView.findAttr( + 'width', + _.get(edgeView, ['labelSelectors', '0', 'body'], null) + ) + max = Math.max(max, labelWidth) + }) + } + return layoutConfig.ranksep + max + }, + nodesep: layoutConfig.nodesep, + controlPoints: true + }) + } else if (layoutConfig.type === LAYOUT_TYPE.GRID) { + layoutFunc = new GridLayout({ + type: LAYOUT_TYPE.GRID, + preventOverlap: true, + preventOverlapPadding: layoutConfig.padding, + sortBy: '_index', + rows: layoutConfig.rows || undefined, + cols: layoutConfig.cols || undefined, + nodeSize: 220 + }) + } const json = this.toJSON() - const nodes = json.cells.filter((cell) => cell.shape === X6_NODE_NAME) + const nodes = json.cells + .filter((cell) => cell.shape === X6_NODE_NAME) + .map((item) => { + return { + ...item, + // sort by code aesc + _index: -item.id + } + }) const edges = json.cells.filter((cell) => cell.shape === X6_EDGE_NAME) - const newModel = dagreLayout.layout({ + const newModel = layoutFunc.layout({ nodes: nodes, edges: edges }) @@ -606,12 +545,10 @@ return { shape: X6_EDGE_NAME, source: { - cell: sourceId, - port: X6_PORT_OUT_NAME + cell: sourceId }, target: { - cell: targetId, - port: X6_PORT_IN_NAME + cell: targetId }, labels: label ? [label] : undefined } @@ -688,7 +625,7 @@ if (node) { // Destroy the previous dom node.removeMarkup() - node.setMarkup(NODE_PROPS.markup.concat(NODE_STATUS_MARKUP)) + node.setMarkup(NODE.markup.concat(NODE_STATUS_MARKUP)) const nodeView = this.graph.findViewByCell(node) const el = nodeView.find('div')[0] nodeStatus({ @@ -828,6 +765,28 @@ const edge = this.genEdgeJSON(code, postCode) this.graph.addEdge(edge) }) + }, + /** + * Navigate to cell + * @param {string} taskName + */ + navigateTo (taskName) { + const nodes = this.getNodes() + nodes.forEach((node) => { + if (node.data.taskName === taskName) { + const id = node.id + const cell = this.graph.getCellById(id) + this.graph.scrollToCell(cell, { animation: { duration: 600 } }) + this.graph.cleanSelection() + this.graph.select(cell) + } + }) + }, + /** + * Canvas scale + */ + scaleChange (val) { + this.graph.zoomTo(val) } } } diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/layoutConfigModal.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/layoutConfigModal.vue new file mode 100644 index 0000000000..d13bd6877c --- /dev/null +++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/layoutConfigModal.vue @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.scss b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.scss index 9f1c6ee329..aec283f77f 100644 --- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.scss +++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.scss @@ -174,6 +174,10 @@ } } } + + &.disabled{ + cursor: default + } } } } diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.vue index 0ffc40c50d..7c5b66544d 100644 --- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.vue +++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.vue @@ -24,6 +24,9 @@
@@ -38,6 +41,7 @@ diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.scss b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.scss index 03578f32d9..155083c0fc 100644 --- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.scss +++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.scss @@ -110,4 +110,18 @@ } } } + + .process-online-tag{ + margin-left: 10px; + } + + .search-box{ + width: 0; + overflow: hidden; + transition: all 0.5s; + + &.visible{ + width: 200px; + } + } } diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.vue index 4071fd955b..d8f57ad959 100644 --- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.vue +++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.vue @@ -23,76 +23,111 @@ :content="$t('Copy name')" placement="bottom" > - +
+ {{ $t("processOnline") }} - + > - + >
+ + + +
+ +
- + - + - + - + - + {{$t('Version Info')}}{{ $t("Version Info") }} {{ $t("Close") }} @@ -143,15 +177,28 @@ inject: ['dagChart'], data () { return { - canvasRef: null + canvasRef: null, + searchText: '', + searchInputVisible: false } }, computed: { - ...mapState('dag', [ - 'isDetails' - ]) + ...mapState('dag', ['isDetails', 'releaseState']) }, methods: { + onSearch () { + const canvas = this.getDagCanvasRef() + canvas.navigateTo(this.searchText) + }, + showSearchInput () { + this.searchInputVisible = true + this.$refs.searchInput.focus() + }, + searchInputBlur () { + if (!this.searchText) { + this.searchInputVisible = false + } + }, getDagCanvasRef () { if (this.canvasRef) { return this.canvasRef @@ -200,7 +247,7 @@ }, chartFormat () { const canvas = this.getDagCanvasRef() - canvas.format() + canvas.showLayoutModal() }, refreshTaskStatus () { this.dagChart.refreshTaskStatus() diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-helper.js b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-helper.js index 96a48ebd7f..1be067f86b 100644 --- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-helper.js +++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-helper.js @@ -17,16 +17,17 @@ export const X6_NODE_NAME = 'dag-task' export const X6_EDGE_NAME = 'dag-edge' export const X6_PORT_OUT_NAME = 'dag-port-out' -export const X6_PORT_IN_NAME = 'dag-port-in' -const EDGE = '#999999' -const BG_BLUE = 'rgba(40, 143, 255, 0.1)' +const EDGE_COLOR = '#999999' +const BG_BLUE = '#DFE9F7' const BG_WHITE = '#FFFFFF' -const NODE_BORDER = '#e4e4e4' -const TITLE = '#333' +const NODE_BORDER = '#CCCCCC' +const TITLE = '#333333' const STROKE_BLUE = '#288FFF' +const NODE_SHADOW = 'drop-shadow(3px 3px 4px rgba(0, 0, 0, 0.2))' +const EDGE_SHADOW = 'drop-shadow(3px 3px 2px rgba(0, 0, 0, 0.2))' -export const PORT_PROPS = { +export const PORT = { groups: { [X6_PORT_OUT_NAME]: { position: { @@ -62,14 +63,14 @@ export const PORT_PROPS = { }, 'plus-text': { fontSize: 12, - fill: EDGE, + fill: NODE_BORDER, text: '+', textAnchor: 'middle', x: 0, y: 3 }, 'circle-outer': { - stroke: EDGE, + stroke: NODE_BORDER, strokeWidth: 1, r: 6, fill: BG_WHITE @@ -79,57 +80,42 @@ export const PORT_PROPS = { fill: 'transparent' } } - }, - [X6_PORT_IN_NAME]: { - position: { - name: 'absolute', - args: { - x: 0, - y: 24 - } - }, - markup: [ - { - tagName: 'g', - selector: 'body', - className: 'in-port-body', - children: [{ - tagName: 'circle', - selector: 'circle', - className: 'circle' - }] - } - ], + } + } +} + +export const PORT_HOVER = { + groups: { + [X6_PORT_OUT_NAME]: { attrs: { - body: { - magnet: true + 'circle-outer': { + stroke: STROKE_BLUE, + fill: BG_BLUE, + r: 8 }, - circle: { - r: 4, - strokeWidth: 0, - fill: 'transparent' + 'circle-inner': { + fill: STROKE_BLUE, + r: 6 } } } } } -export const PORT_HIGHLIGHT_PROPS = { - [X6_PORT_OUT_NAME]: { - attrs: { - 'circle-outer': { - stroke: STROKE_BLUE, - fill: BG_BLUE - }, - 'plus-text': { - fill: STROKE_BLUE - }, - 'circle-inner': { - fill: STROKE_BLUE +export const PORT_SELECTED = { + groups: { + [X6_PORT_OUT_NAME]: { + attrs: { + 'plus-text': { + fill: STROKE_BLUE + }, + 'circle-outer': { + stroke: STROKE_BLUE, + fill: BG_WHITE + } } } - }, - [X6_PORT_IN_NAME]: {} + } } export const NODE_STATUS_MARKUP = [{ @@ -148,13 +134,14 @@ export const NODE_STATUS_MARKUP = [{ ] }] -export const NODE_PROPS = { +export const NODE = { width: 220, height: 48, markup: [ { tagName: 'rect', - selector: 'body' + selector: 'body', + className: 'dag-task-body' }, { tagName: 'image', @@ -174,7 +161,9 @@ export const NODE_PROPS = { pointerEvents: 'visiblePainted', fill: BG_WHITE, stroke: NODE_BORDER, - strokeWidth: 1 + strokeWidth: 1, + strokeDasharray: 'none', + filter: 'none' }, image: { width: 30, @@ -199,21 +188,17 @@ export const NODE_PROPS = { } }, ports: { - ...PORT_PROPS, + ...PORT, items: [ { id: X6_PORT_OUT_NAME, group: X6_PORT_OUT_NAME - }, - { - id: X6_PORT_IN_NAME, - group: X6_PORT_IN_NAME } ] } } -export const NODE_HIGHLIGHT_PROPS = { +export const NODE_HOVER = { attrs: { body: { fill: BG_BLUE, @@ -226,28 +211,42 @@ export const NODE_HIGHLIGHT_PROPS = { } } -export const EDGE_PROPS = { +export const NODE_SELECTED = { + attrs: { + body: { + filter: NODE_SHADOW, + fill: BG_WHITE, + stroke: STROKE_BLUE, + strokeDasharray: '5,2', + strokeWidth: '1.5' + }, + title: { + fill: STROKE_BLUE + } + } +} + +export const EDGE = { attrs: { line: { - stroke: EDGE, - strokeWidth: 0.8, + stroke: EDGE_COLOR, + strokeWidth: 1, targetMarker: { tagName: 'path', - fill: EDGE, + fill: EDGE_COLOR, strokeWidth: 0, d: 'M 6 -3 0 0 6 3 Z' - } + }, + filter: 'none' } }, connector: { name: 'rounded' }, router: { - name: 'er', + name: 'manhattan', args: { - offset: 20, - min: 20, - direction: 'L' + endDirections: ['top', 'bottom', 'left'] } }, defaultLabel: { @@ -263,7 +262,7 @@ export const EDGE_PROPS = { ], attrs: { label: { - fill: EDGE, + fill: EDGE_COLOR, fontSize: 14, textAnchor: 'middle', textVerticalAnchor: 'middle', @@ -272,7 +271,7 @@ export const EDGE_PROPS = { body: { ref: 'label', fill: BG_WHITE, - stroke: EDGE, + stroke: EDGE_COLOR, strokeWidth: 1, rx: 4, ry: 4, @@ -292,7 +291,7 @@ export const EDGE_PROPS = { } } -export const EDGE_HIGHLIGHT_PROPS = { +export const EDGE_HOVER = { attrs: { line: { stroke: STROKE_BLUE, @@ -313,3 +312,27 @@ export const EDGE_HIGHLIGHT_PROPS = { } } } + +export const EDGE_SELECTED = { + attrs: { + line: { + stroke: STROKE_BLUE, + targetMarker: { + fill: STROKE_BLUE + }, + strokeWidth: 2, + filter: EDGE_SHADOW + } + }, + defaultLabel: { + attrs: { + label: { + fill: STROKE_BLUE + }, + body: { + fill: BG_WHITE, + stroke: STROKE_BLUE + } + } + } +} diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style-mixin.js b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style-mixin.js new file mode 100644 index 0000000000..d5f9f7b3a5 --- /dev/null +++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style-mixin.js @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + NODE, + EDGE, + PORT, + NODE_HOVER, + PORT_HOVER, + EDGE_HOVER, + PORT_SELECTED, + NODE_SELECTED, + EDGE_SELECTED, + X6_PORT_OUT_NAME +} from './x6-helper' +import _ from 'lodash' + +export default { + data () { + return { + hoverCell: null + } + }, + methods: { + bindStyleEvent (graph) { + // nodes and edges hover + graph.on('cell:mouseenter', (data) => { + const { cell, e } = data + const isStatusIcon = (tagName) => + tagName && + (tagName.toLocaleLowerCase() === 'em' || + tagName.toLocaleLowerCase() === 'body') + if (!isStatusIcon(e.target.tagName)) { + this.hoverCell = cell + this.updateCellStyle(cell, graph) + } + }) + graph.on('cell:mouseleave', ({ cell }) => { + this.hoverCell = null + this.updateCellStyle(cell, graph) + }) + // select + graph.on('cell:selected', ({ cell }) => { + this.updateCellStyle(cell, graph) + }) + graph.on('cell:unselected', ({ cell }) => { + this.updateCellStyle(cell, graph) + }) + }, + updateCellStyle (cell, graph) { + if (cell.isEdge()) { + this.setEdgeStyle(cell, graph) + } else if (cell.isNode()) { + this.setNodeStyle(cell, graph) + } + }, + /** + * Set node style + * @param {Node} node + * @param {Graph} graph + */ + setNodeStyle (node, graph) { + const isHover = node === this.hoverCell + const isSelected = graph.isSelected(node) + const portHover = _.cloneDeep(PORT_HOVER.groups[X6_PORT_OUT_NAME].attrs) + const portSelected = _.cloneDeep(PORT_SELECTED.groups[X6_PORT_OUT_NAME].attrs) + const portDefault = _.cloneDeep(PORT.groups[X6_PORT_OUT_NAME].attrs) + const nodeHover = _.merge(_.cloneDeep(NODE.attrs), NODE_HOVER.attrs) + const nodeSelected = _.merge(_.cloneDeep(NODE.attrs), NODE_SELECTED.attrs) + + let img = null + let nodeAttrs = null + let portAttrs = null + + if (isHover || isSelected) { + img = require(`../images/task-icos/${node.data.taskType.toLocaleLowerCase()}_hover.png`) + if (isHover) { + nodeAttrs = nodeHover + portAttrs = _.merge(portDefault, portHover) + } else { + nodeAttrs = nodeSelected + portAttrs = _.merge(portDefault, portSelected) + } + } else { + img = require(`../images/task-icos/${node.data.taskType.toLocaleLowerCase()}.png`) + nodeAttrs = NODE.attrs + portAttrs = portDefault + } + node.setAttrByPath('image/xlink:href', img) + node.setAttrs(nodeAttrs) + node.setPortProp( + X6_PORT_OUT_NAME, + 'attrs', + portAttrs + ) + }, + /** + * Set edge style + * @param {Edge} edge + * @param {Graph} graph + */ + setEdgeStyle (edge, graph) { + const isHover = edge === this.hoverCell + const isSelected = graph.isSelected(edge) + const labelName = this.getEdgeLabelName ? this.getEdgeLabelName(edge) : '' + let edgeProps = null + + if (isHover) { + edgeProps = _.merge(_.cloneDeep(EDGE), EDGE_HOVER) + } else if (isSelected) { + edgeProps = _.merge(_.cloneDeep(EDGE), EDGE_SELECTED) + } else { + edgeProps = _.cloneDeep(EDGE) + } + + edge.setAttrs(edgeProps.attrs) + edge.setLabels([ + { + ..._.merge( + { + attrs: _.cloneDeep(edgeProps.defaultLabel.attrs) + }, + { + attrs: { label: { text: labelName } } + } + ) + } + ]) + } + } +} diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style.scss b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style.scss index b86b51afc6..bc56d51b35 100644 --- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style.scss +++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style.scss @@ -14,16 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -$STROKE_BLUE: #288FFF; -$BG_WHITE: #FFFFFF; +$STROKE_BLUE: #288fff; +$BG_WHITE: #ffffff; -.x6-node[data-shape="dag-task"]{ - .in-port-body{ - &.adsorbed,&.available{ - .circle { - stroke: $STROKE_BLUE; - stroke-width: 4; - fill: $BG_WHITE; +.x6-node[data-shape="dag-task"] { + &.available { + .dag-task-body { + stroke: $STROKE_BLUE; + stroke-width: 1; + stroke-dasharray: 5, 2; + } + &.adsorbed { + .dag-task-body { + stroke-width: 3; } } } diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue index 77946e7f74..1489020eef 100644 --- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue +++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue @@ -60,6 +60,10 @@ + +
+ +
{ const location = locations.find((l) => l.taskCode === task.code) || {} const node = this.$refs.canvas.genNodeJSON( @@ -484,6 +488,10 @@ const connects = this.connects const json = this.buildGraphJSON(tasks, locations, connects) this.$refs.canvas.fromJSON(json) + // Auto format + if (!locations) { + this.$refs.canvas.format() + } }, /** * Return to the previous process diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue index fcafefc268..0552a4d111 100644 --- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue +++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue @@ -398,7 +398,6 @@
diff --git a/dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js b/dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js index a78df485ac..fda65d9fd7 100644 --- a/dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js +++ b/dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js @@ -18,6 +18,16 @@ import _ from 'lodash' import io from '@/module/io' +// Avoid passing in illegal values when users directly call third-party interfaces +const convertLocations = (locationStr) => { + let locations = null + if (!locationStr) return locations + try { + locations = JSON.parse(locationStr) + } catch (error) {} + return Array.isArray(locations) ? locations : null +} + export default { /** * Task status acquisition @@ -133,12 +143,14 @@ export default { state.version = res.data.processDefinition.version // name state.name = res.data.processDefinition.name + // releaseState + state.releaseState = res.data.processDefinition.releaseState // description state.description = res.data.processDefinition.description // taskRelationJson state.connects = res.data.processTaskRelationList // locations - state.locations = JSON.parse(res.data.processDefinition.locations) + state.locations = convertLocations(res.data.processDefinition.locations) // global params state.globalParams = res.data.processDefinition.globalParamList // timeout @@ -164,6 +176,7 @@ export default { 'timeout', 'environmentCode' ])) + resolve(res.data) }).catch(res => { reject(res) @@ -235,7 +248,7 @@ export default { // connects state.connects = processTaskRelationList // locations - state.locations = JSON.parse(processDefinition.locations) + state.locations = convertLocations(processDefinition.locations) // global params state.globalParams = processDefinition.globalParamList // timeout