Browse Source

[Feature-5498][JsonSplit-api] refactor of dolphin-scheduler-ui/dag (#6098)

* refactor of dolphin-scheduler-ui/dag

* copy task feature and code optimization

* fix review & ut bugs

* add newline at end of file

* add license header

Co-authored-by: chenxiwei <cxwbeta@139.com>
Co-authored-by: chenxiwei <chenxiwei_yewu@cmss.chinamobile.com>
2.0.7-release
Wangyizhi1 3 years ago committed by GitHub
parent
commit
d4f5012f42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      dolphinscheduler-ui/package.json
  2. 52
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.scss
  3. 739
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.vue
  4. 40
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/contextMenu.scss
  5. 155
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/contextMenu.vue
  6. 50
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/draggableBox.vue
  7. 114
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/edgeEditModel.vue
  8. 42
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/menuItem.vue
  9. 16
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/statusMenu.scss
  10. 70
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/statusMenu.vue
  11. 167
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.scss
  12. 64
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.vue
  13. 113
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.scss
  14. 199
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.vue
  15. 323
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-helper.js
  16. 30
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style.scss
  17. 195
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.js
  18. 557
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.scss
  19. 1081
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue
  20. 214
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
  21. 1
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/log.vue
  22. 26
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/sub_process.vue
  23. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/full-screen-close.png
  24. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/full-screen-close_hover.png
  25. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/full-screen-open.png
  26. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/full-screen-open_hover.png
  27. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/graph-format.png
  28. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/graph-format_hover.png
  29. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/startup-params.png
  30. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/startup-params_hover.png
  31. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/conditions.png
  32. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/conditions_hover.png
  33. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/datax.png
  34. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/datax_hover.png
  35. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/dependent.png
  36. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/dependent_hover.png
  37. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/flink.png
  38. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/flink_hover.png
  39. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/http.png
  40. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/http_hover.png
  41. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/mr.png
  42. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/mr_hover.png
  43. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/procedure.png
  44. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/procedure_hover.png
  45. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/python.png
  46. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/python_hover.png
  47. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/shell.png
  48. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/shell_hover.png
  49. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/spark.png
  50. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/spark_hover.png
  51. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/sql.png
  52. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/sql_hover.png
  53. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/sqoop.png
  54. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/sqoop_hover.png
  55. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/sub_process.png
  56. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/sub_process_hover.png
  57. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/waterdrop.png
  58. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/waterdrop_hover.png
  59. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/view-variables.png
  60. BIN
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/view-variables_hover.png
  61. 122
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/plugIn/downChart.js
  62. 814
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/plugIn/jsPlumbHandle.js
  63. 67
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/plugIn/multiDrag.js
  64. 171
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/plugIn/util.js
  65. 7
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/udp/_source/selectTenant.vue
  66. 16
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/udp/udp.vue
  67. 8
      dolphinscheduler-ui/src/js/conf/home/pages/dag/definitionDetails.vue
  68. 12
      dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/list.vue
  69. 4
      dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/start.vue
  70. 28
      dolphinscheduler-ui/src/js/conf/home/router/index.js
  71. 221
      dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js
  72. 54
      dolphinscheduler-ui/src/js/conf/home/store/dag/mutations.js
  73. 6
      dolphinscheduler-ui/src/js/conf/home/store/dag/state.js

2
dolphinscheduler-ui/package.json

@ -15,6 +15,8 @@
"build:release": "npm run clean && cross-env NODE_ENV=production PUBLIC_PATH=/dolphinscheduler/ui webpack --config ./build/webpack.config.release.js"
},
"dependencies": {
"@antv/layout": "^0.1.18",
"@antv/x6": "^1.25.5",
"@form-create/element-ui": "^1.0.18",
"@riophae/vue-treeselect": "^0.4.0",
"axios": "^0.16.2",

52
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.scss

@ -0,0 +1,52 @@
/*
* 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.
*/
.dag-canvas {
display: flex;
overflow: hidden;
height: calc(100% - 50px);
padding: 10px 0 0 0;
box-sizing: border-box;
.dag-container {
height: 100%;
flex: 1;
overflow: hidden;
position: relative;
.paper {
width: 100%;
height: 100%;
}
.minimap {
position: absolute;
width: 300px;
height: 200px;
right: 10px;
bottom: 10px;
border: dashed 1px #e4e4e4;
z-index: 9;
}
.context-menu{
position: absolute;
left: 100px;
top: 100px;
}
}
}

739
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.vue

@ -0,0 +1,739 @@
/*
* 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.
*/
<template>
<div class="dag-canvas">
<dag-taskbar @on-drag-start="_onDragStart" />
<div
class="dag-container"
ref="container"
@dragenter.prevent
@dragover.prevent
@dragleave.prevent
@drop.stop.prevent="_onDrop"
>
<div ref="paper" class="paper"></div>
<div ref="minimap" class="minimap"></div>
<context-menu ref="contextMenu" />
<status-menu ref="statusMenu" />
</div>
</div>
</template>
<script>
import { Graph, DataUri } from '@antv/x6'
import dagTaskbar from './taskbar.vue'
import contextMenu from './contextMenu.vue'
import statusMenu from './statusMenu.vue'
import {
NODE_PROPS,
EDGE_PROPS,
PORT_PROPS,
X6_NODE_NAME,
X6_PORT_OUT_NAME,
X6_PORT_IN_NAME,
X6_EDGE_NAME,
NODE_HIGHLIGHT_PROPS,
PORT_HIGHLIGHT_PROPS,
EDGE_HIGHLIGHT_PROPS
} from './x6-helper'
import { DagreLayout } from '@antv/layout'
import { tasksType } from '../config'
import _ from 'lodash'
import { mapActions, mapMutations } from 'vuex'
export default {
name: 'dag-canvas',
data () {
return {
graph: null,
// Used to calculate the context menu location
originalScrollPosition: {
left: 0,
top: 0
},
editable: true,
dragging: {
// Distance from the mouse to the top-left corner of the dragging element
x: 0,
y: 0,
type: ''
}
}
},
provide () {
return {
dagCanvas: this
}
},
inject: ['dagChart'],
components: {
dagTaskbar,
contextMenu,
statusMenu
},
methods: {
...mapActions('dag', ['genTaskCodeList']),
...mapMutations('dag', ['removeTask']),
/**
* Recalculate the paper width and height
*/
paperResize () {
const w = this.$el.offsetWidth
const h = this.$el.offsetHeight
this.graph.resize(w, h)
},
/**
* Init graph
* This will be called in the dag-chart mounted event
* @param {boolean} uneditable
*/
graphInit (editable) {
const self = this
this.editable = !!editable
const paper = this.$refs.paper
const minimap = this.$refs.minimap
const graph = (this.graph = new Graph({
container: paper,
selecting: {
enabled: true,
multiple: true,
rubberband: true,
rubberEdge: true,
movable: true,
showNodeSelectionBox: false
},
scroller: true,
grid: {
size: 10,
visible: true
},
snapline: true,
minimap: {
enabled: true,
container: minimap
},
interacting: {
edgeLabelMovable: false,
nodeMovable: !!editable,
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
allowBlank: false,
// The start node and the end node are the same node
allowLoop: false,
// Whether an edge is allowed to link to another edge
allowEdge: false,
// Whether edges are allowed to link to nodes
allowNode: true,
// Whether to allow edge links to ports
allowPort: true,
// 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
}
if (
sourceCell &&
targetCell &&
sourceCell.isNode() &&
targetCell.isNode()
) {
const edgeData = {
sourceId: Number(sourceCell.id),
targetId: Number(targetCell.id)
}
if (!self.dagChart.edgeIsValid(edgeData)) {
return false
}
}
return true
}
},
highlighting: {
nodeAvailable: {
name: 'className',
args: {
className: 'available'
}
},
magnetAvailable: {
name: 'className',
args: {
className: 'available'
}
},
magnetAdsorbed: {
name: 'className',
args: {
className: 'adsorbed'
}
}
}
}))
// TODO will be deleted
window._graph = graph
this.registerX6Shape()
this.bindGraphEvent()
this.originalScrollPosition = graph.getScrollbarPosition()
},
/**
* Register custom shapes
*/
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 })
},
/**
* Bind grap event
*/
bindGraphEvent () {
// nodes and edges hover
this.graph.on('cell:mouseenter', (data) => {
const { cell, e } = data
const { left: cL, top: cT } =
this.$refs.container.getBoundingClientRect()
const cX = e.clientX - cL
const cY = e.clientY - cT
const isStatusIcon = (tagName) =>
tagName && tagName.toLocaleLowerCase() === 'i'
if (isStatusIcon(e.target.tagName)) {
this.$refs.statusMenu.show(cX, cY)
this.$refs.statusMenu.setCurrentTask({
name: cell.data.taskName,
type: cell.data.taskType,
code: Number(cell.id)
})
} else {
this.setHighlight(cell)
}
})
this.graph.on('cell:mouseleave', ({ cell }) => {
if (!this.graph.isSelected(cell)) {
this.resetHighlight(cell)
}
this.$refs.statusMenu.hide()
})
// select
this.graph.on('cell:selected', ({ cell }) => {
this.setHighlight(cell)
})
this.graph.on('cell:unselected', ({ cell }) => {
if (!this.graph.isSelected(cell)) {
this.resetHighlight(cell)
}
})
// right click
this.graph.on('node:contextmenu', ({ x, y, cell }) => {
const { left, top } = this.graph.getScrollbarPosition()
const o = this.originalScrollPosition
this.$refs.contextMenu.show(x + (o.left - left), y + (o.top - top))
this.$refs.contextMenu.setCurrentTask({
name: cell.data.taskName,
type: cell.data.taskType,
code: Number(cell.id)
})
})
// node double click
this.graph.on('node:dblclick', ({ cell }) => {
this.dagChart.openFormModel(Number(cell.id), cell.data.taskType)
})
// create edge label
this.graph.on('edge:dblclick', ({ cell }) => {
const labelName = this.getEdgeLabelName(cell)
this.dagChart.$refs.edgeEditModel.show({
id: cell.id,
label: labelName
})
})
},
/**
* @param {Edge|string} edge
*/
getEdgeLabelName (edge) {
if (typeof edge === 'string') edge = this.graph.getCellById(edge)
const labels = edge.getLabels()
const labelName = _.get(labels, ['0', 'attrs', 'label', 'text'], '')
return labelName
},
/**
* Set edge label by id
* @param {string} id
* @param {string} label
*/
setEdgeLabel (id, label) {
const edge = this.graph.getCellById(id)
edge.setLabels(label)
if (this.graph.isSelected(edge)) {
this.setEdgeHighlight(edge)
}
},
/**
* @param {number} limit
* @param {string} text
* Each Chinese character is equal to two chars
*/
truncateText (text, n) {
const exp = /[\u4E00-\u9FA5]/
let res = ''
let len = text.length
let chinese = text.match(new RegExp(exp, 'g'))
if (chinese) {
len += chinese.length
}
if (len > n) {
let i = 0
let acc = 0
while (true) {
let char = text[i]
if (exp.test(char)) {
acc += 2
} else {
acc++
}
if (acc > n) break
res += char
i++
}
res += '...'
} else {
res = text
}
return res
},
/**
* Set node name by id
* @param {string|number} id
* @param {string} name
*/
setNodeName (id, name) {
id += ''
const node = this.graph.getCellById(id)
if (node) {
const truncation = this.truncateText(name, 18)
node.attr('title/text', truncation)
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) {
edge.setAttrs(EDGE_HIGHLIGHT_PROPS.attrs)
const labelName = this.getEdgeLabelName(edge)
if (labelName) {
edge.setLabels([
_.merge(EDGE_HIGHLIGHT_PROPS.defaultLabel, {
attrs: {
label: {
text: labelName
}
}
})
])
}
},
/**
* Reset edge style
* @param {Edge} edge
*/
resetEdgeStyle (edge) {
edge.setAttrs(EDGE_PROPS.attrs)
const labelName = this.getEdgeLabelName(edge)
if (labelName) {
edge.setLabels([
{
attrs: {
label: {
fill: EDGE_PROPS.defaultLabel.attrs.label.fill,
text: labelName
},
body: {
stroke: EDGE_PROPS.defaultLabel.attrs.body.stroke
}
}
}
])
}
},
/**
* 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[]}}
*/
toJSON () {
return this.graph.toJSON()
},
/**
* Generate graph with JSON
*/
fromJSON (json) {
this.graph.fromJSON(json)
},
/**
* getNodes
* @return {Node[]}
*/
// interface Node {
// id: number;
// position: {x:number;y:number};
// data: {taskType:string;taskName:string;}
// }
getNodes () {
const nodes = this.graph.getNodes()
return nodes.map((node) => {
const position = node.getPosition()
const data = node.getData()
return {
id: Number(node.id),
position: position,
data: data
}
})
},
/**
* getEdges
* @return {Edge[]} Edge is inherited from the Cell
*/
// interface Edge {
// label: string;
// sourceId: number;
// targetId: number;
// }
getEdges () {
const edges = this.graph.getEdges()
return edges.map((edge) => {
const labelData = edge.getLabelAt(0)
return {
label: _.get(labelData, ['attrs', 'label', 'text'], ''),
sourceId: Number(edge.getSourceCellId()),
targetId: Number(edge.getTargetCellId())
}
})
},
/**
* downloadPNG
* @param {string} filename
*/
downloadPNG (fileName = 'chart') {
this.graph.toPNG(
(dataUri) => {
DataUri.downloadDataUri(dataUri, `${fileName}.png`)
},
{
padding: {
top: 50,
right: 50,
bottom: 50,
left: 50
},
backgroundColor: '#f2f3f7'
}
)
},
/**
* 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
})
const json = this.toJSON()
const nodes = json.cells.filter((cell) => cell.shape === X6_NODE_NAME)
const edges = json.cells.filter((cell) => cell.shape === X6_EDGE_NAME)
const newModel = dagreLayout.layout({
nodes: nodes,
edges: edges
})
this.fromJSON(newModel)
},
/**
* add a node to the graph
* @param {string|number} id
* @param {string} taskType
* @param {{x:number;y:number}} coordinate Default is { x: 100, y: 100 }
*/
addNode (id, taskType, coordinate = { x: 100, y: 100 }) {
id += ''
if (!tasksType[taskType]) {
console.warn(`taskType:${taskType} is invalid!`)
return
}
const node = this.genNodeJSON(id, taskType, '', coordinate)
this.graph.addNode(node)
},
/**
* generate node json
* @param {number|string} id
* @param {string} taskType
* @param {{x:number;y:number}} coordinate Default is { x: 100, y: 100 }
*/
genNodeJSON (id, taskType, taskName, coordinate = { x: 100, y: 100 }) {
id += ''
const url = require(`../images/task-icos/${taskType.toLocaleLowerCase()}.png`)
const truncation = taskName ? this.truncateText(taskName, 18) : id
return {
id: id,
shape: X6_NODE_NAME,
x: coordinate.x,
y: coordinate.y,
data: {
taskType: taskType,
taskName: taskName
},
attrs: {
image: {
// Use href instead of xlink:href, you may lose the icon when downloadPNG
'xlink:href': url
},
title: {
text: truncation
}
}
}
},
/**
* generate edge json
* @param {number|string} sourceId
* @param {number|string} targetId
* @param {string} label
*/
genEdgeJSON (sourceId, targetId, label = '') {
sourceId += ''
targetId += ''
return {
shape: X6_EDGE_NAME,
source: {
cell: sourceId,
port: X6_PORT_OUT_NAME
},
target: {
cell: targetId,
port: X6_PORT_IN_NAME
},
labels: label ? [label] : undefined
}
},
/**
* remove a node
* @param {string|number} id NodeId
*/
removeNode (id) {
id += ''
this.graph.removeNode(id)
this.removeTask(id)
},
/**
* remove an edge
* @param {string|number} id EdgeId
*/
removeEdge (id) {
id += ''
this.graph.removeEdge(id)
},
/**
* remove multiple cells
* @param {Cell[]} cells
*/
removeCells (cells) {
this.graph.removeCells(cells)
cells.forEach((cell) => {
if (cell.isNode()) {
this.removeTask(cell.id)
}
})
},
/**
* Gets the current selections
* @return {Cell[]}
*/
getSelections () {
return this.graph.getSelectedCells()
},
/**
* Lock scroller
*/
lockScroller () {
this.graph.lockScroller()
},
/**
* Unlock scroller
*/
unlockScroller () {
this.graph.unlockScroller()
},
/**
* Drag && Drop Event
*/
_onDragStart (e, taskType) {
if (!this.editable) {
e.preventDefault()
return
}
this.dragging = {
x: e.offsetX,
y: e.offsetY,
type: taskType.name
}
},
calcGraphCoordinate (mClientX, mClientY) {
// Distance from the mouse to the top-left corner of the container;
const { left: cX, top: cY } =
this.$refs.container.getBoundingClientRect()
const mouseX = mClientX - cX
const mouseY = mClientY - cY
// The distance that paper has been scrolled
const { left: sLeft, top: sTop } = this.graph.getScrollbarPosition()
const { left: oLeft, top: oTop } = this.originalScrollPosition
const scrollX = sLeft - oLeft
const scrollY = sTop - oTop
// Distance from the mouse to the top-left corner of the dragging element;
const { x: eX, y: eY } = this.dragging
return {
x: mouseX + scrollX - eX,
y: mouseY + scrollY - eY
}
},
_onDrop (e) {
const { type } = this.dragging
const { x, y } = this.calcGraphCoordinate(e.clientX, e.clientY)
this.genTaskCodeList({
genNum: 1
})
.then((res) => {
const [code] = res
this.addNode(code, type, { x, y })
this.dagChart.openFormModel(code, type)
})
.catch((err) => {
console.error(err)
})
}
}
}
</script>
<style lang="scss" scoped>
@import "./canvas";
</style>
<style lang="scss">
@import "./x6-style";
</style>

