@kestra-io/ui-libs 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -2
- package/src/assets/icons/SplitCellsHorizontal.vue +24 -0
- package/src/assets/icons/SplitCellsVertical.vue +24 -0
- package/src/components/index.js +4 -0
- package/src/components/misc/ExecutionInformations.vue +4 -3
- package/src/components/misc/TaskIcon.vue +32 -3
- package/src/components/nodes/BasicNode.vue +71 -24
- package/src/components/nodes/ClusterNode.vue +28 -2
- package/src/components/nodes/DependenciesNode.vue +38 -3
- package/src/components/nodes/EdgeNode.vue +25 -0
- package/src/components/nodes/TaskNode.vue +41 -15
- package/src/components/nodes/TriggerNode.vue +27 -2
- package/src/components/topology/Topology.vue +355 -0
- package/src/index.js +6 -0
- package/src/scss/_variables.scss +68 -11
- package/src/scss/app.scss +2 -0
- package/src/scss/bootstrap-dark.scss +12 -0
- package/src/scss/bootstrap.scss +10 -0
- package/src/utils/VueFlowUtils.js +445 -0
- package/src/utils/YamlUtils.js +78 -1
- package/src/utils/constants.js +17 -0
- package/src/utils/global.js +5 -5
- package/src/utils/state.js +3 -3
- package/src/utils/GraphUtils.js +0 -390
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import {MarkerType, Position, useVueFlow} from "@vue-flow/core"
|
|
2
|
+
import dagre from "dagre";
|
|
3
|
+
import {YamlUtils} from "../index.js";
|
|
4
|
+
import Utils from "./Utils.js";
|
|
5
|
+
import {CLUSTER_UID_SEPARATOR, NODE_SIZES} from "./constants.js";
|
|
6
|
+
|
|
7
|
+
export default class VueFlowUtils {
|
|
8
|
+
|
|
9
|
+
static predecessorsEdge(vueFlowId, nodeUid) {
|
|
10
|
+
const {getEdges} = useVueFlow({id: vueFlowId});
|
|
11
|
+
|
|
12
|
+
let nodes = [];
|
|
13
|
+
|
|
14
|
+
for (const edge of getEdges.value) {
|
|
15
|
+
if (edge.target === nodeUid) {
|
|
16
|
+
nodes.push(edge)
|
|
17
|
+
let recursiveEdge = this.predecessorsEdge(vueFlowId, edge.source);
|
|
18
|
+
if (recursiveEdge.length > 0) {
|
|
19
|
+
nodes.push(...recursiveEdge);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return nodes;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static successorsEdge(vueFlowId, nodeUid) {
|
|
28
|
+
const {getEdges} = useVueFlow({id: vueFlowId});
|
|
29
|
+
|
|
30
|
+
let nodes = [];
|
|
31
|
+
|
|
32
|
+
for (const edge of getEdges.value) {
|
|
33
|
+
if (edge.source === nodeUid) {
|
|
34
|
+
nodes.push(edge)
|
|
35
|
+
let recursiveEdge = this.successorsEdge(vueFlowId, edge.target);
|
|
36
|
+
if (recursiveEdge.length > 0) {
|
|
37
|
+
nodes.push(...recursiveEdge);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return nodes;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static predecessorsNode(vueFlowId, nodeUid) {
|
|
46
|
+
const {getEdges, findNode} = useVueFlow({id: vueFlowId});
|
|
47
|
+
|
|
48
|
+
let nodes = [findNode(nodeUid)];
|
|
49
|
+
|
|
50
|
+
for (const edge of getEdges.value) {
|
|
51
|
+
if (edge.target === nodeUid) {
|
|
52
|
+
nodes.push(edge.sourceNode)
|
|
53
|
+
let recursiveEdge = this.predecessorsNode(vueFlowId, edge.source);
|
|
54
|
+
if (recursiveEdge.length > 0) {
|
|
55
|
+
nodes.push(...recursiveEdge);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return nodes;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static successorsNode(vueFlowId, nodeUid) {
|
|
64
|
+
const {getEdges, findNode} = useVueFlow({id: vueFlowId});
|
|
65
|
+
|
|
66
|
+
let nodes = [findNode(nodeUid)];
|
|
67
|
+
|
|
68
|
+
for (const edge of getEdges.value) {
|
|
69
|
+
if (edge.source === nodeUid) {
|
|
70
|
+
nodes.push(edge.targetNode)
|
|
71
|
+
let recursiveEdge = this.successorsNode(vueFlowId, edge.target);
|
|
72
|
+
if (recursiveEdge.length > 0) {
|
|
73
|
+
nodes.push(...recursiveEdge);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return nodes;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static linkedElements(vueFlowId, nodeUid) {
|
|
82
|
+
return ([
|
|
83
|
+
...this.predecessorsEdge(vueFlowId, nodeUid),
|
|
84
|
+
...this.predecessorsNode(vueFlowId, nodeUid),
|
|
85
|
+
...this.successorsEdge(vueFlowId, nodeUid),
|
|
86
|
+
...this.successorsNode(vueFlowId, nodeUid),
|
|
87
|
+
])
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static generateDagreGraph(flowGraph, hiddenNodes, isHorizontal, clusterCollapseToNode, edgeReplacer, collapsed, clusterToNode) {
|
|
91
|
+
const dagreGraph = new dagre.graphlib.Graph({compound: true})
|
|
92
|
+
dagreGraph.setDefaultEdgeLabel(() => ({}))
|
|
93
|
+
dagreGraph.setGraph({rankdir: isHorizontal ? "LR" : "TB"})
|
|
94
|
+
|
|
95
|
+
for (const node of flowGraph.nodes) {
|
|
96
|
+
if (!hiddenNodes.includes(node.uid)) {
|
|
97
|
+
dagreGraph.setNode(node.uid, {
|
|
98
|
+
width: this.getNodeWidth(node),
|
|
99
|
+
height: this.getNodeHeight(node)
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (let cluster of (flowGraph.clusters || [])) {
|
|
105
|
+
if (clusterCollapseToNode.includes(cluster.cluster.uid) && collapsed.includes(cluster.cluster.uid)) {
|
|
106
|
+
const node = {uid: cluster.cluster.uid, type: "collapsedcluster"};
|
|
107
|
+
dagreGraph.setNode(cluster.cluster.uid, {
|
|
108
|
+
width: this.getNodeWidth(node),
|
|
109
|
+
height: this.getNodeHeight(node)
|
|
110
|
+
});
|
|
111
|
+
clusterToNode.push(node)
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
if (!edgeReplacer[cluster.cluster.uid]) {
|
|
115
|
+
dagreGraph.setNode(cluster.cluster.uid, {clusterLabelPos: "top"});
|
|
116
|
+
|
|
117
|
+
for (let node of (cluster.nodes || [])) {
|
|
118
|
+
if (!hiddenNodes.includes(node)) {
|
|
119
|
+
dagreGraph.setParent(node, cluster.cluster.uid)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (cluster.parents) {
|
|
124
|
+
const nodeChild = edgeReplacer[cluster.cluster.uid] ? edgeReplacer[cluster.cluster.uid] : cluster.cluster.uid
|
|
125
|
+
if (!hiddenNodes.includes(nodeChild)) {
|
|
126
|
+
dagreGraph.setParent(nodeChild, cluster.parents[cluster.parents.length - 1]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const edge of (flowGraph.edges || [])) {
|
|
132
|
+
const newEdge = this.replaceIfCollapsed(edge.source, edge.target, edgeReplacer, hiddenNodes);
|
|
133
|
+
if (newEdge) {
|
|
134
|
+
dagreGraph.setEdge(newEdge.source, newEdge.target)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
dagre.layout(dagreGraph)
|
|
139
|
+
return dagreGraph;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static getNodePosition(n, parent) {
|
|
143
|
+
const position = {x: n.x - n.width / 2, y: n.y - n.height / 2};
|
|
144
|
+
|
|
145
|
+
// bug with parent node,
|
|
146
|
+
if (parent) {
|
|
147
|
+
const parentPosition = this.getNodePosition(parent);
|
|
148
|
+
position.x = position.x - parentPosition.x;
|
|
149
|
+
position.y = position.y - parentPosition.y;
|
|
150
|
+
}
|
|
151
|
+
return position;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static getNodeWidth(node) {
|
|
155
|
+
return this.isTaskNode(node) || this.isTriggerNode(node) ? NODE_SIZES.TASK_WIDTH : this.isCollapsedCluster(node) ? NODE_SIZES.COLLAPSED_CLUSTER_WIDTH : NODE_SIZES.DOT_WIDTH;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
static getNodeHeight(node) {
|
|
159
|
+
return this.isTaskNode(node) || this.isTriggerNode(node) ? NODE_SIZES.TASK_HEIGHT : this.isCollapsedCluster(node) ? NODE_SIZES.COLLAPSED_CLUSTER_HEIGHT : NODE_SIZES.DOT_HEIGHT;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static isTaskNode(node) {
|
|
163
|
+
return node.task !== undefined && (node.type === "io.kestra.core.models.hierarchies.GraphTask")
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
static isTriggerNode(node) {
|
|
167
|
+
return node.trigger !== undefined && (node.type === "io.kestra.core.models.hierarchies.GraphTrigger");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
static isCollapsedCluster(node) {
|
|
171
|
+
return node.type === "collapsedcluster";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
static replaceIfCollapsed(source, target, edgeReplacer, hiddenNodes) {
|
|
175
|
+
const newSource = edgeReplacer[source] ? edgeReplacer[source] : source
|
|
176
|
+
const newTarget = edgeReplacer[target] ? edgeReplacer[target] : target
|
|
177
|
+
|
|
178
|
+
if (newSource === newTarget || (hiddenNodes.includes(newSource) || hiddenNodes.includes(newTarget))) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
return {target: newTarget, source: newSource}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
static cleanGraph(vueflowId) {
|
|
185
|
+
const {
|
|
186
|
+
getEdges,
|
|
187
|
+
getNodes,
|
|
188
|
+
getElements,
|
|
189
|
+
removeEdges,
|
|
190
|
+
removeNodes,
|
|
191
|
+
removeSelectedElements
|
|
192
|
+
} = useVueFlow({id: vueflowId});
|
|
193
|
+
removeEdges(getEdges.value)
|
|
194
|
+
removeNodes(getNodes.value)
|
|
195
|
+
removeSelectedElements(getElements.value)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
static flowHaveTasks(source) {
|
|
199
|
+
return source ? YamlUtils.flowHaveTasks(source) : false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
static linkDatas(task, execution) {
|
|
203
|
+
const data = {id: task.flowId, namespace: task.namespace}
|
|
204
|
+
if (execution) {
|
|
205
|
+
const taskrun = execution.taskRunList.find(r => r.taskId === task.id && r.outputs.executionId)
|
|
206
|
+
if (taskrun) {
|
|
207
|
+
data.executionId = taskrun?.outputs.executionId
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return data
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
static nodeColor(node, collapsed, flowSource) {
|
|
214
|
+
if (this.isTaskNode(node)) {
|
|
215
|
+
if (collapsed.includes(node.uid)) {
|
|
216
|
+
return "blue";
|
|
217
|
+
}
|
|
218
|
+
if (YamlUtils.isTaskError(flowSource, node.task.id)) {
|
|
219
|
+
return "danger"
|
|
220
|
+
}
|
|
221
|
+
if (node.task.type === "io.kestra.core.tasks.flows.Flow") {
|
|
222
|
+
return "primary"
|
|
223
|
+
}
|
|
224
|
+
} else if (this.isTriggerNode(node) || this.isCollapsedCluster(node)) {
|
|
225
|
+
return "success";
|
|
226
|
+
}
|
|
227
|
+
return "default"
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
static getClusterTaskIdWithEndNodeUid (nodeUid, flowGraph) {
|
|
231
|
+
const cluster = flowGraph.clusters.find(cluster => cluster.end === nodeUid);
|
|
232
|
+
if (cluster) {
|
|
233
|
+
return Utils.splitFirst(cluster.cluster.uid, CLUSTER_UID_SEPARATOR);
|
|
234
|
+
}
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
static haveAdd(edge, flowSource, flowGraph, flowables) {
|
|
239
|
+
if (edge.target === YamlUtils.getFirstTask(flowSource)) {
|
|
240
|
+
return [YamlUtils.getNextTaskId(edge.target, flowSource, flowGraph), "before"];
|
|
241
|
+
}
|
|
242
|
+
if (YamlUtils.isTaskParallel(edge.target, flowSource) || YamlUtils.isTrigger(flowSource, edge.target) || YamlUtils.isTrigger(flowSource, edge.source)) {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
if (YamlUtils.extractTask(flowSource, edge.source) && YamlUtils.extractTask(flowSource, edge.target)) {
|
|
246
|
+
return [edge.source, "after"];
|
|
247
|
+
}
|
|
248
|
+
// Check if edge is an ending flowable
|
|
249
|
+
// If true, enable add button to add a task
|
|
250
|
+
// under the flowable task
|
|
251
|
+
if (edge.source.endsWith("_end") && edge.target.endsWith("_end")) {
|
|
252
|
+
// Cluster uid contains the flowable task id
|
|
253
|
+
// So we look for the cluster having this end edge
|
|
254
|
+
// to return his flowable id
|
|
255
|
+
return [this.getClusterTaskIdWithEndNodeUid(edge.source, flowGraph), "after"];
|
|
256
|
+
}
|
|
257
|
+
if (flowables.includes(edge.source)) {
|
|
258
|
+
return [YamlUtils.getNextTaskId(edge.target, flowSource, flowGraph), "before"];
|
|
259
|
+
}
|
|
260
|
+
if (YamlUtils.extractTask(flowSource, edge.source) && edge.target.endsWith("_end")) {
|
|
261
|
+
return [edge.source, "after"];
|
|
262
|
+
}
|
|
263
|
+
if (YamlUtils.extractTask(flowSource, edge.source) && edge.target.endsWith("_start")) {
|
|
264
|
+
return [edge.source, "after"];
|
|
265
|
+
}
|
|
266
|
+
if (YamlUtils.extractTask(flowSource, edge.target) && edge.source.endsWith("_end")) {
|
|
267
|
+
return [edge.target, "before"];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
static getEdgeColor(edge, flowSource) {
|
|
274
|
+
if (YamlUtils.isTaskError(flowSource, edge.source) || YamlUtils.isTaskError(flowSource, edge.target)) {
|
|
275
|
+
return "danger"
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
static generateGraph(vueFlowId, flowId, namespace, flowGraph, flowSource, hiddenNodes, isHorizontal, edgeReplacer, collapsed, clusterToNode, isReadOnly, isAllowedEdit, flowables) {
|
|
281
|
+
const elements = [];
|
|
282
|
+
|
|
283
|
+
const clusterCollapseToNode = ["Triggers"];
|
|
284
|
+
|
|
285
|
+
if (!flowGraph || !this.flowHaveTasks(flowSource)) {
|
|
286
|
+
elements.push({
|
|
287
|
+
id: "start",
|
|
288
|
+
label: "",
|
|
289
|
+
type: "dot",
|
|
290
|
+
position: {x: 0, y: 0},
|
|
291
|
+
style: {
|
|
292
|
+
width: "5px",
|
|
293
|
+
height: "5px"
|
|
294
|
+
},
|
|
295
|
+
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
|
296
|
+
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
|
297
|
+
parentNode: undefined,
|
|
298
|
+
draggable: false,
|
|
299
|
+
})
|
|
300
|
+
elements.push({
|
|
301
|
+
id: "end",
|
|
302
|
+
label: "",
|
|
303
|
+
type: "dot",
|
|
304
|
+
position: isHorizontal ? {x: 50, y: 0} : {x: 0, y: 50},
|
|
305
|
+
style: {
|
|
306
|
+
width: "5px",
|
|
307
|
+
height: "5px"
|
|
308
|
+
},
|
|
309
|
+
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
|
310
|
+
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
|
311
|
+
parentNode: undefined,
|
|
312
|
+
draggable: false,
|
|
313
|
+
})
|
|
314
|
+
elements.push({
|
|
315
|
+
id: "start|end",
|
|
316
|
+
source: "start",
|
|
317
|
+
target: "end",
|
|
318
|
+
type: "edge",
|
|
319
|
+
data: {
|
|
320
|
+
edge: {
|
|
321
|
+
relation: {
|
|
322
|
+
relationType: "SEQUENTIAL"
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
isFlowable: false,
|
|
326
|
+
initTask: true,
|
|
327
|
+
color: "primary"
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const dagreGraph = this.generateDagreGraph(flowGraph, hiddenNodes, isHorizontal, clusterCollapseToNode, edgeReplacer, collapsed, clusterToNode);
|
|
334
|
+
const clusters = {};
|
|
335
|
+
for (let cluster of (flowGraph.clusters || [])) {
|
|
336
|
+
if (!edgeReplacer[cluster.cluster.uid] && !collapsed.includes(cluster.cluster.uid)) {
|
|
337
|
+
for (let nodeUid of cluster.nodes) {
|
|
338
|
+
clusters[nodeUid] = cluster.cluster;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const clusterUid = cluster.cluster.uid;
|
|
342
|
+
const dagreNode = dagreGraph.node(clusterUid)
|
|
343
|
+
const parentNode = cluster.parents ? cluster.parents[cluster.parents.length - 1] : undefined;
|
|
344
|
+
|
|
345
|
+
elements.push({
|
|
346
|
+
id: clusterUid,
|
|
347
|
+
type: "cluster",
|
|
348
|
+
parentNode: parentNode,
|
|
349
|
+
position: this.getNodePosition(dagreNode, parentNode ? dagreGraph.node(parentNode) : undefined),
|
|
350
|
+
style: {
|
|
351
|
+
width: clusterUid === "Triggers" && isHorizontal ? NODE_SIZES.TRIGGER_CLUSTER_WIDTH + "px" : dagreNode.width + "px",
|
|
352
|
+
height: clusterUid === "Triggers" && !isHorizontal ? NODE_SIZES.TRIGGER_CLUSTER_HEIGHT + "px" : dagreNode.height + "px"
|
|
353
|
+
},
|
|
354
|
+
data: {
|
|
355
|
+
collapsable: true,
|
|
356
|
+
color: clusterUid === "Triggers" ? "success" : "blue"
|
|
357
|
+
},
|
|
358
|
+
class: `bg-light-${clusterUid === "Triggers" ? "success" : "blue"}-border rounded p-2`,
|
|
359
|
+
})
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
let disabledLowCode = [];
|
|
364
|
+
for (const node of flowGraph.nodes.concat(clusterToNode)) {
|
|
365
|
+
if (!hiddenNodes.includes(node.uid)) {
|
|
366
|
+
const dagreNode = dagreGraph.node(node.uid);
|
|
367
|
+
let nodeType = "task";
|
|
368
|
+
if (node.type.includes("GraphClusterEnd")) {
|
|
369
|
+
nodeType = "dot";
|
|
370
|
+
} else if (clusters[node.uid] === undefined && node.type.includes("GraphClusterRoot")) {
|
|
371
|
+
nodeType = "dot";
|
|
372
|
+
} else if (node.type.includes("GraphClusterRoot")) {
|
|
373
|
+
nodeType = "dot";
|
|
374
|
+
} else if (node.type.includes("GraphTrigger")) {
|
|
375
|
+
nodeType = "trigger";
|
|
376
|
+
} else if (node.type === "collapsedcluster") {
|
|
377
|
+
nodeType = "collapsedcluster";
|
|
378
|
+
}
|
|
379
|
+
// Disable interaction for Dag task
|
|
380
|
+
// because our low code editor can not handle it for now
|
|
381
|
+
if (this.isTaskNode(node) && node.task.type === "io.kestra.core.tasks.flows.Dag") {
|
|
382
|
+
disabledLowCode.push(node.task.id);
|
|
383
|
+
YamlUtils.getChildrenTasks(flowSource, node.task.id).forEach(child => {
|
|
384
|
+
disabledLowCode.push(child);
|
|
385
|
+
})
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const taskId = node?.task?.id;
|
|
389
|
+
elements.push({
|
|
390
|
+
id: node.uid,
|
|
391
|
+
label: this.isTaskNode(node) ? taskId : "",
|
|
392
|
+
type: nodeType,
|
|
393
|
+
position: this.getNodePosition(dagreNode, clusters[node.uid] ? dagreGraph.node(clusters[node.uid].uid) : undefined),
|
|
394
|
+
style: {
|
|
395
|
+
width: this.getNodeWidth(node) + "px",
|
|
396
|
+
height: this.getNodeHeight(node) + "px"
|
|
397
|
+
},
|
|
398
|
+
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
|
399
|
+
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
|
400
|
+
parentNode: clusters[node.uid] ? clusters[node.uid].uid : undefined,
|
|
401
|
+
draggable: nodeType === "task" && !isReadOnly && this.isTaskNode(node) ? !disabledLowCode.includes(taskId) : false,
|
|
402
|
+
data: {
|
|
403
|
+
node: node,
|
|
404
|
+
namespace: namespace,
|
|
405
|
+
flowId: flowId,
|
|
406
|
+
isFlowable: this.isTaskNode(node) ? flowables.includes(taskId) : false,
|
|
407
|
+
color: nodeType != "dot" ? this.nodeColor(node, collapsed, flowSource) : null,
|
|
408
|
+
expandable: taskId ? flowables.includes(taskId) && edgeReplacer[CLUSTER_UID_SEPARATOR + taskId] !== undefined : this.isCollapsedCluster(node),
|
|
409
|
+
isReadOnly: isReadOnly,
|
|
410
|
+
link: node.task?.type === "io.kestra.core.tasks.flows.Flow" ? this.linkDatas(node.task) : false
|
|
411
|
+
},
|
|
412
|
+
class: node.type === "collapsedcluster" ? `bg-light-${node.uid === "Triggers" ? "success" : "blue"}-border rounded p-2` : "",
|
|
413
|
+
})
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
for (const edge of (flowGraph.edges || [])) {
|
|
417
|
+
const newEdge = this.replaceIfCollapsed(edge.source, edge.target, edgeReplacer, hiddenNodes);
|
|
418
|
+
if (newEdge) {
|
|
419
|
+
elements.push({
|
|
420
|
+
id: newEdge.source + "|" + newEdge.target,
|
|
421
|
+
source: newEdge.source,
|
|
422
|
+
target: newEdge.target,
|
|
423
|
+
type: "edge",
|
|
424
|
+
markerEnd: YamlUtils.extractTask(flowSource, newEdge.target) ? {
|
|
425
|
+
id: "marker-custom",
|
|
426
|
+
type: MarkerType.ArrowClosed,
|
|
427
|
+
} : "",
|
|
428
|
+
data: {
|
|
429
|
+
haveAdd: this.haveAdd(edge, flowSource, flowGraph, flowables),
|
|
430
|
+
isFlowable: flowables.includes(edge.source) || flowables.includes(edge.target),
|
|
431
|
+
haveDashArray: YamlUtils.isTrigger(flowSource, edge.source) || YamlUtils.isTrigger(flowSource, edge.target),
|
|
432
|
+
nextTaskId: YamlUtils.getNextTaskId(edge.target, flowSource, flowGraph),
|
|
433
|
+
disabled: disabledLowCode.includes(edge.source) || isReadOnly || !isAllowedEdit,
|
|
434
|
+
color: this.getEdgeColor(edge, flowSource)
|
|
435
|
+
},
|
|
436
|
+
style: {
|
|
437
|
+
zIndex: 10,
|
|
438
|
+
}
|
|
439
|
+
})
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return elements;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
}
|
package/src/utils/YamlUtils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import JsYaml from "js-yaml";
|
|
2
2
|
import yaml, {Document, YAMLMap, isSeq, isMap, Pair, Scalar, YAMLSeq, LineCounter} from "yaml";
|
|
3
3
|
import _cloneDeep from "lodash/cloneDeep"
|
|
4
|
-
import {SECTIONS} from "./constants";
|
|
4
|
+
import {SECTIONS} from "./constants.js";
|
|
5
5
|
|
|
6
6
|
const TOSTRING_OPTIONS = {lineWidth: 0};
|
|
7
7
|
|
|
@@ -428,6 +428,66 @@ export default class YamlUtils {
|
|
|
428
428
|
return children;
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
+
static getParentTask(source, taskId) {
|
|
432
|
+
const yamlDoc = yaml.parseDocument(source);
|
|
433
|
+
let parentTask = null;
|
|
434
|
+
yaml.visit(yamlDoc, {
|
|
435
|
+
Map(_, map) {
|
|
436
|
+
if (map.get("id") !== taskId) {
|
|
437
|
+
yaml.visit(map, {
|
|
438
|
+
Map(_, childMap) {
|
|
439
|
+
if (childMap.get("id") === taskId) {
|
|
440
|
+
parentTask = map.get("id");
|
|
441
|
+
return yaml.visit.BREAK;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
})
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
})
|
|
448
|
+
return parentTask;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
static isTaskError(source, taskId) {
|
|
452
|
+
const yamlDoc = yaml.parseDocument(source);
|
|
453
|
+
let isTaskError = false;
|
|
454
|
+
yaml.visit(yamlDoc, {
|
|
455
|
+
Pair(_, pair) {
|
|
456
|
+
if (pair.key.value === "errors") {
|
|
457
|
+
yaml.visit(pair, {
|
|
458
|
+
Map(_, map) {
|
|
459
|
+
if (map.get("id") === taskId) {
|
|
460
|
+
isTaskError = true;
|
|
461
|
+
return yaml.visit.BREAK;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
})
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
})
|
|
468
|
+
return isTaskError;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
static isTrigger(source, taskId) {
|
|
472
|
+
const yamlDoc = yaml.parseDocument(source);
|
|
473
|
+
let isTrigger = false;
|
|
474
|
+
yaml.visit(yamlDoc, {
|
|
475
|
+
Pair(_, pair) {
|
|
476
|
+
if (pair.key.value === "triggers") {
|
|
477
|
+
yaml.visit(pair, {
|
|
478
|
+
Map(_, map) {
|
|
479
|
+
if (map.get("id") === taskId) {
|
|
480
|
+
isTrigger = true;
|
|
481
|
+
return yaml.visit.BREAK;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
})
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
})
|
|
488
|
+
return isTrigger;
|
|
489
|
+
}
|
|
490
|
+
|
|
431
491
|
static replaceIdAndNamespace(source, id, namespace) {
|
|
432
492
|
return source.replace(/^(id\s*:\s*(["']?))\S*/m, "$1"+id+"$2").replace(/^(namespace\s*:\s*(["']?))\S*/m, "$1"+namespace+"$2")
|
|
433
493
|
}
|
|
@@ -475,4 +535,21 @@ export default class YamlUtils {
|
|
|
475
535
|
const tasks = yaml.parseDocument(source).contents.items.find(item => item.key.value === "tasks");
|
|
476
536
|
return tasks && tasks.value.items && tasks.value.items.length >= 1;
|
|
477
537
|
}
|
|
538
|
+
|
|
539
|
+
static getNextTaskId (target, flowSource, flowGraph) {
|
|
540
|
+
while (YamlUtils.extractTask(flowSource, target) === undefined) {
|
|
541
|
+
const edge = flowGraph.edges.find(e => e.source === target)
|
|
542
|
+
if (!edge) {
|
|
543
|
+
return null
|
|
544
|
+
}
|
|
545
|
+
target = edge.target
|
|
546
|
+
}
|
|
547
|
+
return target
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
static isTaskParallel (taskId, flowSource) {
|
|
551
|
+
const clusterTask = YamlUtils.parse(YamlUtils.extractTask(flowSource, taskId));
|
|
552
|
+
return clusterTask?.type === "io.kestra.core.tasks.flows.EachParallel" ||
|
|
553
|
+
clusterTask?.type === "io.kestra.core.tasks.flows.Parallel" ? clusterTask : undefined;
|
|
554
|
+
}
|
|
478
555
|
}
|
package/src/utils/constants.js
CHANGED
|
@@ -16,4 +16,21 @@ export const EVENTS = {
|
|
|
16
16
|
"MOUSE_LEAVE": "mouseleave",
|
|
17
17
|
"ADD_ERROR": "addError",
|
|
18
18
|
"EXPAND_DEPENDENCIES": "expandDependencies",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const CLUSTER_UID_SEPARATOR = "cluster_";
|
|
22
|
+
|
|
23
|
+
export const NODE_SIZES = {
|
|
24
|
+
TASK_WIDTH: 184,
|
|
25
|
+
TASK_HEIGHT: 44,
|
|
26
|
+
TRIGGER_WIDTH: 184,
|
|
27
|
+
TRIGGER_HEIGHT: 44,
|
|
28
|
+
DOT_WIDTH: 5,
|
|
29
|
+
DOT_HEIGHT: 5,
|
|
30
|
+
COLLAPSED_CLUSTER_WIDTH: 150,
|
|
31
|
+
COLLAPSED_CLUSTER_HEIGHT: 44,
|
|
32
|
+
TRIGGER_CLUSTER_WIDTH: 350,
|
|
33
|
+
TRIGGER_CLUSTER_HEIGHT: 180
|
|
34
|
+
|
|
35
|
+
|
|
19
36
|
}
|
package/src/utils/global.js
CHANGED
|
@@ -15,9 +15,9 @@ String.prototype.hashCode = function () {
|
|
|
15
15
|
return hash + "";
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const root = document.querySelector(":root");
|
|
19
|
-
const rootStyle = getComputedStyle(root);
|
|
18
|
+
const root = document.querySelector(":root");
|
|
19
|
+
const rootStyle = getComputedStyle(root);
|
|
20
20
|
|
|
21
|
-
export const cssVariable = (name) => {
|
|
22
|
-
|
|
23
|
-
}
|
|
21
|
+
export const cssVariable = (name) => {
|
|
22
|
+
return rootStyle.getPropertyValue(name);
|
|
23
|
+
}
|
package/src/utils/state.js
CHANGED
|
@@ -7,7 +7,7 @@ import StopCircle from "vue-material-design-icons/StopCircle.vue";
|
|
|
7
7
|
import SkipPreviousCircle from "vue-material-design-icons/SkipPreviousCircle.vue";
|
|
8
8
|
import AlertCircle from "vue-material-design-icons/AlertCircle.vue";
|
|
9
9
|
import DotsVerticalCircle from "vue-material-design-icons/DotsVerticalCircle.vue";
|
|
10
|
-
import {cssVariable} from "./global"
|
|
10
|
+
// import {cssVariable} from "./global"
|
|
11
11
|
|
|
12
12
|
const STATE = Object.freeze({
|
|
13
13
|
CREATED: {
|
|
@@ -142,7 +142,7 @@ export default class State {
|
|
|
142
142
|
return {
|
|
143
143
|
key: state.name,
|
|
144
144
|
icon: state.icon,
|
|
145
|
-
color:
|
|
145
|
+
color: ""
|
|
146
146
|
}
|
|
147
147
|
});
|
|
148
148
|
}
|
|
@@ -156,7 +156,7 @@ export default class State {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
static color() {
|
|
159
|
-
return _mapValues(STATE, state => cssVariable("--bs-" + state.colorClass));
|
|
159
|
+
return _mapValues(STATE, state => 'cssVariable("--bs-" + state.colorClass)');
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
static icon() {
|