From a34f8296cc14e143eea86fb9250524d541b2e4f0 Mon Sep 17 00:00:00 2001 From: elonlo Date: Wed, 16 Sep 2020 18:04:49 +0800 Subject: [PATCH] [Improvement][ui] Refactored dag formatting logic (#3703) Co-authored-by: dashi --- dolphinscheduler-dist/release-docs/LICENSE | 1 + .../licenses/ui-licenses/LICENSE-dagre | 19 ++ dolphinscheduler-ui/package.json | 1 + .../src/js/conf/home/pages/dag/_source/dag.js | 212 ++---------------- 4 files changed, 45 insertions(+), 188 deletions(-) create mode 100644 dolphinscheduler-dist/release-docs/licenses/ui-licenses/LICENSE-dagre diff --git a/dolphinscheduler-dist/release-docs/LICENSE b/dolphinscheduler-dist/release-docs/LICENSE index 59da2746bf..707ea5cab1 100644 --- a/dolphinscheduler-dist/release-docs/LICENSE +++ b/dolphinscheduler-dist/release-docs/LICENSE @@ -502,6 +502,7 @@ MIT licenses vue-router 2.7.0: https://github.com/vuejs/vue-router MIT vuex 3.0.0: https://github.com/vuejs/vuex MIT vuex-router-sync 4.1.2: https://github.com/vuejs/vuex-router-sync MIT + dagre 0.8.5: https://github.com/dagrejs/dagre MIT ======================================== Apache 2.0 licenses diff --git a/dolphinscheduler-dist/release-docs/licenses/ui-licenses/LICENSE-dagre b/dolphinscheduler-dist/release-docs/licenses/ui-licenses/LICENSE-dagre new file mode 100644 index 0000000000..e3c8f95557 --- /dev/null +++ b/dolphinscheduler-dist/release-docs/licenses/ui-licenses/LICENSE-dagre @@ -0,0 +1,19 @@ +Copyright (c) 2012-2014 Chris Pettitt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/dolphinscheduler-ui/package.json b/dolphinscheduler-ui/package.json index a5642c8f97..9624fa6212 100644 --- a/dolphinscheduler-ui/package.json +++ b/dolphinscheduler-ui/package.json @@ -20,6 +20,7 @@ "clipboard": "^2.0.1", "codemirror": "^5.43.0", "d3": "^3.5.17", + "dagre": "^0.8.5", "dayjs": "^1.7.8", "echarts": "4.1.0", "html2canvas": "^0.5.0-beta4", diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.js b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.js index ff8a4528d5..d7f2f78a0c 100644 --- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.js +++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.js @@ -22,6 +22,7 @@ import { jsPlumb } from 'jsplumb' import JSP from './plugIn/jsPlumbHandle' import DownChart from './plugIn/downChart' import store from '@/conf/home/store' +import dagre from "dagre" /** * Prototype method @@ -115,202 +116,38 @@ Dag.prototype.toolbarEvent = function ({ item, code, is }) { */ Dag.prototype.backfill = function (arg) { if (arg) { - let locationsValue = store.state.dag.locations - const locationsValue1 = store.state.dag.locations - const locationsValue2 = store.state.dag.locations - const arr = [] - for (const i in locationsValue1) { - const objs = {} - objs.id = i - arr.push(Object.assign(objs, locationsValue1[i])) // Attributes - } - const tmp = [] - for (const i in locationsValue2) { - if (locationsValue2[i].targetarr !== '' && locationsValue2[i].targetarr.split(',').length > 1) { - tmp.push(locationsValue2[i]) - } - } - - const copy = function (array) { - const newArray = [] - for (const item of array) { - newArray.push(item) - } - return newArray - } - - const newArr = copy(arr) - const getNewArr = function () { - for (let i = 0; i < newArr.length; i++) { - if (newArr[i].targetarr !== '' && newArr[i].targetarr.split(',').length > 1) { - newArr[i].targetarr = newArr[i].targetarr.split(',').shift() - } - } - return newArr - } - getNewArr() - /** - * @description Transform flat data into a tree structure - * @param {Array} arr Flat data - * @param {String} pidStr targetarr key name - * @param {String} idStr id key name - * @param {String} childrenStr children key name - */ - const fommat = function ({ arrayList, pidStr = 'targetarr', idStr = 'id', childrenStr = 'children' }) { - const listOjb = {} // Used to store objects of the form {key: obj} - const treeList = [] // An array to store the final tree structure data - // Transform the data into {key: obj} format, which is convenient for the following data processing - for (let i = 0; i < arrayList.length; i++) { - listOjb[arrayList[i][idStr]] = arrayList[i] - } - // Format data based on pid - for (let j = 0; j < arrayList.length; j++) { - // Determine if the parent exists - // let haveParent = arrayList[j].targetarr.split(',').length>1?listOjb[arrayList[j].targetarr.split(',')[0]]:listOjb[arrayList[j][pidStr]] - const haveParent = listOjb[arrayList[j][pidStr]] - if (haveParent) { - // If there is no parent children field, create a children field - !haveParent[childrenStr] && (haveParent[childrenStr] = []) - // Insert child in parent - haveParent[childrenStr].push(arrayList[j]) - } else { - // If there is no parent, insert directly into the outermost layer - treeList.push(arrayList[j]) - } - } - return treeList - } - const datas = fommat({ arrayList: newArr, pidStr: 'targetarr' }) - // Count the number of leaf nodes - const getLeafCountTree = function (json) { - if (!json.children) { - json.colspan = 1 - return 1 - } else { - let leafCount = 0 - for (let i = 0; i < json.children.length; i++) { - leafCount = leafCount + getLeafCountTree(json.children[i]) - } - json.colspan = leafCount - return leafCount - } - } - // Number of tree node levels - const countTree = getLeafCountTree(datas[0]) - const getMaxFloor = function (treeData) { - let max = 0 - function each (data, floor) { - data.forEach(e => { - e.floor = floor - e.x = floor * 170 - if (floor > max) { - max = floor - } - if (e.children) { - each(e.children, floor + 1) - } - }) - } - each(treeData, 1) - return max - } - getMaxFloor(datas) - // The last child of each node - let lastchildren = [] - const forxh = function (list) { - for (let i = 0; i < list.length; i++) { - const chlist = list[i] - if (chlist.children) { - forxh(chlist.children) - } else { - lastchildren.push(chlist) - } - } - } - forxh(datas) - // Get all parent nodes above the leaf node - const treeFindPath = function (tree, func, path, n) { - if (!tree) return [] - for (const data of tree) { - path.push(data.name) - if (func(data)) return path - if (data.children) { - const findChildren = treeFindPath(data.children, func, path, n) - if (findChildren.length) return findChildren - } - path.pop() - } - return [] - } - const toLine = function (data) { - return data.reduce((arrData, { id, name, targetarr, x, y, children = [] }) => - arrData.concat([{ id, name, targetarr, x, y }], toLine(children)), []) - } - const listarr = toLine(datas) - const listarrs = toLine(datas) - const dataObject = {} - for (let i = 0; i < listarrs.length; i++) { - delete (listarrs[i].id) - } + const marginX = 100 + const g = new dagre.graphlib.Graph() + g.setGraph({}) + g.setDefaultEdgeLabel(function () { return {} }) - for (let a = 0; a < listarr.length; a++) { - dataObject[listarr[a].id] = listarrs[a] + for (const i in store.state.dag.locations) { + const location = store.state.dag.locations[i] + g.setNode(i, { label: i, width: Math.min(location.name.length * 7, 170), height: 150 }) } - // Comparison function - const createComparisonFunction = function (propertyName) { - return function (object1, object2) { - const value1 = object1[propertyName] - const value2 = object2[propertyName] - if (value1 < value2) { - return -1 - } else if (value1 > value2) { - return 1 - } else { - return 0 - } - } + for (const i in store.state.dag.connects) { + const connect = store.state.dag.connects[i] + g.setEdge(connect['endPointSourceId'], connect['endPointTargetId']) } + dagre.layout(g) - lastchildren = lastchildren.sort(createComparisonFunction('x')) - - // Coordinate value of each leaf node - for (let a = 0; a < lastchildren.length; a++) { - dataObject[lastchildren[a].id].y = (a + 1) * 120 - } - for (let i = 0; i < lastchildren.length; i++) { - const node = treeFindPath(datas, data => data.targetarr === lastchildren[i].targetarr, [], i + 1) - for (let j = 0; j < node.length; j++) { - for (let k = 0; k < listarrs.length; k++) { - if (node[j] === listarrs[k].name) { - listarrs[k].y = (i + 1) * 120 - } - } - } - } - for (let i = 0; i < tmp.length; i++) { - for (const objs in dataObject) { - if (tmp[i].name === dataObject[objs].name) { - dataObject[objs].targetarr = tmp[i].targetarr - } - } - } - for (let a = 0; a < lastchildren.length; a++) { - dataObject[lastchildren[a].id].y = (a + 1) * 120 - } - if (countTree > 1) { - dataObject[Object.keys(locationsValue1)[0]].y = (countTree / 2) * 120 + 50 - } - - locationsValue = dataObject - const self = this + const dataObject = {} + g.nodes().forEach(function (v) { + const node = g.node(v) + const obj = {} + obj.name = node.label + obj.x = node.x + marginX + obj.y = node.y + dataObject[node.label] = obj + }) jsPlumb.ready(() => { JSP.init({ dag: this.dag, instance: this.instance, options: { onRemoveNodes ($id) { - self.dag.removeEventModelById($id) + this.dag.removeEventModelById($id) } } }) @@ -319,20 +156,19 @@ Dag.prototype.backfill = function (arg) { // connects connects: _.cloneDeep(store.state.dag.connects), // Node location information - locations: _.cloneDeep(locationsValue), + locations: _.cloneDeep(dataObject), // Node data largeJson: _.cloneDeep(store.state.dag.tasks) }) }) } else { - const self = this jsPlumb.ready(() => { JSP.init({ dag: this.dag, instance: this.instance, options: { onRemoveNodes ($id) { - self.dag.removeEventModelById($id) + this.dag.removeEventModelById($id) } } })