40
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/plugIn/dragZoom.js → dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/contextMenu.scss

@ -1,4 +1,3 @@
import d3 from 'd3'
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
@ -15,24 +14,29 @@ import d3 from 'd3'
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.dag-context-menu{
position: absolute;
left: 0;
top: 0;
width: 100px;
background-color: #ffffff;
box-shadow: 0 2px 10px rgba(0,0,0,0.12);
const DragZoom = function () {
this.element = {}
this.zoom = {}
this.scale = 1
}
.menu-item{
padding: 5px 10px;
border-bottom: solid 1px #f2f3f7;
cursor: pointer;
color: rgb(89, 89, 89);
font-size: 12px;
DragZoom.prototype.init = function () {
const $canvas = $('#canvas')
this.element = d3.select('#canvas')
this.zoom = d3.behavior.zoom()
.scaleExtent([0.5, 2])
.on('zoom', () => {
this.scale = d3.event.scale
$canvas.css('transform', 'scale(' + this.scale + ')')
$canvas.css('transform-origin', '0 0')
})
this.element.call(this.zoom).on('dblclick.zoom', null)
&:hover:not(.disabled){
color: #262626;
background-color: #f5f5f5;
}
export default new DragZoom()
&.disabled{
cursor: not-allowed;
color: rgba(89, 89, 89, .4);
}
}
}

155
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/contextMenu.vue

@ -0,0 +1,155 @@
/*
* 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.
*/
<template>
<div
class="dag-context-menu"
v-show="visible"
:style="{
left: `${left}px`,
top: `${top}px`,
}"
>
<menu-item :disabled="!startAvailable" @on-click="onStart">
{{ $t("Start") }}
</menu-item>
<menu-item :disabled="readOnly" @on-click="onEdit">
{{ $t("Edit") }}
</menu-item>
<menu-item :disabled="readOnly" @on-click="onCopy">
{{ $t("Copy") }}
</menu-item>
<menu-item :disabled="readOnly" @on-click="onDelete">
{{ $t("Delete") }}
</menu-item>
</div>
</template>
<script>
import { mapState, mapActions, mapMutations } from 'vuex'
import { findComponentDownward, uuid } from '@/module/util/'
import MenuItem from './menuItem.vue'
export default {
name: 'dag-context-menu',
inject: ['dagChart', 'dagCanvas'],
components: {
MenuItem
},
data () {
return {
visible: false,
left: 0,
top: 0,
canvasRef: null,
currentTask: {
code: 0,
name: '',
type: ''
}
}
},
computed: {
...mapState('dag', ['isDetails', 'releaseState', 'tasks']),
startAvailable () {
return (
this.$route.name === 'projects-definition-details' &&
this.releaseState !== 'NOT_RELEASE'
)
},
readOnly () {
return this.isDetails
}
},
mounted () {
document.addEventListener('click', (e) => {
this.hide()
})
},
methods: {
...mapActions('dag', ['genTaskCodeList']),
...mapMutations('dag', ['addTask']),
getDagCanvasRef () {
if (this.canvasRef) {
return this.canvasRef
} else {
const canvas = findComponentDownward(this.dagChart, 'dag-canvas')
this.canvasRef = canvas
return canvas
}
},
setCurrentTask (task) {
this.currentTask = { ...this.currentTask, ...task }
},
onStart () {
this.dagChart.startRunning(this.currentTask.name)
},
onEdit () {
this.dagChart.openFormModel(this.currentTask.code, this.currentTask.type)
},
onCopy () {
const nodes = this.dagCanvas.getNodes()
const targetNode = nodes.find(
(node) => node.id === this.currentTask.code
)
const targetTask = this.tasks.find(
(task) => task.code === this.currentTask.code
)
if (!targetNode || !targetTask) return
this.genTaskCodeList({
genNum: 1
})
.then((res) => {
const [code] = res
const taskName = uuid(targetTask.name + '_')
const task = {
...targetTask,
code,
name: taskName
}
this.dagCanvas.addNode(code, this.currentTask.type, {
x: targetNode.position.x + 100,
y: targetNode.position.y + 100
})
this.addTask(task)
this.dagCanvas.setNodeName(code, taskName)
})
.catch((err) => {
console.error(err)
})
},
onDelete () {
this.dagCanvas.removeNode(this.currentTask.code)
},
show (x = 0, y = 0) {
this.dagCanvas.lockScroller()
this.visible = true
this.left = x + 10
this.top = y + 10
},
hide () {
this.dagCanvas.unlockScroller()
this.visible = false
}
}
}
</script>
<style lang="scss" scoped>
@import "./contextMenu";
</style>

50
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/draggableBox.vue

@ -0,0 +1,50 @@
/*
* 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.
*/
<template>
<div
class="draggable-box"
ref="draggable"
draggable="true"
@dragstart="onDragstart"
@drag="onDrag"
@dragend="onDragend"
>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'draggable-box',
data () {
return {
tmp: null
}
},
methods: {
onDragstart (e) {
this.$emit('onDragstart', e)
},
onDrag (e) {
this.$emit('onDrag', e)
},
onDragend (e) {
this.$emit('onDragend', e)
}
}
}
</script>

114
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/edgeEditModel.vue

@ -0,0 +1,114 @@
/*
* 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.
*/
<template>
<el-drawer
:visible.sync="drawerVisible"
:wrapperClosable="false"
size=""
:with-header="false"
>
<div class="form-model-wrapper">
<div class="title-box">
<span class="name">{{ $t("Current connection settings") }}</span>
</div>
<div class="content-box">
<div class="form-model">
<!-- Node name -->
<div class="clearfix list">
<div class="text-box">
<span>{{ $t("Connection name") }}</span>
</div>
<div class="cont-box">
<label class="label-box">
<el-input
type="text"
size="small"
v-model="label"
:disabled="isDetails"
:placeholder="$t('Please enter name')"
maxlength="100"
>
</el-input>
</label>
</div>
</div>
</div>
</div>
<div class="bottom-box">
<div class="submit" style="background: #fff">
<el-button type="text" size="small" @click="cancel()">
{{ $t("Cancel") }}
</el-button>
<el-button
type="primary"
size="small"
@click="ok()"
:disabled="isDetails"
>{{ $t("Confirm add") }}
</el-button>
</div>
</div>
</div>
</el-drawer>
</template>
<script>
import disabledState from '@/module/mixin/disabledState'
import { mapState } from 'vuex'
import { findComponentDownward } from '@/module/util/'
export default {
name: 'edge-edit-model',
data () {
return {
id: '',
label: '',
drawerVisible: false
}
},
inject: ['dagChart'],
mixins: [disabledState],
computed: {
...mapState('dag', ['isDetails'])
},
methods: {
getDagCanvasRef () {
if (this.canvasRef) {
return this.canvasRef
} else {
const canvas = findComponentDownward(this.dagChart, 'dag-canvas')
this.canvasRef = canvas
return canvas
}
},
show ({ id, label }) {
this.id = id
this.label = label
this.drawerVisible = true
},
cancel () {
this.drawerVisible = false
this.id = ''
this.label = ''
},
ok () {
const canvas = this.getDagCanvasRef()
canvas.setEdgeLabel(this.id, this.label)
this.cancel()
}
}
}
</script>

42
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/menuItem.vue

@ -0,0 +1,42 @@
/*
* 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.
*/
<template>
<div class="menu-item" :class="disabled ? 'disabled' : ''" @click="onClick">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'dag-context-menu-item',
props: {
disabled: {
type: Boolean,
default: false
}
},
data () {
return {}
},
methods: {
onClick (e) {
if (this.disabled) return
this.$emit('on-click', e)
}
}
}
</script>

16
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/statusMenu.scss

@ -0,0 +1,16 @@
/*
* 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.
*/

70
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/statusMenu.vue

@ -0,0 +1,70 @@
/*
* 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.
*/
<template>
<div
class="dag-status-menu"
v-show="visible"
:style="{
left: `${left}px`,
top: `${top}px`,
}"
>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'dag-status-menu',
inject: ['dagChart', 'dagCanvas'],
data () {
return {
visible: false,
left: 0,
top: 0,
currentTask: {
code: 0,
name: '',
type: ''
}
}
},
computed: {
...mapState('dag', ['tasks'])
},
methods: {
setCurrentTask (task) {
this.currentTask = { ...this.currentTask, ...task }
},
show (x = 0, y = 0) {
this.dagCanvas.lockScroller()
this.visible = true
this.left = x
this.top = y
},
hide () {
this.dagCanvas.unlockScroller()
this.visible = false
}
}
}
</script>
<style lang="scss" scoped>
@import "./contextMenu";
</style>

167
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.scss

@ -0,0 +1,167 @@
/*
* 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.
*/
.dag-taskbar {
width: 190px;
height: 100%;
background-color: #fff;
margin-right: 20px;
.taskbar-title {
display: flex;
border-bottom: dashed 1px #e5e5e5;
height: 42px;
padding: 0 20px;
align-items: center;
h4 {
color: #666;
font-size: 14px;
}
}
.tasks {
width: 100%;
padding: 10px 20px;
display: flex;
flex-wrap: wrap;
box-sizing: border-box;
max-height: calc(100% - 42px);
overflow: auto;
.draggable-box {
cursor: move;
width: 100%;
height: 32px;
margin-bottom: 10px;
.task-item {
width: 100%;
height: 100%;
display: flex;
align-items: center;
border: 1px dashed #e4e4e4;
padding: 0 10px;
border-radius: 4px;
em {
margin-right: 10px;
display: block;
width: 18px;
height: 18px;
background-size: 100% 100%;
&.icos-shell {
background-image: url("../images/task-icos/shell.png");
}
&.icos-sub_process {
background-image: url("../images/task-icos/sub_process.png");
}
&.icos-procedure {
background-image: url("../images/task-icos/procedure.png");
}
&.icos-sql {
background-image: url("../images/task-icos/sql.png");
}
&.icos-flink {
background-image: url("../images/task-icos/flink.png");
}
&.icos-mr {
background-image: url("../images/task-icos/mr.png");
}
&.icos-python {
background-image: url("../images/task-icos/python.png");
}
&.icos-dependent {
background-image: url("../images/task-icos/dependent.png");
}
&.icos-http {
background-image: url("../images/task-icos/http.png");
}
&.icos-datax {
background-image: url("../images/task-icos/datax.png");
}
&.icos-sqoop {
background-image: url("../images/task-icos/sqoop.png");
}
&.icos-conditions {
background-image: url("../images/task-icos/conditions.png");
}
&.icos-waterdrop {
background-image: url("../images/task-icos/waterdrop.png");
}
&.icos-spark {
background-image: url("../images/task-icos/spark.png");
}
}
span {
font-size: 12px;
}
&:hover {
color: #288fff;
border: 1px dashed #288fff;
background-color: rgba(40, 143, 255, 0.1);
em {
&.icos-shell {
background-image: url("../images/task-icos/shell_hover.png");
}
&.icos-sub_process {
background-image: url("../images/task-icos/sub_process_hover.png");
}
&.icos-procedure {
background-image: url("../images/task-icos/procedure_hover.png");
}
&.icos-sql {
background-image: url("../images/task-icos/sql_hover.png");
}
&.icos-flink {
background-image: url("../images/task-icos/flink_hover.png");
}
&.icos-mr {
background-image: url("../images/task-icos/mr_hover.png");
}
&.icos-python {
background-image: url("../images/task-icos/python_hover.png");
}
&.icos-dependent {
background-image: url("../images/task-icos/dependent_hover.png");
}
&.icos-http {
background-image: url("../images/task-icos/http_hover.png");
}
&.icos-datax {
background-image: url("../images/task-icos/datax_hover.png");
}
&.icos-sqoop {
background-image: url("../images/task-icos/sqoop_hover.png");
}
&.icos-conditions {
background-image: url("../images/task-icos/conditions_hover.png");
}
&.icos-waterdrop {
background-image: url("../images/task-icos/waterdrop_hover.png");
}
&.icos-spark {
background-image: url("../images/task-icos/spark_hover.png");
}
}
}
}
}
}
}

64
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.vue

@ -0,0 +1,64 @@
/*
* 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.
*/
<template>
<div class="dag-taskbar">
<div class="taskbar-title">
<h4>{{$t('Toolbar')}}</h4>
</div>
<div class="tasks">
<template v-for="taskType in tasksTypeList">
<draggable-box
:key="taskType.name"
@onDragstart="(e) => $emit('on-drag-start', e, taskType)"
>
<div class="task-item">
<em :class="`icos-${taskType.name.toLocaleLowerCase()}`"></em>
<span>{{ taskType.name }}</span>
</div>
</draggable-box>
</template>
</div>
</div>
</template>
<script>
import draggableBox from './draggableBox.vue'
import { tasksType } from '../config.js'
export default {
name: 'dag-taskbar',
components: {
draggableBox
},
data () {
const tasksTypeList = Object.keys(tasksType).map((type) => {
return {
name: type,
desc: tasksType[type].desc
}
})
return {
tasksTypeList
}
}
}
</script>
<style lang="scss" scoped>
@import "./taskbar";
</style>

113
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.scss

@ -0,0 +1,113 @@
/*
* 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.
*/
.dag-toolbar {
width: 100%;
height: 50px;
background-color: #fff;
padding: 0 20px;
display: flex;
align-items: center;
box-sizing: border-box;
h3 {
font-size: 14px;
font-weight: bold;
margin: 0 10px 0 0;
}
.transparent {
width: 1px;
height: 1px;
opacity: 0;
}
.toolbar-operation {
font-size: 18px;
cursor: pointer;
margin-left: 20px;
color: #666666;
&:hover {
color: #288fff;
}
&.last{
margin-right: 10px;
}
}
.toolbar-btn {
font-size: 16px;
}
.toolbar-left {
flex: 1;
display: flex;
align-items: center;
}
.toolbar-right {
justify-self: flex-end;
display: flex;
align-items: center;
}
.toolbar-el-btn{
margin-right: 0;
margin-left: 10px;
}
.custom-ico{
display: block;
width: 18px;
height: 18px;
background-size: 100% 100%;
&.view-variables{
background-image: url('../images/view-variables.png');
&:hover{
background-image: url('../images/view-variables_hover.png');
}
}
&.startup-parameters{
background-image: url('../images/startup-params.png');
&:hover{
background-image: url('../images/startup-params_hover.png');
}
}
&.full-screen-open{
background-image: url('../images/full-screen-open.png');
&:hover{
background-image: url('../images/full-screen-open_hover.png');
}
}
&.full-screen-close{
background-image: url('../images/full-screen-close.png');
&:hover{
background-image: url('../images/full-screen-close_hover.png');
}
}
&.graph-format{
background-image: url('../images/graph-format.png');
&:hover{
background-image: url('../images/graph-format_hover.png');
}
}
}
}

199
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.vue

@ -0,0 +1,199 @@
/*
* 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.
*/
<template>
<div class="dag-toolbar">
<h3>{{ dagChart.name || $t("Create process") }}</h3>
<el-tooltip
v-if="dagChart.name"
class="toolbar-operation"
:content="$t('Copy name')"
placement="bottom"
>
<i class="el-icon-copy-document" @click="copyName"></i>
</el-tooltip>
<textarea ref="textarea" cols="30" rows="10" class="transparent"></textarea>
<div class="toolbar-left">
<el-tooltip
:content="$t('View variables')"
placement="bottom"
class="toolbar-operation"
>
<i
class="custom-ico view-variables"
v-if="$route.name === 'projects-instance-details'"
@click="toggleVariableView"
></i>
</el-tooltip>
<el-tooltip
:content="$t('Startup parameter')"
placement="bottom"
class="toolbar-operation"
>
<i
class="custom-ico startup-parameters"
v-if="$route.name === 'projects-instance-details'"
@click="toggleParamView"
></i>
</el-tooltip>
</div>
<div class="toolbar-right">
<el-tooltip
class="toolbar-operation"
:content="$t('Delete selected lines or nodes')"
placement="bottom"
>
<i class="el-icon-delete" @click="removeCells"></i>
</el-tooltip>
<el-tooltip
class="toolbar-operation"
:content="$t('Download')"
placement="bottom"
>
<i class="el-icon-download" @click="downloadPNG"></i>
</el-tooltip>
<el-tooltip
class="toolbar-operation"
:content="$t('Full Screen')"
placement="bottom"
>
<i
:class="[
'custom-ico',
dagChart.fullScreen
? 'full-screen-close'
: 'full-screen-open',
]"
@click="toggleFullScreen"
></i>
</el-tooltip>
<el-tooltip
class="toolbar-operation last"
:content="$t('Format DAG')"
placement="bottom"
>
<i class="custom-ico graph-format" @click="chartFormat"></i>
</el-tooltip>
<el-button
class="toolbar-el-btn"
type="primary"
size="mini"
@click="saveProcess"
>{{ $t("Save") }}</el-button
>
<el-button
class="toolbar-el-btn"
v-if="$route.query.subProcessCodes"
type="primary"
size="mini"
icon="el-icon-back"
@click="dagChart.returnToPrevProcess"
>
{{ $t("Return_1") }}
</el-button>
<el-button
class="toolbar-el-btn"
type="primary"
icon="el-icon-switch-button"
size="mini"
v-if="type === 'instance' || 'definition'"
@click="returnToListPage"
>
{{ $t("Close") }}
</el-button>
</div>
</div>
</template>
<script>
import { findComponentDownward } from '@/module/util/'
export default {
name: 'dag-toolbar',
inject: ['dagChart'],
data () {
return {
canvasRef: null
}
},
methods: {
getDagCanvasRef () {
if (this.canvasRef) {
return this.canvasRef
} else {
const canvas = findComponentDownward(this.dagChart, 'dag-canvas')
this.canvasRef = canvas
return canvas
}
},
toggleVariableView () {
findComponentDownward(this.$root, 'assist-dag-index')._toggleView()
},
toggleParamView () {
findComponentDownward(
this.$root,
'starting-params-dag-index'
)._toggleParam()
},
toggleFullScreen () {
this.dagChart.toggleFullScreen()
},
saveProcess () {
const canvas = this.getDagCanvasRef()
const nodes = canvas.getNodes()
if (!nodes.length) {
this.$message.error(this.$t('Failed to create node to save'))
return
}
this.dagChart.toggleSaveDialog(true)
},
downloadPNG () {
const canvas = this.getDagCanvasRef()
canvas.downloadPNG(this.processName)
},
removeCells () {
const canvas = this.getDagCanvasRef()
const selections = canvas.getSelections()
canvas.removeCells(selections)
},
copyName () {
const textarea = this.$refs.textarea
textarea.value = this.dagChart.name
textarea.select()
document.execCommand('copy')
this.$message(this.$t('Copy success'))
},
chartFormat () {
const canvas = this.getDagCanvasRef()
canvas.format()
},
// TODO
refreshTaskState () {},
returnToListPage () {
let $name = this.$route.name
if ($name && $name.indexOf('definition') !== -1) {
this.$router.push({ name: 'projects-definition-list' })
} else {
this.$router.push({ name: 'projects-instance-list' })
}
}
}
}
</script>
<style lang="scss" scoped>
@import "./toolbar";
</style>

323
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-helper.js

@ -0,0 +1,323 @@
/*
* 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.
*/
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 BG_WHITE = '#FFFFFF'
const NODE_BORDER = '#e4e4e4'
const TITLE = '#333'
const STROKE_BLUE = '#288FFF'
export const PORT_PROPS = {
groups: {
[X6_PORT_OUT_NAME]: {
position: {
name: 'absolute',
args: {
x: 200,
y: 24
}
},
markup: [
{
tagName: 'g',
selector: 'body',
children: [
{
tagName: 'circle',
selector: 'circle-outer'
},
{
tagName: 'text',
selector: 'plus-text'
},
{
tagName: 'circle',
selector: 'circle-inner'
}
]
}
],
attrs: {
body: {
magnet: true
},
'plus-text': {
fontSize: 12,
fill: EDGE,
text: '+',
textAnchor: 'middle',
x: 0,
y: 3
},
'circle-outer': {
stroke: EDGE,
strokeWidth: 1,
r: 6,
fill: BG_WHITE
},
'circle-inner': {
r: 4,
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'
}]
}
],
attrs: {
body: {
magnet: true
},
circle: {
r: 4,
strokeWidth: 0,
fill: 'transparent'
}
}
}
}
}
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
}
}
},
[X6_PORT_IN_NAME]: {}
}
export const NODE_PROPS = {
width: 220,
height: 48,
markup: [
{
tagName: 'rect',
selector: 'body'
},
{
tagName: 'image',
selector: 'image'
},
{
tagName: 'text',
selector: 'title'
}
// {
// tagName: 'foreignObject',
// selector: 'fo',
// children: [
// {
// tagName: 'body',
// selector: 'fo-body',
// ns: 'http://www.w3.org/1999/xhtml',
// children: [{
// tagName: 'i',
// selector: 'state',
// className: 'state-icon el-icon-circle-check'
// }]
// }
// ]
// }
],
attrs: {
body: {
refWidth: '100%',
refHeight: '100%',
rx: 6,
ry: 6,
pointerEvents: 'visiblePainted',
fill: BG_WHITE,
stroke: NODE_BORDER,
strokeWidth: 1
},
image: {
width: 30,
height: 30,
refX: 12,
refY: 9
},
title: {
refX: 45,
refY: 18,
fontFamily: 'Microsoft Yahei',
fontSize: 12,
fontWeight: 'bold',
fill: TITLE,
strokeWidth: 0
},
fo: {
refX: '46%',
refY: -25,
width: 18,
height: 18
},
state: {
style: {
display: 'block',
width: '100%',
height: '100%',
fontSize: '18px'
}
}
},
ports: {
...PORT_PROPS,
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 = {
attrs: {
body: {
fill: BG_BLUE,
stroke: STROKE_BLUE,
strokeDasharray: '5,2'
},
title: {
fill: STROKE_BLUE
}
}
}
export const EDGE_PROPS = {
attrs: {
line: {
stroke: EDGE,
strokeWidth: 0.8,
targetMarker: {
tagName: 'path',
fill: EDGE,
strokeWidth: 0,
d: 'M 6 -3 0 0 6 3 Z'
}
}
},
connector: {
name: 'rounded'
},
router: {
name: 'er',
args: {
offset: 20,
min: 20,
direction: 'L'
}
},
defaultLabel: {
markup: [
{
tagName: 'rect',
selector: 'body'
},
{
tagName: 'text',
selector: 'label'
}
],
attrs: {
label: {
fill: EDGE,
fontSize: 14,
textAnchor: 'middle',
textVerticalAnchor: 'middle',
pointerEvents: 'none'
},
body: {
ref: 'label',
fill: BG_WHITE,
stroke: EDGE,
strokeWidth: 1,
rx: 4,
ry: 4,
refWidth: '140%',
refHeight: '140%',
refX: '-20%',
refY: '-20%'
}
},
position: {
distance: 0.5,
options: {
absoluteDistance: true,
reverseDistance: true
}
}
}
}
export const EDGE_HIGHLIGHT_PROPS = {
attrs: {
line: {
stroke: STROKE_BLUE,
targetMarker: {
fill: STROKE_BLUE
}
}
},
defaultLabel: {
attrs: {
label: {
fill: STROKE_BLUE
},
body: {
fill: BG_WHITE,
stroke: STROKE_BLUE
}
}
}
}

30
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style.scss

@ -0,0 +1,30 @@
/*
* 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.
*/
$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;
}
}
}
}

195
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.js

@ -1,195 +0,0 @@
/*
* 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 Vue from 'vue'
import _ from 'lodash'
import i18n from '@/module/i18n'
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
*/
const Dag = function () {
this.dag = {}
this.instance = {}
}
/**
* init
* @dag dag vue instance
*/
Dag.prototype.init = function ({ dag, instance }) {
this.dag = dag
this.instance = instance
}
/**
* set init config
*/
Dag.prototype.setConfig = function (o) {
JSP.setConfig(o)
}
/**
* create dag
*/
Dag.prototype.create = function () {
const self = this
const plumbIns = jsPlumb.getInstance()
plumbIns.reset()
plumbIns.ready(() => {
JSP.init({
dag: this.dag,
instance: this.instance,
options: {
onRemoveNodes ($id) {
self.dag.removeEventModelById($id)
}
}
})
// init event
JSP.handleEvent()
// init draggable
JSP.draggable()
})
}
/**
* Action event on the right side of the toolbar
*/
Dag.prototype.toolbarEvent = function ({ item, code, is }) {
const self = this
switch (code) {
case 'pointer':
JSP.handleEventPointer(is)
break
case 'line':
JSP.handleEventLine(is)
break
case 'remove':
JSP.handleEventRemove()
break
case 'screen':
JSP.handleEventScreen({ item, is })
break
case 'download':
Vue.prototype.$confirm(`${i18n.$t('Please confirm whether the workflow has been saved before downloading')}`, `${i18n.$t('Download')}`, {
confirmButtonText: `${i18n.$t('Confirm')}`,
cancelButtonText: `${i18n.$t('Cancel')}`,
type: 'warning'
}).then(() => {
DownChart.download({
dagThis: self.dag
})
}).catch(() => {
})
break
}
}
/**
* Echo data display
*/
Dag.prototype.backfill = function (arg) {
const that = this
if (arg) {
const marginX = 100
const g = new dagre.graphlib.Graph()
g.setGraph({})
g.setDefaultEdgeLabel(function () { return {} })
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 })
}
for (const i in store.state.dag.connects) {
const connect = store.state.dag.connects[i]
g.setEdge(connect.endPointSourceId, connect.endPointTargetId)
}
dagre.layout(g)
const dataObject = {}
g.nodes().forEach(function (v) {
const node = g.node(v)
const location = store.state.dag.locations[node.label]
const obj = {}
obj.name = location.name
obj.x = node.x + marginX
obj.y = node.y
obj.targetarr = location.targetarr
dataObject[node.label] = obj
})
jsPlumb.ready(() => {
JSP.init({
dag: this.dag,
instance: this.instance,
options: {
onRemoveNodes ($id) {
that.dag.removeEventModelById($id)
}
}
})
// Backfill
JSP.jspBackfill({
// connects
connects: _.cloneDeep(store.state.dag.connects),
// Node location information
locations: _.cloneDeep(dataObject),
// Node data
largeJson: _.cloneDeep(store.state.dag.tasks)
})
})
} else {
const plumbIns = jsPlumb.getInstance()
plumbIns.reset()
plumbIns.ready(() => {
JSP.init({
dag: this.dag,
instance: this.instance,
options: {
onRemoveNodes ($id) {
that.dag.removeEventModelById($id)
}
}
})
// Backfill
JSP.jspBackfill({
// connects
connects: _.cloneDeep(store.state.dag.connects),
// Node location information
locations: _.cloneDeep(store.state.dag.locations),
// Node data
largeJson: _.cloneDeep(store.state.dag.tasks)
})
})
}
}
/**
* Get dag storage format data
*/
Dag.prototype.saveStore = function () {
return JSP.saveStore()
}
export default new Dag()

557
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.scss

@ -15,557 +15,18 @@
* limitations under the License.
*/
.dag-model {
background: url("../img/dag_bg.png");
.dag-chart {
width: 100%;
height: calc(100vh - 100px);
::selection {
background:transparent;
}
::-moz-selection {
background:transparent;
}
::-webkit-selection {
background:transparent;
}
.jsplumb-connector {
z-index: 1;
}
.endpoint-tasks {
margin-top:22px;
}
.draggable {
> span {
text-align: center;
display: block;
margin-top: -4px;
padding: 0 4px;
width: 200px;
margin-left: -81px;
position: absolute;
left: 0;
bottom: -12px;
}
.fa {
display: inline-block;
position: absolute;
right: -8px;
top: -8px;
z-index: 2;
cursor: pointer;
}
.icos {
display: inline-block;
cursor: pointer;
}
&.active-tasks {
span {
color: #0296DF;
}
}
}
.icos {
width: 32px;
height: 32px;
margin: 2px;
border-radius: 3px;
position: relative;
z-index: 9;
}
.icos-SHELL {
background: url("../img/toolbar_SHELL.png") no-repeat 50% 50%;
}
.icos-WATERDROP {
background: url("../img/toolbar_WATERDROP.png") no-repeat 50% 50%;
}
.icos-SUB_PROCESS {
background: url("../img/toolbar_SUB_PROCESS.png") no-repeat 50% 50%;
}
.icos-PROCEDURE {
background: url("../img/toolbar_PROCEDURE.png") no-repeat 50% 50%;
}
.icos-SQL {
background: url("../img/toolbar_SQL.png") no-repeat 50% 50%;
}
.icos-SPARK {
background: url("../img/toolbar_SPARK.png") no-repeat 50% 50%;
}
.icos-FLINK {
background: url("../img/toolbar_FLINK.png") no-repeat 50% 50%;
}
.icos-MR {
background: url("../img/toolbar_MR.png") no-repeat 50% 50%;
}
.icos-PYTHON {
background: url("../img/toolbar_PYTHON.png") no-repeat 50% 50%;
}
.icos-DEPENDENT {
background: url("../img/toolbar_DEPENDENT.png") no-repeat 50% 50%;
}
.icos-HTTP {
background: url("../img/toolbar_HTTP.png") no-repeat 50% 50%;
}
.icos-DATAX {
background: url("../img/toolbar_DATAX.png") no-repeat 50% 50%;
}
.icos-SQOOP {
background: url("../img/toolbar_SQOOP.png") no-repeat 50% 50%;
}
.icos-CONDITIONS {
background: url("../img/toolbar_CONDITIONS.png") no-repeat 50% 50%;
}
.toolbar {
width: 60px;
height: 100%;
background: #F2F3F7;
float: left;
border-radius: 0 0 0 3px;
.title {
height: 40px;
line-height: 40px;
background: #40434C;
text-align: center;
border-radius: 3px 0 0 0;
span {
font-size: 14px;
color: #fff;
font-weight: bold;
}
}
.toolbar-btn {
overflow: hidden;
padding: 8px 11px 0 11px;
.bar-box {
width: 36px;
height: 36px;
float: left;
margin-bottom: 3px;
border-radius: 3px;
.disabled {
.icos {
opacity: .6;
-webkit-filter: grayscale(100%);
-moz-filter: grayscale(100%);
-ms-filter: grayscale(100%);
-o-filter: grayscale(100%);
filter: grayscale(100%);
filter: gray;
}
}
&:nth-child(odd) {
margin-right: 6px;
}
&.active {
background: #e1e2e3;
}
}
}
}
.dag-contect {
float: left;
width: calc(100% - 60px);
height: 100%;
.dag-toolbar {
height: 40px;
background: #F2F3F7;
position: relative;
border-radius: 0 3px 0 0;
.ans-btn-text {
color: #337ab7;
.ans-icon {
font-size: 16px;
}
}
.assist-btn {
position: absolute;
left: 10px;
top: 7px;
>.name {
padding-left: 6px;
vertical-align: middle;
}
>.copy-name {
cursor: pointer;
padding-left: 4px;
position: relative;
top: -2px;
&:hover {
i {
color: #47c3ff;
}
}
i {
color: #333;
font-size: 18px;
vertical-align: middle;
}
}
}
.save-btn {
position: absolute;
right: 8px;
top: 6px;
.operation {
overflow: hidden;
display: inline-block;
a {
float: left;
width: 28px;
height: 28px;
text-align: center;
line-height: 28px;
margin-left: 6px;
border-radius: 3px;
vertical-align: middle;
i {
color: #333;
}
&.active {
// background: #e1e2e3;
i {
color: #2d8cf0;
}
}
&.disable {
i {
color: #bbb;
}
}
}
}
}
}
.dag-container {
height: calc(100% - 40px);
overflow-x: auto;
&::-webkit-scrollbar{
width: 9px;
}
}
}
.tools-model {
height: 60px;
background: #F4F5F4;
border-radius: 3px 3px 0px 0px;
}
}
#screen {
margin-right: 5px;
}
.v-modal-custom-log {
z-index: 101;
}
svg path:hover {
cursor: pointer;
}
padding: 10px;
background: #f2f3f7;
#chart-container .ui-selecting {
span {
color: #0296DF;
}
}
#chart-container .ui-selected {
span {
color: #0296DF;
}
}
.contextmenu {
&.full-screen {
position: fixed;
width: 90px;
background: #fff;
border-radius: 3px;
box-shadow: 0 2px 4px 1px rgba(0, 0, 0, 0.1);
padding: 4px 4px;
visibility:hidden;
z-index: 10000;
a {
height: 30px;
line-height: 28px;
display: block;
i {
font-size: 16px;
vertical-align: middle;
margin-left: 10px;
}
span {
vertical-align: middle;
font-size: 12px;
color: #666;
padding-left: 2px;
}
&:hover {
background: #f6faff;
}
&#startRunning {
i {
color: #35cd75;
}
}
&#editNodes {
i {
color: #0097e0;
}
}
&#removeNodes {
i {
color: #f04d4e;
}
}
&#copyNodes {
i {
color: #FABC05;
}
}
&.disbled {
i,span {
color: #aaa !important;
}
}
}
}
.jtk-demo {
//min-width: calc(100% - 220px);
width: 8000px;
height: 5000px;
svg:not(:root){
z-index: 11;
}
}
.jtk-demo-canvas {
position: relative;
height: 100%;
display: flex;
}
.jtk-bootstrap {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.jtk-bootstrap .jtk-page-container {
display: flex;
width: 100vw;
justify-content: center;
flex: 1;
}
.jtk-bootstrap .jtk-container {
width: 60%;
max-width: 800px;
}
.jtk-bootstrap-wide .jtk-container {
width: 80%;
max-width: 1187px;
}
.jtk-demo-main {
position: relative;
margin-top: 98px;
}
.jtk-demo-main .description {
font-size: 13px;
margin-top: 25px;
padding: 13px;
margin-bottom: 22px;
background-color: #f4f5ef;
}
.jtk-demo-main .description li {
list-style-type: disc !important;
}
.canvas-wide {
padding-top: 10px;
margin-left: 0;
-ms-transition: all .1s ease-out;
-moz-transition: all .1s ease-out;
-webkit-transition: all .1s ease-out;
-o-transition: all .1s ease-out;
}
.jtk-demo-dataset {
text-align: left;
max-height: 600px;
overflow: auto;
}
.demo-title {
float: left;
font-size: 18px;
}
.controls {
top: 25px;
color: #FFF;
margin-right: 10px;
position: absolute;
left: 25px;
z-index: 1;
}
.controls i {
background-color: #3E7E9C;
border-radius: 4px;
cursor: pointer;
margin-right: 0;
padding: 4px;
}
.w {
position: absolute;
z-index: 4;
font-size: 11px;
-webkit-transition: background-color 0.25s ease-in;
-moz-transition: background-color 0.25s ease-in;
transition: background-color 0.25s ease-in;
border: 7px solid transparent;
border-bottom: 30px solid transparent;
.icos {
width: 50px;
height: 50px;
box-shadow: 2px 2px 19px #e0e0e0;
-o-box-shadow: 2px 2px 19px #e0e0e0;
-webkit-box-shadow: 2px 2px 19px #e0e0e0;
-moz-box-shadow: 2px 2px 19px #e0e0e0;
-moz-border-radius: 8px;
border-radius: 8px;
opacity: 0.8;
cursor: move;
background-color: #fff;
}
.name-p {
position: absolute;
left: 50%;
top: 58px;
width: 200px;
text-align: center;
margin-left: -100px;
word-break:break-all;
}
.ban-p {
position: absolute;
left: -4px;
top: 36px;
z-index: 21;
i {
font-size: 18px;
color: #ff0000;
cursor: pointer;
}
}
.state-p {
width: 20px;
height: 20px;
position: absolute;
top: -20px;
left: 18px;
text-align: center;
cursor: pointer;
b {
font-weight: normal;
}
}
}
.aLabel {
-webkit-transition: background-color 0.25s ease-in;
-moz-transition: background-color 0.25s ease-in;
transition: background-color 0.25s ease-in;
background-color: white;
opacity: 0.8;
padding: 0.3em;
border-radius: 0.5em;
border: 1px solid #346789;
cursor: pointer;
}
.aLabel.jtk-hover,
.jtk-source-hover,
.jtk-target-hover {
.icos {
background-color: #333;
color: #333;
-ms-transition: all 0.6s ease-out;
-moz-transition: all 0.6s ease-out;
-webkit-transition: all 0.6s ease-out;
-o-transition: all 0.6s ease-out;
}
}
.jtk-tasks-active {
.icos {
background-color: #2db7f5;
color: #0097e0;
-ms-transition: all 0.6s ease-out;
-moz-transition: all 0.6s ease-out;
-webkit-transition: all 0.6s ease-out;
-o-transition: all 0.6s ease-out;
}
}
.jtk-ep {
.ep {
display: block;
}
}
.ep {
position: absolute;
top: -4%;
right: -1px;
width: 1em;
height: 1em;
z-index: 12;
background-color: orange;
cursor: pointer;
box-shadow: 0 0 2px black;
-webkit-transition: -webkit-box-shadow 0.25s ease-in;
-moz-transition: -moz-box-shadow 0.25s ease-in;
transition: box-shadow 0.25s ease-in;
border-radius:100%;
display: none;
}
.ep:hover {
box-shadow: 0 0 6px black;
}
.statemachine-demo .jtk-endpoint {
z-index: 3;
}
#canvas {
.dot-style {
opacity: 0;
}
}
.form-mirror {
width: 100%;
position: relative;
z-index: 0;
.CodeMirror {
height:auto;
min-height: 72px;
}
.CodeMirror-scroll {
height:auto;
min-height: 72px;
overflow-y: hidden;
overflow-x: auto;
}
height: 100%;
top: 0;
left: 0;
z-index: 9999;
}
.ans-modal-box.ans-drawer.ans-drawer-right.dagMask.mask {
width: 628px;
left: auto;
}

1081
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue

File diff suppressed because it is too large Load Diff

214
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue

@ -50,8 +50,8 @@
<div slot="text">{{$t('Run flag')}}</div>
<div slot="content">
<el-radio-group v-model="runFlag" size="small">
<el-radio :label="'NORMAL'" :disabled="isDetails">{{$t('Normal')}}</el-radio>
<el-radio :label="'FORBIDDEN'" :disabled="isDetails">{{$t('Prohibition execution')}}</el-radio>
<el-radio :label="'YES'" :disabled="isDetails">{{$t('Normal')}}</el-radio>
<el-radio :label="'NO'" :disabled="isDetails">{{$t('Prohibition execution')}}</el-radio>
</el-radio-group>
</div>
</m-list-box>
@ -259,11 +259,12 @@
:pre-node="nodeData.preNode">
</m-conditions>
<!-- Pre-tasks in workflow -->
<m-pre-tasks
<!-- TODO -->
<!-- <m-pre-tasks
v-if="['SHELL', 'SUB_PROCESS'].indexOf(nodeData.taskType) > -1"
@on-pre-tasks="_onPreTasks"
ref="PRE_TASK"
:backfill-item="backfillItem"></m-pre-tasks>
:backfill-item="backfillItem"></m-pre-tasks> -->
</div>
</div>
<div class="bottom-box">
@ -276,7 +277,7 @@
</template>
<script>
import _ from 'lodash'
import { mapActions } from 'vuex'
import { mapActions, mapState } from 'vuex'
import mLog from './log'
import mMr from './tasks/mr'
import mSql from './tasks/sql'
@ -287,7 +288,6 @@
import mSpark from './tasks/spark'
import mFlink from './tasks/flink'
import mPython from './tasks/python'
import JSP from './../plugIn/jsPlumbHandle'
import mProcedure from './tasks/procedure'
import mDependent from './tasks/dependent'
import mHttp from './tasks/http'
@ -299,10 +299,9 @@
import mTimeoutAlarm from './_source/timeoutAlarm'
import mDependentTimeout from './_source/dependentTimeout'
import mWorkerGroups from './_source/workerGroups'
import mPreTasks from './tasks/pre_tasks'
// import mPreTasks from './tasks/pre_tasks'
import clickoutside from '@/module/util/clickoutside'
import disabledState from '@/module/mixin/disabledState'
import { isNameExDag, rtBantpl } from './../plugIn/util'
import mPriority from '@/module/components/priority/priority'
export default {
@ -337,7 +336,7 @@
// Current node params data
params: {},
// Running sign
runFlag: 'NORMAL',
runFlag: 'YES',
// The second echo problem caused by the node data is specifically which node hook caused the unfinished special treatment
isContentBox: false,
// Number of failed retries
@ -380,6 +379,31 @@
},
methods: {
...mapActions('dag', ['getTaskInstanceList']),
taskToBackfillItem (task) {
return {
code: task.code,
conditionResult: task.taskParams.conditionResult,
delayTime: task.delayTime,
dependence: task.taskParams.dependence,
desc: task.description,
id: task.id,
maxRetryTimes: task.failRetryTimes,
name: task.name,
params: _.omit(task.taskParams, ['conditionResult', 'dependence']),
preTasks: [],
retryInterval: task.failRetryInterval,
runFlag: task.flag,
taskInstancePriority: task.taskPriority,
timeout: {
interval: task.timeout,
strategy: task.timeoutNotifyStrategy,
enable: task.timeoutFlag === 'OPEN'
},
type: task.taskType,
waitStartTimeout: task.waitStartTimeout,
workerGroup: task.workerGroup
}
},
/**
* depend
*/
@ -449,8 +473,10 @@
this.$message.error(e.msg || '')
})
} else {
const processDefinitionId = this.backfillItem.params.processDefinitionId
const process = this.processListS.find(process => process.processDefinition.id === processDefinitionId)
this.$emit('onSubProcess', {
subProcessId: this.backfillItem.params.processDefinitionId,
subProcessCode: process.processDefinition.code,
fromThis: this
})
}
@ -461,39 +487,7 @@
_onParams (o) {
this.params = Object.assign({}, o)
},
_onCacheParams (o) {
this.params = Object.assign(this.params, {}, o)
this._cacheItem()
},
_cacheItem () {
this.conditionResult.successNode[0] = this.successBranch
this.conditionResult.failedNode[0] = this.failedBranch
this.$emit('cacheTaskInfo', {
item: {
type: this.nodeData.taskType,
id: this.nodeData.id,
name: this.name,
code: this.code,
params: this.params,
desc: this.desc,
runFlag: this.runFlag,
conditionResult: this.conditionResult,
dependence: this.cacheDependence,
maxRetryTimes: this.maxRetryTimes,
retryInterval: this.retryInterval,
delayTime: this.delayTime,
timeout: this.timeout,
waitStartTimeout: this.waitStartTimeout,
taskInstancePriority: this.taskInstancePriority,
workerGroup: this.workerGroup,
status: this.status,
branch: this.branch
},
fromThis: this
})
},
_onCacheParams (o) {},
/**
* verification name
*/
@ -510,7 +504,9 @@
return true
}
// Name repeat depends on dom backfill dependent store
if (isNameExDag(this.name, _.isEmpty(this.backfillItem) ? 'dom' : 'backfill')) {
const tasks = this.store.state.dag.tasks
const task = tasks.find(t => t.name === 'this.name')
if (task) {
this.$message.warning(`${i18n.$t('Name already exists')}`)
return false
}
@ -558,66 +554,35 @@
if (!this.$refs.PRE_TASK._verification()) {
return
} else {
// Sync data-targetarr
$(`#${this.nodeData.id}`).attr(
'data-targetarr', this.preTaskIdsInWorkflow ? this.preTaskIdsInWorkflow.join(',') : '')
// TODO sync preTasks to graph
// Update JSP connections
let plumbIns = JSP.JspInstance
let targetId = this.nodeData.id
// Update new connections
this.preTasksToAdd.map(sourceId => {
plumbIns.connect({
source: sourceId,
target: targetId,
type: 'basic',
paintStyle: { strokeWidth: 2, stroke: '#2d8cf0' },
HoverPaintStyle: { stroke: '#ccc', strokeWidth: 3 }
})
})
// Update remove connections
let currentConnects = plumbIns.getAllConnections()
let len = currentConnects.length
for (let i = 0; i < len; i++) {
if (this.preTasksToDelete.indexOf(currentConnects[i].sourceId) > -1 && currentConnects[i].targetId === targetId) {
plumbIns.deleteConnection(currentConnects[i])
i -= 1
len -= 1
}
}
}
}
$(`#${this.nodeData.id}`).find('span').text(this.name)
this.conditionResult.successNode[0] = this.successBranch
this.conditionResult.failedNode[0] = this.failedBranch
// Store the corresponding node data structure
this.$emit('addTaskInfo', {
item: {
type: this.nodeData.taskType,
id: this.nodeData.id,
code: this.nodeData.id,
name: this.name,
code: this.code,
params: this.params,
desc: this.desc,
runFlag: this.runFlag,
conditionResult: this.conditionResult,
dependence: this.dependence,
maxRetryTimes: this.maxRetryTimes,
retryInterval: this.retryInterval,
delayTime: this.delayTime,
timeout: this.timeout,
waitStartTimeout: this.waitStartTimeout,
taskInstancePriority: this.taskInstancePriority,
description: this.desc,
taskType: this.nodeData.taskType,
taskParams: {
...this.params,
dependence: this.cacheDependence,
conditionResult: this.conditionResult
},
flag: this.runFlag,
taskPriority: this.taskInstancePriority,
workerGroup: this.workerGroup,
status: this.status,
branch: this.branch
failRetryTimes: this.maxRetryTimes,
failRetryInterval: this.retryInterval,
timeoutFlag: this.timeout.enable ? 'OPEN' : 'CLOSE',
timeoutNotifyStrategy: this.timeout.strategy,
timeout: this.timeout.interval || 0,
delayTime: this.delayTime
},
fromThis: this
})
// set run flag
this._setRunFlag()
},
@ -629,13 +594,10 @@
},
/**
* set run flag
* TODO
*/
_setRunFlag () {
let dom = $(`#${this.nodeData.id}`).find('.ban-p')
dom.html('')
if (this.runFlag === 'FORBIDDEN') {
dom.append(rtBantpl())
}
},
/**
* Submit verification
@ -661,42 +623,25 @@
})
}
},
watch: {
/**
* Watch the item change, cache the value it changes
**/
_item (val) {
// this._cacheItem()
}
},
created () {
// Unbind copy and paste events
JSP.removePaste()
// Backfill data
let taskList = this.store.state.dag.tasks
// fillback use cacheTasks
let cacheTasks = this.store.state.dag.cacheTasks
let o = {}
if (cacheTasks[this.nodeData.id]) {
o = cacheTasks[this.nodeData.id]
this.backfillItem = cacheTasks[this.nodeData.id]
} else {
if (taskList.length) {
taskList.forEach(v => {
if (v.id === this.nodeData.id) {
o = v
this.backfillItem = v
taskList.forEach(task => {
if (task.code === this.nodeData.id) {
const backfillItem = this.taskToBackfillItem(task)
o = backfillItem
this.backfillItem = backfillItem
}
})
}
}
// Non-null objects represent backfill
if (!_.isEmpty(o)) {
this.code = o.code
this.name = o.name
this.taskInstancePriority = o.taskInstancePriority
this.runFlag = o.runFlag || 'NORMAL'
this.runFlag = o.runFlag || 'YES'
this.desc = o.desc
this.maxRetryTimes = o.maxRetryTimes
this.retryInterval = o.retryInterval
@ -755,33 +700,14 @@
destroyed () {
},
computed: {
...mapState('dag', [
'processListS'
]),
/**
* Child workflow entry show/hide
*/
_isGoSubProcess () {
return this.nodeData.taskType === 'SUB_PROCESS' && this.name
},
// Define the item model
_item () {
return {
type: this.nodeData.taskType,
id: this.nodeData.id,
code: this.code,
name: this.name,
desc: this.desc,
runFlag: this.runFlag,
dependence: this.cacheDependence,
maxRetryTimes: this.maxRetryTimes,
retryInterval: this.retryInterval,
delayTime: this.delayTime,
timeout: this.timeout,
waitStartTimeout: this.waitStartTimeout,
taskInstancePriority: this.taskInstancePriority,
workerGroup: this.workerGroup,
successBranch: this.successBranch,
failedBranch: this.failedBranch
}
}
},
components: {
@ -805,8 +731,8 @@
mTimeoutAlarm,
mDependentTimeout,
mPriority,
mWorkerGroups,
mPreTasks
mWorkerGroups
// mPreTasks
}
}
</script>

1
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/log.vue

@ -79,6 +79,7 @@
store,
router,
isLog: false,
// TODO
stateId: $(`#${this.item.id}`).attr('data-state-id') || null,
isScreen: false,
loadingIndex: 0,

26
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/sub_process.vue

@ -30,7 +30,7 @@
v-for="city in processDefinitionList"
:key="city.code"
:value="city.id"
:label="city.code">
:label="city.name">
</el-option>
</el-select>
</div>
@ -74,14 +74,14 @@
/**
* The selected process defines the upper component name padding
*/
_handleWdiChanged (o) {
this.$emit('on-set-process-name', this._handleName(o))
_handleWdiChanged (id) {
this.$emit('on-set-process-name', this._handleName(id))
},
/**
* Return the name according to the process definition id
*/
_handleName (id) {
return _.filter(this.processDefinitionList, v => id === v.id)[0].code
return _.filter(this.processDefinitionList, v => id === v.id)[0].name
}
},
watch: {
@ -93,22 +93,20 @@
},
created () {
let processListS = _.cloneDeep(this.store.state.dag.processListS)
let id = null
let code = null
if (this.router.history.current.name === 'projects-instance-details') {
id = this.router.history.current.query.id || null
code = this.router.history.current.query.code || null
} else {
id = this.router.history.current.params.id || null
code = this.router.history.current.params.code || null
}
this.processDefinitionList = (() => {
let a = _.map(processListS, v => {
this.processDefinitionList = processListS.map(v => {
return {
id: v.id,
code: v.name,
id: v.processDefinition.id,
code: v.processDefinition.code,
name: v.processDefinition.name,
disabled: false
}
})
return _.filter(a, v => +v.id !== +id)
})()
}).filter(a => (a.code + '') !== code)
let o = this.backfillItem
// Non-null objects represent backfill

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/full-screen-close.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/full-screen-close_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/full-screen-open.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/full-screen-open_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/graph-format.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/graph-format_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/startup-params.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/startup-params_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/conditions.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/conditions_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/datax.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/datax_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/dependent.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/dependent_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/flink.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/flink_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/http.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/http_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/mr.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/mr_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/procedure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/procedure_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/python.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/python_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/shell.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/shell_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/spark.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/spark_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/sql.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/sql_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/sqoop.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/sqoop_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/sub_process.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/sub_process_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/waterdrop.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/task-icos/waterdrop_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/view-variables.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

BIN
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/images/view-variables_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

122
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/plugIn/downChart.js vendored

@ -1,122 +0,0 @@
/*
* 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 _ from 'lodash'
import canvg from 'canvg'
import { tasksAll } from './util'
import html2canvas from 'html2canvas'
import { findComponentDownward } from '@/module/util/'
const DownChart = function () {
this.dag = {}
}
/**
* Get interception location information
*/
DownChart.prototype.maxVal = function () {
return new Promise((resolve, reject) => {
// All nodes
const tasksAllList = tasksAll()
const dom = $('.dag-container')
const y = parseInt(_.maxBy(tasksAllList, 'y').y + 60)
const x = parseInt(_.maxBy(tasksAllList, 'x').x + 100)
resolve({
width: (x > 600 ? x : dom.width()) + 100,
height: (y > 500 ? y : dom.height()) + 100
})
})
}
/**
* Download to image
*/
DownChart.prototype.download = function ({ dagThis }) {
this.dag = dagThis
this.maxVal().then(({ width, height }) => {
// Dom to save
const copyDom = $('#canvas')
// gain
const scale = 1
// divReport is the id of the dom that needs to be intercepted into a picture
const svgElem = copyDom.find('svg')
svgElem.each((index, node) => {
// svg handle
const nodesToRecover = []
const nodesToRemove = []
const parentNode = node.parentNode
const svg = node.outerHTML.trim()
const canvas = document.createElement('canvas')
canvg(canvas, svg)
if (node.style.position) {
canvas.style.position += node.style.position
canvas.style.left += node.style.left
canvas.style.top += node.style.top
}
nodesToRecover.push({
parent: parentNode,
child: node
})
parentNode.removeChild(node)
nodesToRemove.push({
parent: parentNode,
child: canvas
})
parentNode.appendChild(canvas)
})
const canvas = document.createElement('canvas')
// canvas width
canvas.width = width * scale
// canvas height
canvas.height = height * scale
const content = canvas.getContext('2d')
content.scale(scale, scale)
// Get the offset of the element relative to the inspection
const rect = copyDom.get(0).getBoundingClientRect()
// Set the context position, the value is a negative value relative to the window offset, let the picture reset
content.translate(-rect.left, -rect.top)
html2canvas(copyDom[0], {
dpi: window.devicePixelRatio * 2,
scale: scale,
width: width,
canvas: canvas,
heigth: height,
useCORS: true // Enable cross-domain configuration
}).then((canvas) => {
const name = `${this.dag.name}.png`
const url = canvas.toDataURL('image/png', 1)
setTimeout(() => {
const triggerDownload = $('<a>').attr('href', url).attr('download', name).appendTo('body')
triggerDownload[0].click()
triggerDownload.remove()
}, 100)
// To refresh the dag instance, otherwise you can't re-plot
setTimeout(() => {
// Refresh current dag
findComponentDownward(this.dag.$root, `${this.dag.type}-details`).init()
}, 500)
})
})
}
export default new DownChart()

814
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/plugIn/jsPlumbHandle.js

@ -1,814 +0,0 @@
/*
* 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 'jquery-ui/ui/widgets/draggable'
import 'jquery-ui/ui/widgets/droppable'
import 'jquery-ui/ui/widgets/resizable'
import _ from 'lodash'
import i18n from '@/module/i18n'
import { jsPlumb } from 'jsplumb'
import DragZoom from './dragZoom'
import store from '@/conf/home/store'
import router from '@/conf/home/router'
import { uuid, findComponentDownward } from '@/module/util/'
import {
tasksAll,
rtTasksTpl,
setSvgColor,
saveTargetarr,
rtTargetarrArr,
computeScale
} from './util'
import multiDrag from './multiDrag'
const JSP = function () {
this.dag = {}
this.selectedElement = {}
this.config = {
// Whether to drag
isDrag: true,
// Whether to allow connection
isAttachment: false,
// Whether to drag a new node
isNewNodes: true,
// Whether to support double-click node events
isDblclick: true,
// Whether to support right-click menu events
isContextmenu: true,
// Whether to allow click events
isClick: false
}
}
/**
* dag init
*/
JSP.prototype.init = function ({ dag, instance, options }) {
// Get the dag component instance
this.dag = dag
// Get jsplumb instance
this.JspInstance = instance
// Get JSP options
this.options = options || {}
// Register jsplumb connection type and configuration
this.JspInstance.registerConnectionType('basic', {
anchor: 'Continuous',
connector: 'Bezier' // Line type
})
// Initial configuration
this.setConfig({
isDrag: !store.state.dag.isDetails,
isAttachment: false,
isNewNodes: !store.state.dag.isDetails, // Permissions.getAuth() === false ? false : !store.state.dag.isDetails,
isDblclick: true,
isContextmenu: true,
isClick: false
})
// Monitor line click
this.JspInstance.bind('click', e => {
// Untie event
if (this.config.isClick) {
this.connectClick(e)
} else {
findComponentDownward(this.dag.$root, 'dag-chart')._createLineLabel({ id: e._jsPlumb.overlays.label.canvas.id, sourceId: e.sourceId, targetId: e.targetId })
}
})
// Drag and drop
if (this.config.isNewNodes) {
DragZoom.init()
}
// support multi drag
multiDrag()
}
/**
* set config attribute
*/
JSP.prototype.setConfig = function (o) {
this.config = Object.assign(this.config, {}, o)
}
/**
* Node binding event
*/
JSP.prototype.tasksEvent = function (selfId) {
const tasks = $(`#${selfId}`)
// Bind right event
tasks.on('contextmenu', e => {
this.tasksContextmenu(e)
return false
})
// Binding double click event
tasks.find('.icos').bind('dblclick', e => {
this.tasksDblclick(e)
})
// Binding click event
tasks.on('click', e => {
this.tasksClick(e)
})
}
/**
* Dag node drag and drop processing
*/
JSP.prototype.draggable = function () {
if (this.config.isNewNodes) {
let selfId
const self = this
$('.toolbar-btn .roundedRect').draggable({
scope: 'plant',
helper: 'clone',
containment: $('.dag-model'),
stop: function (e, ui) {
},
drag: function () {
$('body').find('.tooltip.fade.top.in').remove()
}
})
$('#canvas').droppable({
scope: 'plant',
drop: function (ev, ui) {
let id = 'tasks-' + Math.ceil(Math.random() * 100000) // eslint-disable-line
let scale = computeScale($(this))
scale = scale || 1
// Get mouse coordinates and after scale coordinate
const left = parseInt(ui.offset.left - $(this).offset().left) / scale
const top = parseInt(ui.offset.top - $(this).offset().top) / scale
// Generate template node
$('#canvas').append(rtTasksTpl({
id: id,
name: id,
x: left,
y: top,
isAttachment: self.config.isAttachment,
taskType: findComponentDownward(self.dag.$root, 'dag-chart').dagBarId
}))
// Get the generated node
const thisDom = jsPlumb.getSelector('.statemachine-demo .w')
// Generating a connection node
self.JspInstance.batch(() => {
self.initNode(thisDom[thisDom.length - 1])
})
selfId = id
self.tasksEvent(selfId)
// Dom structure is not generated without pop-up form form
if ($(`#${selfId}`).html()) {
// dag event
findComponentDownward(self.dag.$root, 'dag-chart')._createNodes({
id: selfId
})
}
}
})
}
}
/**
* Echo json processing and old data structure processing
*/
JSP.prototype.jsonHandle = function ({ largeJson, locations }) {
_.map(largeJson, v => {
// Generate template
$('#canvas').append(rtTasksTpl({
id: v.id,
name: v.name,
x: locations[v.id].x,
y: locations[v.id].y,
targetarr: locations[v.id].targetarr,
isAttachment: this.config.isAttachment,
taskType: v.type,
runFlag: v.runFlag,
nodenumber: locations[v.id].nodenumber,
successNode: v.conditionResult === undefined ? '' : v.conditionResult.successNode[0],
failedNode: v.conditionResult === undefined ? '' : v.conditionResult.failedNode[0]
}))
// contextmenu event
$(`#${v.id}`).on('contextmenu', e => {
this.tasksContextmenu(e)
return false
})
// dblclick event
$(`#${v.id}`).find('.icos').bind('dblclick', e => {
this.tasksDblclick(e)
})
// click event
$(`#${v.id}`).bind('click', e => {
this.tasksClick(e)
})
})
}
/**
* Initialize a single node
*/
JSP.prototype.initNode = function (el) {
// Whether to drag
if (this.config.isDrag) {
this.JspInstance.draggable(el, {
containment: 'dag-container'
})
}
// Node attribute configuration
this.JspInstance.makeSource(el, {
filter: '.ep',
anchor: 'Continuous',
connectorStyle: {
stroke: '#2d8cf0',
strokeWidth: 2,
outlineStroke: 'transparent',
outlineWidth: 4
},
// This place is leaking
// connectionType: "basic",
extract: {
action: 'the-action'
},
maxConnections: -1
})
// Node connection property configuration
this.JspInstance.makeTarget(el, {
dropOptions: { hoverClass: 'dragHover' },
anchor: 'Continuous',
allowLoopback: false // Forbid yourself to connect yourself
})
this.JspInstance.fire('jsPlumbDemoNodeAdded', el)
}
/**
* Node right click menu
*/
JSP.prototype.tasksContextmenu = function (event) {
if (this.config.isContextmenu) {
const routerName = router.history.current.name
// state
const isOne = routerName === 'projects-definition-details' && this.dag.releaseState !== 'NOT_RELEASE'
// hide
const isTwo = store.state.dag.isDetails
const html = [
`<a href="javascript:" id="startRunning" class="${isOne ? '' : 'disbled'}"><em class="el-icon-video-play"></em><span>${i18n.$t('Start')}</span></a>`,
`<a href="javascript:" id="editNodes" class="${isTwo ? 'disbled' : ''}"><em class="el-icon-edit-outline"></em><span>${i18n.$t('Edit')}</span></a>`,
`<a href="javascript:" id="copyNodes" class="${isTwo ? 'disbled' : ''}"><em class="el-icon-copy-document"></em><span>${i18n.$t('Copy')}</span></a>`,
`<a href="javascript:" id="removeNodes" class="${isTwo ? 'disbled' : ''}"><em class="el-icon-delete"></em><span>${i18n.$t('Delete')}</span></a>`
]
const operationHtml = () => {
return html.splice(',')
}
const e = event
const $id = e.currentTarget.id
const $contextmenu = $('#contextmenu')
const $name = $(`#${$id}`).find('.name-p').text()
const $left = e.pageX + document.body.scrollLeft - 5
const $top = e.pageY + document.body.scrollTop - 5
$contextmenu.css({
left: $left,
top: $top,
visibility: 'visible'
})
// Action bar
$contextmenu.html('').append(operationHtml)
if (isOne) {
// start run
$('#startRunning').on('click', () => {
const name = store.state.dag.name
const id = router.history.current.params.id
store.dispatch('dag/getStartCheck', { processDefinitionId: id }).then(res => {
this.dag.startRunning({ id: id, name: name }, $name, 'contextmenu')
})
})
}
if (!isTwo) {
// edit node
$('#editNodes').click(ev => {
findComponentDownward(this.dag.$root, 'dag-chart')._createNodes({
id: $id,
type: $(`#${$id}`).attr('data-tasks-type')
})
})
// delete node
$('#removeNodes').click(ev => {
this.removeNodes($id)
})
// copy node
$('#copyNodes').click(res => {
this.copyNodes($id)
})
}
}
}
/**
* Node double click event
*/
JSP.prototype.tasksDblclick = function (e) {
// Untie event
if (this.config.isDblclick) {
const id = $(e.currentTarget.offsetParent).attr('id')
findComponentDownward(this.dag.$root, 'dag-chart')._createNodes({
id: id,
type: $(`#${id}`).attr('data-tasks-type')
})
}
}
/**
* Node click event
*/
JSP.prototype.tasksClick = function (e) {
let $id
const self = this
const $body = $('body')
if (this.config.isClick) {
const $connect = this.selectedElement.connect
$('.w').removeClass('jtk-tasks-active')
$(e.currentTarget).addClass('jtk-tasks-active')
if ($connect) {
setSvgColor($connect, '#2d8cf0')
this.selectedElement.connect = null
}
this.selectedElement.id = $(e.currentTarget).attr('id')
// Unbind copy and paste events
$body.unbind('copy').unbind('paste')
// Copy binding id
$id = self.selectedElement.id
$body.bind({
copy: function () {
$id = self.selectedElement.id
},
paste: function () {
$id && self.copyNodes($id)
}
})
}
}
/**
* Remove binding events
* paste
*/
JSP.prototype.removePaste = function () {
const $body = $('body')
// Unbind copy and paste events
$body.unbind('copy').unbind('paste')
// Remove selected node parameters
this.selectedElement.id = null
// Remove node selection effect
$('.w').removeClass('jtk-tasks-active')
}
/**
* Line click event
*/
JSP.prototype.connectClick = function (e) {
// Set svg color
setSvgColor(e, '#0097e0')
const $id = this.selectedElement.id
if ($id) {
$(`#${$id}`).removeClass('jtk-tasks-active')
this.selectedElement.id = null
}
this.selectedElement.connect = e
}
/**
* toolbarEvent
* @param {Pointer}
*/
JSP.prototype.handleEventPointer = function (is) {
this.setConfig({
isClick: is,
isAttachment: false
})
}
/**
* toolbarEvent
* @param {Line}
*/
JSP.prototype.handleEventLine = function (is) {
const wDom = $('.w')
this.setConfig({
isAttachment: is
})
is ? wDom.addClass('jtk-ep') : wDom.removeClass('jtk-ep')
}
/**
* toolbarEvent
* @param {Remove}
*/
JSP.prototype.handleEventRemove = function () {
const $id = this.selectedElement.id || null
const $connect = this.selectedElement.connect || null
if ($id) {
this.removeNodes(this.selectedElement.id)
} else {
this.removeConnect($connect)
}
// Monitor whether to edit DAG
store.commit('dag/setIsEditDag', true)
}
/**
* Delete node
*/
JSP.prototype.removeNodes = function ($id) {
// Delete node processing(data-targetarr)
_.map(tasksAll(), v => {
const targetarr = v.targetarr.split(',')
if (targetarr.length) {
const newArr = _.filter(targetarr, v1 => v1 !== $id)
$(`#${v.id}`).attr('data-targetarr', newArr.toString())
}
})
// delete node
this.JspInstance.remove($id)
// delete dom
$(`#${$id}`).remove()
// callback onRemoveNodes event
this.options && this.options.onRemoveNodes && this.options.onRemoveNodes($id)
const connects = []
_.map(this.JspInstance.getConnections(), v => {
connects.push({
endPointSourceId: v.sourceId,
endPointTargetId: v.targetId,
label: v._jsPlumb.overlays.label.canvas.innerText
})
})
// Storage line dependence
store.commit('dag/setConnects', connects)
}
/**
* Delete connection
*/
JSP.prototype.removeConnect = function ($connect) {
if (!$connect) {
return
}
// Remove connections and remove node and node dependencies
const targetId = $connect.targetId
const sourceId = $connect.sourceId
let targetarr = rtTargetarrArr(targetId)
if (targetarr.length) {
targetarr = _.filter(targetarr, v => v !== sourceId)
$(`#${targetId}`).attr('data-targetarr', targetarr.toString())
}
if ($(`#${sourceId}`).attr('data-tasks-type') === 'CONDITIONS') {
$(`#${sourceId}`).attr('data-nodenumber', Number($(`#${sourceId}`).attr('data-nodenumber')) - 1)
}
this.JspInstance.deleteConnection($connect)
this.selectedElement = {}
}
/**
* Copy node
*/
JSP.prototype.copyNodes = function ($id) {
let newNodeInfo = _.cloneDeep(_.find(store.state.dag.tasks, v => v.id === $id))
const newNodePors = store.state.dag.locations[$id]
// Unstored nodes do not allow replication
if (!newNodePors) {
return
}
// Generate random id
const newUuId = `${uuid() + uuid()}`
const id = newNodeInfo.id.length > 8 ? newNodeInfo.id.substr(0, 7) : newNodeInfo.id
const name = newNodeInfo.name.length > 8 ? newNodeInfo.name.substr(0, 7) : newNodeInfo.name
// new id
const newId = `${id || ''}-${newUuId}`
// new name
const newName = `${name || ''}-${newUuId}`
// coordinate x
const newX = newNodePors.x + 100
// coordinate y
const newY = newNodePors.y + 40
// Generate template node
$('#canvas').append(rtTasksTpl({
id: newId,
name: newName,
x: newX,
y: newY,
isAttachment: this.config.isAttachment,
taskType: newNodeInfo.type
}))
// Get the generated node
const thisDom = jsPlumb.getSelector('.statemachine-demo .w')
// Copy node information
newNodeInfo = Object.assign(newNodeInfo, {
id: newId,
name: newName
})
// Add new node
store.commit('dag/addTasks', newNodeInfo)
// Add node location information
store.commit('dag/addLocations', {
[newId]: {
name: newName,
targetarr: '',
nodenumber: 0,
x: newX,
y: newY
}
})
// Generating a connection node
this.JspInstance.batch(() => {
this.initNode(thisDom[thisDom.length - 1])
// Add events to nodes
this.tasksEvent(newId)
})
}
/**
* toolbarEvent
* @param {Screen}
*/
JSP.prototype.handleEventScreen = function ({ item, is }) {
let screenOpen = true
if (is) {
item.icon = 'el-icon-aim'
screenOpen = true
} else {
item.icon = 'el-icon-full-screen'
screenOpen = false
}
const $mainLayoutModel = $('.main-layout-model')
if (screenOpen) {
$mainLayoutModel.addClass('dag-screen')
} else {
$mainLayoutModel.removeClass('dag-screen')
}
}
/**
* save task
* @param tasks
* @param locations
* @param connects
*/
JSP.prototype.saveStore = function () {
return new Promise((resolve, reject) => {
const connects = []
const locations = {}
const tasks = []
const is = (id) => {
return !!_.filter(tasksAll(), v => v.id === id).length
}
// task
_.map(_.cloneDeep(store.state.dag.tasks), v => {
if (is(v.id)) {
const preTasks = []
const id = $(`#${v.id}`)
const tar = id.attr('data-targetarr')
const idDep = tar ? id.attr('data-targetarr').split(',') : []
if (idDep.length) {
_.map(idDep, v1 => {
preTasks.push($(`#${v1}`).find('.name-p').text())
})
}
let tasksParam = _.assign(v, {
preTasks: preTasks,
depList: null
})
// Sub-workflow has no retries and interval
if (v.type === 'SUB_PROCESS') {
tasksParam = _.omit(tasksParam, ['maxRetryTimes', 'retryInterval'])
}
tasks.push(tasksParam)
}
})
if (store.state.dag.connects.length === this.JspInstance.getConnections().length) {
_.map(store.state.dag.connects, u => {
connects.push({
endPointSourceId: u.endPointSourceId,
endPointTargetId: u.endPointTargetId,
label: u.label
})
})
} else if (store.state.dag.connects.length > 0 && store.state.dag.connects.length < this.JspInstance.getConnections().length) {
_.map(this.JspInstance.getConnections(), v => {
connects.push({
endPointSourceId: v.sourceId,
endPointTargetId: v.targetId,
label: v._jsPlumb.overlays.label.canvas.innerText
})
})
_.map(store.state.dag.connects, u => {
_.map(connects, v => {
if (u.label && u.endPointSourceId === v.endPointSourceId && u.endPointTargetId === v.endPointTargetId) {
v.label = u.label
}
})
})
} else if (store.state.dag.connects.length === 0) {
_.map(this.JspInstance.getConnections(), v => {
connects.push({
endPointSourceId: v.sourceId,
endPointTargetId: v.targetId,
label: v._jsPlumb.overlays.label.canvas.innerText
})
})
} else if (store.state.dag.connects.length > this.JspInstance.getConnections().length) {
_.map(this.JspInstance.getConnections(), v => {
connects.push({
endPointSourceId: v.sourceId,
endPointTargetId: v.targetId,
label: v._jsPlumb.overlays.label.canvas.innerText
})
})
}
_.map(tasksAll(), v => {
locations[v.id] = {
name: v.name,
targetarr: v.targetarr,
nodenumber: v.nodenumber,
x: v.x,
y: v.y
}
})
// Storage node
store.commit('dag/setTasks', tasks)
// Store coordinate information
store.commit('dag/setLocations', locations)
// Storage line dependence
store.commit('dag/setConnects', connects)
resolve({
connects: connects,
tasks: tasks,
locations: locations
})
})
}
/**
* Event processing
*/
JSP.prototype.handleEvent = function () {
this.JspInstance.bind('beforeDrop', function (info) {
const sourceId = info.sourceId// 出
const targetId = info.targetId// 入
/**
* Recursive search for nodes
*/
let recursiveVal
const recursiveTargetarr = (arr, targetId) => {
for (const i in arr) {
if (arr[i] === targetId) {
recursiveVal = targetId
} else {
recursiveTargetarr(rtTargetarrArr(arr[i]), targetId)
}
}
return recursiveVal
}
// Connection to connected nodes is not allowed
if (_.findIndex(rtTargetarrArr(targetId), v => v === sourceId) !== -1) {
return false
}
// Recursive form to find if the target Targetarr has a sourceId
if (recursiveTargetarr(rtTargetarrArr(sourceId), targetId)) {
return false
}
if ($(`#${sourceId}`).attr('data-tasks-type') === 'CONDITIONS' && $(`#${sourceId}`).attr('data-nodenumber') === 2) {
return false
} else {
$(`#${sourceId}`).attr('data-nodenumber', Number($(`#${sourceId}`).attr('data-nodenumber')) + 1)
}
// Storage node dependency information
saveTargetarr(sourceId, targetId)
// Monitor whether to edit DAG
store.commit('dag/setIsEditDag', true)
return true
})
}
/**
* Backfill data processing
*/
JSP.prototype.jspBackfill = function ({ connects, locations, largeJson }) {
// Backfill nodes
this.jsonHandle({
largeJson: largeJson,
locations: locations
})
const wNodes = jsPlumb.getSelector('.statemachine-demo .w')
// Backfill line
this.JspInstance.batch(() => {
for (let i = 0; i < wNodes.length; i++) {
this.initNode(wNodes[i])
}
_.map(connects, v => {
let sourceId = v.endPointSourceId.split('-')
let targetId = v.endPointTargetId.split('-')
const labels = v.label
if (sourceId.length === 4 && targetId.length === 4) {
sourceId = `${sourceId[0]}-${sourceId[1]}-${sourceId[2]}`
targetId = `${targetId[0]}-${targetId[1]}-${targetId[2]}`
} else {
sourceId = v.endPointSourceId
targetId = v.endPointTargetId
}
if ($(`#${sourceId}`).attr('data-tasks-type') === 'CONDITIONS' && $(`#${sourceId}`).attr('data-successnode') === $(`#${targetId}`).find('.name-p').text()) {
this.JspInstance.connect({
source: sourceId,
target: targetId,
type: 'basic',
paintStyle: { strokeWidth: 2, stroke: '#4caf50' },
HoverPaintStyle: { stroke: '#ccc', strokeWidth: 3 },
overlays: [['Label', { label: labels }]]
})
} else if ($(`#${sourceId}`).attr('data-tasks-type') === 'CONDITIONS' && $(`#${sourceId}`).attr('data-failednode') === $(`#${targetId}`).find('.name-p').text()) {
this.JspInstance.connect({
source: sourceId,
target: targetId,
type: 'basic',
paintStyle: { strokeWidth: 2, stroke: '#252d39' },
HoverPaintStyle: { stroke: '#ccc', strokeWidth: 3 },
overlays: [['Label', { label: labels }]]
})
} else {
this.JspInstance.connect({
source: sourceId,
target: targetId,
type: 'basic',
paintStyle: { strokeWidth: 2, stroke: '#2d8cf0' },
HoverPaintStyle: { stroke: '#ccc', strokeWidth: 3 },
overlays: [['Label', { label: labels }]]
})
}
})
})
jsPlumb.fire('jsPlumbDemoLoaded', this.JspInstance)
// Connection monitoring
this.handleEvent()
// Drag and drop new nodes
this.draggable()
}
export default new JSP()

67
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/plugIn/multiDrag.js

@ -1,67 +0,0 @@
/*
* 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 JSP from './jsPlumbHandle'
/**
* when and only ctrl or meta key pressing, we can select one or more dags to drag
*/
export default function () {
// init
let selectableObjects = []
JSP.JspInstance.clearDragSelection()
let ctrlPress = false
let nodes = null
const $window = $(window)
$window.bind('keydown', function (event) {
if (event.ctrlKey || event.metaKey) {
if (nodes) {
nodes.unbind('mousedown', select)
}
nodes = $('.jtk-draggable')
nodes.bind('mousedown', select)
ctrlPress = true
}
})
$window.bind('keyup', function (event) {
clear()
})
function select (event) {
if (ctrlPress && event.button === 0) {
let index = null
if ((index = selectableObjects.indexOf(this)) !== -1) {
selectableObjects.splice(index, 1)
JSP.JspInstance.removeFromDragSelection(this)
$(this).css('border-color', '')
} else {
selectableObjects.push(this)
JSP.JspInstance.addToDragSelection(this)
$(this).css('border-color', '#4af')
}
}
}
function clear () {
ctrlPress = false
selectableObjects.map(item => {
$(item).css('border-color', '')
})
selectableObjects = []
JSP.JspInstance.clearDragSelection()
}
}

171
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/plugIn/util.js

@ -1,171 +0,0 @@
/*
* 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 _ from 'lodash'
import i18n from '@/module/i18n'
import store from '@/conf/home/store'
/**
* Node, to array
*/
const rtTargetarrArr = (id) => {
const ids = $(`#${id}`).attr('data-targetarr')
return ids ? ids.split(',') : []
}
/**
* Store node id to targetarr
*/
const saveTargetarr = (valId, domId) => {
const $target = $(`#${domId}`)
const targetStr = $target.attr('data-targetarr') ? $target.attr('data-targetarr') + `,${valId}` : `${valId}`
$target.attr('data-targetarr', targetStr)
}
const rtBantpl = () => {
return `<em class="ri-indeterminate-circle-line" data-toggle="tooltip" data-html="true" data-container="body" data-placement="left" title="${i18n.$t('Prohibition execution')}"></em>`
}
/**
* return node html
*/
const rtTasksTpl = ({ id, name, x, y, targetarr, isAttachment, taskType, runFlag, nodenumber, successNode, failedNode }) => {
let tpl = ''
tpl += `<div class="w jtk-draggable jtk-droppable jtk-endpoint-anchor jtk-connected ${isAttachment ? 'jtk-ep' : ''}" data-targetarr="${targetarr || ''}" data-successNode="${successNode || ''}" data-failedNode="${failedNode || ''}" data-nodenumber="${nodenumber || 0}" data-tasks-type="${taskType}" id="${id}" style="left: ${x}px; top: ${y}px;">`
tpl += '<div>'
tpl += '<div class="state-p"></div>'
tpl += `<div class="icos icos-${taskType}"></div>`
tpl += `<span class="name-p">${name}</span>`
tpl += '</div>'
tpl += '<div class="ep"></div>'
tpl += '<div class="ban-p">'
if (runFlag === 'FORBIDDEN') {
tpl += rtBantpl()
}
tpl += '</div>'
tpl += '</div>'
return tpl
}
/**
* Get all tasks nodes
*/
const tasksAll = () => {
const a = []
$('#canvas .w').each(function (idx, elem) {
const e = $(elem)
a.push({
id: e.attr('id'),
name: e.find('.name-p').text(),
targetarr: e.attr('data-targetarr') || '',
nodenumber: e.attr('data-nodenumber'),
x: parseInt(e.css('left'), 10),
y: parseInt(e.css('top'), 10)
})
})
return a
}
/**
* Determine if name is in the current dag map
* rely dom / backfill
*/
const isNameExDag = (name, rely) => {
if (rely === 'dom') {
return _.findIndex(tasksAll(), v => v.name === name) !== -1
} else {
return _.findIndex(store.state.dag.tasks, v => v.name === name) !== -1
}
}
/**
* Change svg line color
*/
const setSvgColor = (e, color) => {
// Traverse clear all colors
$('.jtk-connector').each((i, o) => {
_.map($(o)[0].childNodes, v => {
if ($(v).attr('fill') === '#ccc') {
$(v).attr('fill', '#2d8cf0')
}
if ($(v).attr('fill') === '#4caf50') {
$(v).attr('fill', '#4caf50').attr('stroke', '#4caf50').attr('stroke-width', 2)
$(v).prev().attr('stroke', '#4caf50').attr('stroke-width', 2)
} else if ($(v).attr('fill') === '#252d39') {
$(v).attr('stroke', '#252d39').attr('stroke-width', 2)
$(v).prev().attr('stroke', '#252d39').attr('stroke-width', 2)
} else {
$(v).attr('stroke', '#2d8cf0').attr('stroke-width', 2)
}
})
})
// Add color to the selection
_.map($(e.canvas)[0].childNodes, (v, i) => {
if ($(v).attr('fill') === '#2d8cf0') {
$(v).attr('fill', '#ccc')
}
$(v).attr('stroke', '#ccc')
if ($(v).attr('class')) {
$(v).attr('stroke-width', 2)
}
})
}
/**
* Get all node ids
*/
const allNodesId = () => {
const idArr = []
$('.w').each((i, o) => {
const $obj = $(o)
const $span = $obj.find('.name-p').text()
if ($span) {
idArr.push({
id: $obj.attr('id'),
name: $span
})
}
})
return idArr
}
/**
* compute scalebecause it cant get from jquery directly
* @param el element
* @returns {boolean|number}
*/
const computeScale = function (el) {
const matrix = el.css('transform')
if (!matrix || matrix === 'none') {
return false
}
const values = matrix.split('(')[1].split(')')[0].split(',')
return Math.sqrt(values[0] * values[0] + values[1] * values[1])
}
export {
rtTargetarrArr,
saveTargetarr,
rtTasksTpl,
tasksAll,
isNameExDag,
setSvgColor,
allNodesId,
rtBantpl,
computeScale
}

7
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/udp/_source/selectTenant.vue

@ -61,8 +61,11 @@
}
},
methods: {
_onChange (o) {
this.$emit('tenantSelectEvent', o)
_onChange (id) {
const tenant = this.itemList.find(item => item.id === id)
if (tenant) {
this.$emit('tenantSelectEvent', tenant.tenantCode)
}
}
},
watch: {

16
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/udp/udp.vue

@ -42,7 +42,7 @@
<div class="title" style="padding-top: 6px;">
<span class="text-b">{{$t('select tenant')}}</span>
<form-tenant v-model="tenantId"></form-tenant>
<form-tenant v-model="tenantCode"></form-tenant>
</div>
<div class="title" style="padding-top: 6px;">
<span class="text-b">{{$t('warning of timeout')}}</span>
@ -117,8 +117,8 @@
syncDefine: true,
// Timeout alarm
timeout: 0,
tenantId: -1,
// tenant code
tenantCode: 'default',
// checked Timeout alarm
checkedTimeout: true
}
@ -150,7 +150,7 @@
this.store.commit('dag/setName', _.cloneDeep(this.name))
this.store.commit('dag/setTimeout', _.cloneDeep(this.timeout))
this.store.commit('dag/setTenantId', _.cloneDeep(this.tenantId))
this.store.commit('dag/setTenantCode', _.cloneDeep(this.tenantCode))
this.store.commit('dag/setDesc', _.cloneDeep(this.description))
this.store.commit('dag/setSyncDefine', this.syncDefine)
this.store.commit('dag/setReleaseState', this.releaseState)
@ -257,10 +257,10 @@
this.timeout = dag.timeout || 0
this.checkedTimeout = this.timeout !== 0
this.$nextTick(() => {
if (dag.tenantId > -1) {
this.tenantId = dag.tenantId
} else if (this.store.state.user.userInfo.tenantId) {
this.tenantId = this.store.state.user.userInfo.tenantId
if (dag.tenantCode) {
this.tenantCode = dag.tenantCode
} else {
this.tenantCode = this.store.state.user.userInfo.tenantCode || 'default'
}
})
},

8
dolphinscheduler-ui/src/js/conf/home/pages/dag/definitionDetails.vue

@ -53,7 +53,7 @@
// Promise Get node needs data
Promise.all([
// Node details
this.getProcessDetails(this.$route.params.id),
this.getProcessDetails(this.$route.params.code),
// get process definition
this.getProcessList(),
// get project
@ -69,8 +69,8 @@
this.getTenantList()
]).then((data) => {
let item = data[0]
this.setIsDetails(item.releaseState === 'ONLINE')
this.releaseState = item.releaseState
this.setIsDetails(item.processDefinition.releaseState === 'ONLINE')
this.releaseState = item.processDefinition.releaseState
this.isLoading = false
// Whether to pop up the box?
Affirm.init(this.$root)
@ -82,7 +82,7 @@
* Redraw (refresh operation)
*/
_reset () {
this.getProcessDetails(this.$route.params.id).then(res => {
this.getProcessDetails(this.$route.params.code).then(res => {
let item = res
this.setIsDetails(item.releaseState === 'ONLINE')
this.releaseState = item.releaseState

12
dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/list.vue

@ -25,7 +25,7 @@
<el-popover trigger="hover" placement="top">
<p>{{ scope.row.name }}</p>
<div slot="reference" class="name-wrapper">
<router-link :to="{ path: `/projects/${projectId}/definition/list/${scope.row.id}` }" tag="a" class="links">
<router-link :to="{ path: `/projects/${projectCode}/definition/list/${scope.row.code}` }" tag="a" class="links">
<span class="ellipsis">{{scope.row.name}}</span>
</router-link>
</div>
@ -256,7 +256,7 @@
}
// remove one
this.deleteDefinition({
processDefinitionId: item.id
code: item.code
}).then(res => {
this._onUpdate()
this.$message.success(res.msg)
@ -268,14 +268,14 @@
* edit
*/
_edit (item) {
this.$router.push({ path: `/projects/${this.projectId}/definition/list/${item.id}` })
this.$router.push({ path: `/projects/${this.projectCode}/definition/list/${item.code}` })
},
/**
* Offline
*/
_downline (item) {
this._upProcessState({
processId: item.id,
...item,
releaseState: 'OFFLINE'
})
},
@ -284,7 +284,7 @@
*/
_poponline (item) {
this._upProcessState({
processId: item.id,
...item,
releaseState: 'ONLINE'
})
},
@ -520,7 +520,7 @@
mounted () {
},
computed: {
...mapState('dag', ['projectId'])
...mapState('dag', ['projectId', 'projectCode'])
},
components: { mVersions, mStart, mTiming, mRelatedItems }
}

4
dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/start.vue

@ -265,7 +265,7 @@
}
}
let param = {
processDefinitionId: this.startData.id,
processDefinitionCode: this.startData.code,
scheduleTime: this.scheduleTime.length && this.scheduleTime.join(',') || '',
failureStrategy: this.failureStrategy,
warningType: this.warningType,
@ -319,7 +319,7 @@
}
},
_getGlobalParams () {
this.store.dispatch('dag/getProcessDetails', this.startData.id).then(res => {
this.store.dispatch('dag/getProcessDetails', this.startData.code).then(res => {
this.definitionGlobalParams = _.cloneDeep(this.store.state.dag.globalParams)
this.udpList = _.cloneDeep(this.store.state.dag.globalParams)
})

28
dolphinscheduler-ui/src/js/conf/home/router/index.js

@ -92,7 +92,7 @@ const router = new Router({
}
},
{
path: '/projects/:projectId/kinship',
path: '/projects/:projectCode/kinship',
name: 'projects-kinship',
component: resolve => require(['../pages/projects/pages/kinship/index'], resolve),
meta: {
@ -101,7 +101,7 @@ const router = new Router({
}
},
{
path: '/projects/:projectId/definition',
path: '/projects/:projectCode/definition',
name: 'definition',
component: resolve => require(['../pages/projects/pages/definition/index'], resolve),
meta: {
@ -113,7 +113,7 @@ const router = new Router({
},
children: [
{
path: '/projects/:projectId/definition/list',
path: '/projects/:projectCode/definition/list',
name: 'projects-definition-list',
component: resolve => require(['../pages/projects/pages/definition/pages/list/index'], resolve),
meta: {
@ -122,7 +122,7 @@ const router = new Router({
}
},
{
path: '/projects/:projectId/definition/list/:id',
path: '/projects/:projectCode/definition/list/:code',
name: 'projects-definition-details',
component: resolve => require(['../pages/projects/pages/definition/pages/details/index'], resolve),
meta: {
@ -131,7 +131,7 @@ const router = new Router({
}
},
{
path: '/projects/:projectId/definition/create',
path: '/projects/:projectCode/definition/create',
name: 'definition-create',
component: resolve => require(['../pages/projects/pages/definition/pages/create/index'], resolve),
meta: {
@ -139,7 +139,7 @@ const router = new Router({
}
},
{
path: '/projects/:projectId/definition/tree/:id',
path: '/projects/:projectCode/definition/tree/:id',
name: 'definition-tree-view-index',
component: resolve => require(['../pages/projects/pages/definition/pages/tree/index'], resolve),
meta: {
@ -148,7 +148,7 @@ const router = new Router({
}
},
{
path: '/projects/:projectId/definition/list/timing/:code',
path: '/projects/:projectCode/definition/list/timing/:code',
name: 'definition-timing-details',
component: resolve => require(['../pages/projects/pages/definition/timing/index'], resolve),
meta: {
@ -159,7 +159,7 @@ const router = new Router({
]
},
{
path: '/projects/:projectId/instance',
path: '/projects/:projectCode/instance',
name: 'instance',
component: resolve => require(['../pages/projects/pages/instance/index'], resolve),
meta: {
@ -170,7 +170,7 @@ const router = new Router({
},
children: [
{
path: '/projects/:projectId/instance/list',
path: '/projects/:projectCode/instance/list',
name: 'projects-instance-list',
component: resolve => require(['../pages/projects/pages/instance/pages/list/index'], resolve),
meta: {
@ -179,7 +179,7 @@ const router = new Router({
}
},
{
path: '/projects/:projectId/instance/list/:id',
path: '/projects/:projectCode/instance/list/:id',
name: 'projects-instance-details',
component: resolve => require(['../pages/projects/pages/instance/pages/details/index'], resolve),
meta: {
@ -188,7 +188,7 @@ const router = new Router({
}
},
{
path: '/projects/:projectId/instance/gantt/:id',
path: '/projects/:projectCode/instance/gantt/:id',
name: 'instance-gantt-index',
component: resolve => require(['../pages/projects/pages/instance/pages/gantt/index'], resolve),
meta: {
@ -199,7 +199,7 @@ const router = new Router({
]
},
{
path: '/projects/:projectId/task-instance',
path: '/projects/:projectCode/task-instance',
name: 'task-instance',
component: resolve => require(['../pages/projects/pages/taskInstance'], resolve),
meta: {
@ -209,7 +209,7 @@ const router = new Router({
},
{
path: '/projects/:projectId/task-record',
path: '/projects/:projectCode/task-record',
name: 'task-record',
component: resolve => require(['../pages/projects/pages/taskRecord'], resolve),
meta: {
@ -218,7 +218,7 @@ const router = new Router({
}
},
{
path: '/projects/:projectId/history-task-record',
path: '/projects/:projectCode/history-task-record',
name: 'history-task-record',
component: resolve => require(['../pages/projects/pages/historyTaskRecord'], resolve),
meta: {

221
dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js

@ -19,32 +19,13 @@ import _ from 'lodash'
import io from '@/module/io'
import { tasksState } from '@/conf/home/pages/dag/_source/config'
// delete 'definitionList' from tasks
const deleteDefinitionList = (tasks) => {
const newTasks = []
tasks.forEach(item => {
const newItem = Object.assign({}, item)
if (newItem.dependence && newItem.dependence.dependTaskList) {
newItem.dependence.dependTaskList.forEach(dependTaskItem => {
if (dependTaskItem.dependItemList) {
dependTaskItem.dependItemList.forEach(dependItem => {
Reflect.deleteProperty(dependItem, 'definitionList')
})
}
})
}
newTasks.push(newItem)
})
return newTasks
}
export default {
/**
* Task status acquisition
*/
getTaskState ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/instance/task-list-by-process-id`, {
io.get(`projects/${state.projectCode}/instance/task-list-by-process-id`, {
processInstanceId: payload
}, res => {
const arr = _.map(res.data.taskList, v => {
@ -69,8 +50,9 @@ export default {
*/
editProcessState ({ state }, payload) {
return new Promise((resolve, reject) => {
io.post(`projects/${state.projectName}/process/release`, {
processId: payload.processId,
io.post(`projects/${state.projectCode}/process/release`, {
code: payload.code,
name: payload.name,
releaseState: payload.releaseState
}, res => {
resolve(res)
@ -85,7 +67,7 @@ export default {
*/
getProcessDefinitionVersionsPage ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/process/versions`, payload, res => {
io.get(`projects/${state.projectCode}/process/versions`, payload, res => {
resolve(res)
}).catch(e => {
reject(e)
@ -98,7 +80,7 @@ export default {
*/
switchProcessDefinitionVersion ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/process/version/switch`, payload, res => {
io.get(`projects/${state.projectCode}/process/version/switch`, payload, res => {
resolve(res)
}).catch(e => {
reject(e)
@ -111,7 +93,7 @@ export default {
*/
deleteProcessDefinitionVersion ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/process/version/delete`, payload, res => {
io.get(`projects/${state.projectCode}/process/version/delete`, payload, res => {
resolve(res)
}).catch(e => {
reject(e)
@ -139,7 +121,7 @@ export default {
*/
verifDAGName ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/process/verify-name`, {
io.get(`projects/${state.projectCode}/process/verify-name`, {
name: payload
}, res => {
state.name = payload
@ -155,36 +137,45 @@ export default {
*/
getProcessDetails ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/process/select-by-id`, {
processId: payload
io.get(`projects/${state.projectCode}/process/select-by-code`, {
code: payload
}, res => {
// process definition code
state.code = res.data.code
state.code = res.data.processDefinition.code
// version
state.version = res.data.version
state.version = res.data.processDefinition.version
// name
state.name = res.data.name
state.name = res.data.processDefinition.name
// description
state.description = res.data.description
// connects
state.connects = JSON.parse(res.data.connects)
state.description = res.data.processDefinition.description
// taskRelationJson
state.connects = res.data.processTaskRelationList
// locations
state.locations = JSON.parse(res.data.locations)
// Process definition
const processDefinitionJson = JSON.parse(res.data.processDefinitionJson)
// tasks info
state.tasks = processDefinitionJson.tasks
// tasks cache
state.cacheTasks = {}
processDefinitionJson.tasks.forEach(v => {
state.cacheTasks[v.id] = v
})
state.locations = JSON.parse(res.data.processDefinition.locations)
// global params
state.globalParams = processDefinitionJson.globalParams
state.globalParams = res.data.processDefinition.globalParamList
// timeout
state.timeout = processDefinitionJson.timeout
state.tenantId = processDefinitionJson.tenantId
state.timeout = res.data.processDefinition.timeout
// tenantId
state.tenantCode = res.data.processDefinition.tenantCode
// tasks info
state.tasks = res.data.taskDefinitionList.map(task => _.pick(task, [
'code',
'name',
'version',
'description',
'delayTime',
'taskType',
'taskParams',
'flag',
'taskPriority',
'workerGroup',
'failRetryTimes',
'failRetryInterval',
'timeoutFlag',
'timeoutNotifyStrategy',
'timeout'
]))
resolve(res.data)
}).catch(res => {
reject(res)
@ -197,7 +188,7 @@ export default {
*/
copyProcess ({ state }, payload) {
return new Promise((resolve, reject) => {
io.post(`projects/${state.projectName}/process/copy`, {
io.post(`projects/${state.projectCode}/process/copy`, {
processDefinitionIds: payload.processDefinitionIds,
targetProjectId: payload.targetProjectId
}, res => {
@ -213,7 +204,7 @@ export default {
*/
moveProcess ({ state }, payload) {
return new Promise((resolve, reject) => {
io.post(`projects/${state.projectName}/process/move`, {
io.post(`projects/${state.project}/process/move`, {
processDefinitionIds: payload.processDefinitionIds,
targetProjectId: payload.targetProjectId
}, res => {
@ -242,37 +233,46 @@ export default {
*/
getInstancedetail ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/instance/select-by-id`, {
io.get(`projects/${state.projectCode}/instance/select-by-id`, {
processInstanceId: payload
}, res => {
const { processDefinition, processTaskRelationList, taskDefinitionList } = res.data.dagData
// code
state.code = res.data.processDefinitionCode
state.code = processDefinition.code
// version
state.version = res.data.processDefinitionVersion
state.version = processDefinition.version
// name
state.name = res.data.name
// desc
state.description = res.data.description
state.description = processDefinition.description
// connects
state.connects = JSON.parse(res.data.connects)
state.connects = processTaskRelationList
// locations
state.locations = JSON.parse(res.data.locations)
// process instance
const processInstanceJson = JSON.parse(res.data.processInstanceJson)
// tasks info
state.tasks = processInstanceJson.tasks
// tasks cache
state.cacheTasks = {}
processInstanceJson.tasks.forEach(v => {
state.cacheTasks[v.id] = v
})
state.locations = JSON.parse(processDefinition.locations)
// global params
state.globalParams = processInstanceJson.globalParams
state.globalParams = processDefinition.globalParamList
// timeout
state.timeout = processInstanceJson.timeout
state.tenantId = processInstanceJson.tenantId
state.timeout = processDefinition.timeout
// tenantCode
state.tenantCode = res.data.tenantCode
// tasks info
state.tasks = taskDefinitionList.map(task => _.pick(task, [
'code',
'name',
'version',
'description',
'delayTime',
'taskType',
'taskParams',
'flag',
'taskPriority',
'workerGroup',
'failRetryTimes',
'failRetryInterval',
'timeoutFlag',
'timeoutNotifyStrategy',
'timeout'
]))
// startup parameters
state.startup = _.assign(state.startup, _.pick(res.data, ['commandType', 'failureStrategy', 'processInstancePriority', 'workerGroup', 'warningType', 'warningGroupId', 'receivers', 'receiversCc']))
state.startup.commandParam = JSON.parse(res.data.commandParam)
@ -288,18 +288,15 @@ export default {
*/
saveDAGchart ({ state }, payload) {
return new Promise((resolve, reject) => {
const data = {
globalParams: state.globalParams,
tasks: deleteDefinitionList(state.tasks),
tenantId: state.tenantId,
timeout: state.timeout
}
io.post(`projects/${state.projectName}/process/save`, {
processDefinitionJson: JSON.stringify(data),
io.post(`projects/${state.projectCode}/process/save`, {
locations: JSON.stringify(state.locations),
name: _.trim(state.name),
taskDefinitionJson: JSON.stringify(state.tasks),
taskRelationJson: JSON.stringify(state.connects),
tenantCode: state.tenantCode,
description: _.trim(state.description),
locations: JSON.stringify(state.locations),
connects: JSON.stringify(state.connects)
globalParams: JSON.stringify(state.globalParams),
timeout: state.timeout
}, res => {
resolve(res)
}).catch(e => {
@ -312,20 +309,17 @@ export default {
*/
updateDefinition ({ state }, payload) {
return new Promise((resolve, reject) => {
const data = {
globalParams: state.globalParams,
tasks: deleteDefinitionList(state.tasks),
tenantId: state.tenantId,
timeout: state.timeout
}
io.post(`projects/${state.projectName}/process/update`, {
processDefinitionJson: JSON.stringify(data),
io.post(`projects/${state.projectCode}/process/update`, {
locations: JSON.stringify(state.locations),
connects: JSON.stringify(state.connects),
name: _.trim(state.name),
taskDefinitionJson: JSON.stringify(state.tasks),
taskRelationJson: JSON.stringify(state.connects),
tenantCode: state.tenantCode,
description: _.trim(state.description),
id: payload,
releaseState: state.releaseState
globalParams: JSON.stringify(state.globalParams),
timeout: state.timeout,
releaseState: state.releaseState,
code: payload
}, res => {
resolve(res)
state.isEditDag = false
@ -345,7 +339,7 @@ export default {
tenantId: state.tenantId,
timeout: state.timeout
}
io.post(`projects/${state.projectName}/instance/update`, {
io.post(`projects/${state.projectCode}/instance/update`, {
processInstanceJson: JSON.stringify(data),
locations: JSON.stringify(state.locations),
connects: JSON.stringify(state.connects),
@ -368,7 +362,7 @@ export default {
resolve()
return
}
io.get(`projects/${state.projectName}/process/list`, payload, res => {
io.get(`projects/${state.projectCode}/process/list`, payload, res => {
state.processListS = res.data
resolve(res.data)
}).catch(res => {
@ -381,7 +375,7 @@ export default {
*/
getProcessListP ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/process/list-paging`, payload, res => {
io.get(`projects/${state.projectCode}/process/list-paging`, payload, res => {
resolve(res.data)
}).catch(res => {
reject(res)
@ -410,7 +404,7 @@ export default {
*/
getProcessByProjectId ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/process/queryProcessDefinitionAllByProjectId`, payload, res => {
io.get(`projects/${state.projectCode}/process/queryProcessDefinitionAllByProjectId`, payload, res => {
resolve(res.data)
}).catch(res => {
reject(res)
@ -474,7 +468,7 @@ export default {
*/
getProcessInstance ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/instance/list-paging`, payload, res => {
io.get(`projects/${state.projectCode}/instance/list-paging`, payload, res => {
state.instanceListS = res.data.totalList
resolve(res.data)
}).catch(res => {
@ -531,7 +525,7 @@ export default {
*/
getSubProcessId ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/instance/select-sub-process`, payload, res => {
io.get(`projects/${state.projectCode}/instance/select-sub-process`, payload, res => {
resolve(res)
}).catch(e => {
reject(e)
@ -628,7 +622,7 @@ export default {
*/
deleteInstance ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/instance/delete`, payload, res => {
io.get(`projects/${state.projectCode}/instance/delete`, payload, res => {
resolve(res)
}).catch(e => {
reject(e)
@ -640,7 +634,7 @@ export default {
*/
batchDeleteInstance ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/instance/batch-delete`, payload, res => {
io.get(`projects/${state.projectCode}/instance/batch-delete`, payload, res => {
resolve(res)
}).catch(e => {
reject(e)
@ -652,7 +646,7 @@ export default {
*/
deleteDefinition ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/process/delete`, payload, res => {
io.get(`projects/${state.projectCode}/process/delete`, payload, res => {
resolve(res)
}).catch(e => {
reject(e)
@ -664,7 +658,7 @@ export default {
*/
batchDeleteDefinition ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/process/batch-delete`, payload, res => {
io.get(`projects/${state.projectCode}/process/batch-delete`, payload, res => {
resolve(res)
}).catch(e => {
reject(e)
@ -696,7 +690,7 @@ export default {
}
}
io.get(`projects/${state.projectName}/process/export`, { processDefinitionIds: payload.processDefinitionIds }, res => {
io.get(`projects/${state.projectCode}/process/export`, { processDefinitionIds: payload.processDefinitionIds }, res => {
downloadBlob(res, payload.fileName)
}, e => {
@ -710,7 +704,7 @@ export default {
*/
getViewvariables ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/instance/view-variables`, payload, res => {
io.get(`projects/${state.projectCode}/instance/view-variables`, payload, res => {
resolve(res)
}).catch(e => {
reject(e)
@ -734,7 +728,7 @@ export default {
*/
getTaskInstanceList ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/task-instance/list-paging`, payload, res => {
io.get(`projects/${state.projectCode}/task-instance/list-paging`, payload, res => {
resolve(res.data)
}).catch(e => {
reject(e)
@ -746,7 +740,7 @@ export default {
*/
forceTaskSuccess ({ state }, payload) {
return new Promise((resolve, reject) => {
io.post(`projects/${state.projectName}/task-instance/force-success`, payload, res => {
io.post(`projects/${state.projectCode}/task-instance/force-success`, payload, res => {
resolve(res)
}).catch(e => {
reject(e)
@ -782,7 +776,7 @@ export default {
*/
getViewTree ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/process/view-tree`, payload, res => {
io.get(`projects/${state.projectCode}/process/view-tree`, payload, res => {
resolve(res.data)
}).catch(e => {
reject(e)
@ -794,7 +788,7 @@ export default {
*/
getViewGantt ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/instance/view-gantt`, payload, res => {
io.get(`projects/${state.projectCode}/instance/view-gantt`, payload, res => {
resolve(res.data)
}).catch(e => {
reject(e)
@ -806,7 +800,7 @@ export default {
*/
getProcessTasksList ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/process/gen-task-list`, payload, res => {
io.get(`projects/${state.projectCode}/process/gen-task-list`, payload, res => {
resolve(res.data)
}).catch(e => {
reject(e)
@ -815,7 +809,7 @@ export default {
},
getTaskListDefIdAll ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectName}/process/get-task-list`, payload, res => {
io.get(`projects/${state.projectCode}/process/get-task-list`, payload, res => {
resolve(res.data)
}).catch(e => {
reject(e)
@ -842,5 +836,14 @@ export default {
reject(e)
})
})
},
genTaskCodeList ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectCode}/task/gen-task-code-list`, payload, res => {
resolve(res.data)
}).catch(e => {
reject(e)
})
})
}
}

54
dolphinscheduler-ui/src/js/conf/home/store/dag/mutations.js

@ -64,10 +64,10 @@ export default {
state.timeout = payload
},
/**
* set tenantId
* set tenantCode
*/
setTenantId (state, payload) {
state.tenantId = payload
setTenantCode (state, payload) {
state.tenantCode = payload
},
/**
* set global params
@ -108,13 +108,12 @@ export default {
* reset params
*/
resetParams (state, payload) {
$('#canvas').html('')
state.globalParams = (payload && payload.globalParams) || []
state.tasks = (payload && payload.tasks) || []
state.name = (payload && payload.name) || ''
state.description = (payload && payload.description) || ''
state.timeout = (payload && payload.timeout) || 0
state.tenantId = (payload && payload.tenantId) || -1
state.tenantCode = (payload && payload.tenantCode) || ''
state.processListS = (payload && payload.processListS) || []
state.resourcesListS = (payload && payload.resourcesListS) || []
state.resourcesListJar = (payload && payload.resourcesListJar) || []
@ -126,48 +125,23 @@ export default {
},
/**
* add task
* object {}
* @param {Task} task
*/
addTasks (state, payload) {
const i = _.findIndex(state.tasks, v => v.id === payload.id)
addTask (state, task) {
const i = _.findIndex(state.tasks, v => v.code === task.code)
if (i !== -1) {
state.tasks[i] = Object.assign(state.tasks[i], {}, payload)
state.tasks[i] = Object.assign(state.tasks[i], {}, task)
} else {
state.tasks.push(payload)
state.tasks.push(task)
}
if (state.cacheTasks[payload.id]) {
state.cacheTasks[payload.id] = Object.assign(state.cacheTasks[payload.id], {}, payload)
} else {
state.cacheTasks[payload.id] = payload
}
const dom = $(`#${payload.id}`)
state.locations[payload.id] = _.assign(state.locations[payload.id], {
name: dom.find('.name-p').text(),
targetarr: dom.attr('data-targetarr'),
nodenumber: dom.attr('data-nodenumber'),
x: parseInt(dom.css('left'), 10),
y: parseInt(dom.css('top'), 10)
})
},
addConnects (state, payload) {
state.connects = _.map(state.connects, v => {
if (v.endPointSourceId === payload.sourceId && v.endPointTargetId === payload.targetId) {
v.label = payload.labelName
}
return v
})
},
/**
* Cache the input
* @param state
* @param payload
* remove task
* @param {object} state
* @param {string} code
*/
cacheTasks (state, payload) {
if (state.cacheTasks[payload.id]) {
state.cacheTasks[payload.id] = Object.assign(state.cacheTasks[payload.id], {}, payload)
} else {
state.cacheTasks[payload.id] = payload
}
removeTask (state, code) {
state.tasks = state.tasks.filter(task => task.code !== code)
},
resetLocalParam (state, payload) {
const tasks = state.tasks

6
dolphinscheduler-ui/src/js/conf/home/store/dag/state.js

@ -39,11 +39,11 @@ export default {
cacheTasks: {},
// Timeout alarm
timeout: 0,
// tenant id
tenantId: -1,
// tenant code
tenantCode: '',
// Node location information
locations: {},
// Node-to-node connection
// Node relations
connects: [],
// Running sign
runFlag: '',

Loading…
Cancel
Save