@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
package/src/utils/GraphUtils.js
DELETED
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
import YamlUtils from "./YamlUtils.js";
|
|
2
|
-
import {MarkerType, Position, useVueFlow} from "@vue-flow/core";
|
|
3
|
-
import dagre from "dagre";
|
|
4
|
-
import Utils from "./Utils.js";
|
|
5
|
-
|
|
6
|
-
// Vue flow methods to interact with Graph
|
|
7
|
-
const {
|
|
8
|
-
id,
|
|
9
|
-
getNodes,
|
|
10
|
-
removeNodes,
|
|
11
|
-
getEdges,
|
|
12
|
-
removeEdges,
|
|
13
|
-
fitView,
|
|
14
|
-
getElements,
|
|
15
|
-
removeSelectedElements,
|
|
16
|
-
} = useVueFlow();
|
|
17
|
-
|
|
18
|
-
export default class GraphUtils {
|
|
19
|
-
// Graph generation methods
|
|
20
|
-
static cleanGraph = () => {
|
|
21
|
-
removeEdges(getEdges.value)
|
|
22
|
-
removeNodes(getNodes.value)
|
|
23
|
-
removeSelectedElements(getElements.value)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
static generateGraph = (flowGraph, source, isHorizontal, isReadOnly, flowData) => {
|
|
27
|
-
this.cleanGraph();
|
|
28
|
-
// next tick
|
|
29
|
-
emit("loading", true);
|
|
30
|
-
try {
|
|
31
|
-
const elements = [];
|
|
32
|
-
if (!flowGraph || !flowHaveTasks(source)) {
|
|
33
|
-
elements.value.push({
|
|
34
|
-
id: "start",
|
|
35
|
-
label: "",
|
|
36
|
-
type: "dot",
|
|
37
|
-
position: {x: 0, y: 0},
|
|
38
|
-
style: {
|
|
39
|
-
width: "5px",
|
|
40
|
-
height: "5px"
|
|
41
|
-
},
|
|
42
|
-
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
|
43
|
-
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
|
44
|
-
parentNode: undefined,
|
|
45
|
-
draggable: false,
|
|
46
|
-
})
|
|
47
|
-
elements.value.push({
|
|
48
|
-
id: "end",
|
|
49
|
-
label: "",
|
|
50
|
-
type: "dot",
|
|
51
|
-
position: isHorizontal ? {x: 50, y: 0} : {x: 0, y: 50},
|
|
52
|
-
style: {
|
|
53
|
-
width: "5px",
|
|
54
|
-
height: "5px"
|
|
55
|
-
},
|
|
56
|
-
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
|
57
|
-
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
|
58
|
-
parentNode: undefined,
|
|
59
|
-
draggable: false,
|
|
60
|
-
})
|
|
61
|
-
elements.value.push({
|
|
62
|
-
id: "start|end",
|
|
63
|
-
source: "start",
|
|
64
|
-
target: "end",
|
|
65
|
-
type: "edge",
|
|
66
|
-
markerEnd: MarkerType.ArrowClosed,
|
|
67
|
-
data: {
|
|
68
|
-
edge: {
|
|
69
|
-
relation: {
|
|
70
|
-
relationType: "SEQUENTIAL"
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
isFlowable: false,
|
|
74
|
-
initTask: true,
|
|
75
|
-
}
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
emit("loading", false);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const dagreGraph = this.generateDagreGraph(flowGraph);
|
|
82
|
-
const clusters = {};
|
|
83
|
-
for (let cluster of (flowGraph.clusters || [])) {
|
|
84
|
-
for (let nodeUid of cluster.nodes) {
|
|
85
|
-
clusters[nodeUid] = cluster.cluster;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const dagreNode = dagreGraph.node(cluster.cluster.uid)
|
|
89
|
-
const parentNode = cluster.parents ? cluster.parents[cluster.parents.length - 1] : undefined;
|
|
90
|
-
|
|
91
|
-
const clusterUid = cluster.cluster.uid;
|
|
92
|
-
elements.value.push({
|
|
93
|
-
id: clusterUid,
|
|
94
|
-
label: clusterUid,
|
|
95
|
-
type: "cluster",
|
|
96
|
-
parentNode: parentNode,
|
|
97
|
-
position: this.getNodePosition(dagreNode, parentNode ? dagreGraph.node(parentNode) : undefined),
|
|
98
|
-
style: {
|
|
99
|
-
width: clusterUid === "Triggers" && isHorizontal ? "400px" : dagreNode.width + "px",
|
|
100
|
-
height: clusterUid === "Triggers" && !isHorizontal ? "250px" : dagreNode.height + "px",
|
|
101
|
-
},
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
let disabledLowCode = [];
|
|
106
|
-
|
|
107
|
-
for (const node of flowGraph.nodes) {
|
|
108
|
-
const dagreNode = dagreGraph.node(node.uid);
|
|
109
|
-
let nodeType = "task";
|
|
110
|
-
if (node.type.includes("GraphClusterEnd")) {
|
|
111
|
-
nodeType = "dot";
|
|
112
|
-
} else if (clusters[node.uid] === undefined && node.type.includes("GraphClusterRoot")) {
|
|
113
|
-
nodeType = "dot";
|
|
114
|
-
} else if (node.type.includes("GraphClusterRoot")) {
|
|
115
|
-
nodeType = "dot";
|
|
116
|
-
} else if (node.type.includes("GraphTrigger")) {
|
|
117
|
-
nodeType = "trigger";
|
|
118
|
-
}
|
|
119
|
-
// Disable interaction for Dag task
|
|
120
|
-
// because our low code editor can not handle it for now
|
|
121
|
-
if (this.isTaskNode(node) && node.task.type === "io.kestra.core.tasks.flows.Dag") {
|
|
122
|
-
disabledLowCode.push(node.task.id);
|
|
123
|
-
YamlUtils.getChildrenTasks(source, node.task.id).forEach(child => {
|
|
124
|
-
disabledLowCode.push(child);
|
|
125
|
-
})
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
elements.value.push({
|
|
129
|
-
id: node.uid,
|
|
130
|
-
label: this.isTaskNode(node) ? node.task.id : "",
|
|
131
|
-
type: nodeType,
|
|
132
|
-
position: this.getNodePosition(dagreNode, clusters[node.uid] ? dagreGraph.node(clusters[node.uid].uid) : undefined),
|
|
133
|
-
style: {
|
|
134
|
-
width: this.getNodeWidth(node) + "px",
|
|
135
|
-
height: this.getNodeHeight(node) + "px"
|
|
136
|
-
},
|
|
137
|
-
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
|
138
|
-
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
|
139
|
-
parentNode: clusters[node.uid] ? clusters[node.uid].uid : undefined,
|
|
140
|
-
draggable: nodeType === "task" && !isReadOnly && this.isTaskNode(node) ? !disabledLowCode.includes(node.task.id) : false,
|
|
141
|
-
data: {
|
|
142
|
-
node: node,
|
|
143
|
-
namespace: flowData.namespace,
|
|
144
|
-
flowId: flowData.flowId,
|
|
145
|
-
revision: flowData.execution ? flowData.execution.flowRevision : undefined,
|
|
146
|
-
isFlowable: this.isTaskNode(node) ? (flowGraph && flowGraph.flowables ? flowGraph.flowables : []).includes(node.task.id) : false
|
|
147
|
-
},
|
|
148
|
-
})
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
for (const edge of flowGraph.edges) {
|
|
152
|
-
elements.value.push({
|
|
153
|
-
id: edge.source + "|" + edge.target,
|
|
154
|
-
source: edge.source,
|
|
155
|
-
target: edge.target,
|
|
156
|
-
type: "edge",
|
|
157
|
-
markerEnd: MarkerType.ArrowClosed,
|
|
158
|
-
data: {
|
|
159
|
-
edge: edge,
|
|
160
|
-
haveAdd: complexEdgeHaveAdd(edge),
|
|
161
|
-
isFlowable: flowables().includes(edge.source) || flowables().includes(edge.target),
|
|
162
|
-
nextTaskId: getNextTaskId(edge.target),
|
|
163
|
-
disabled: disabledLowCode.includes(edge.source)
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
}
|
|
167
|
-
return elements;
|
|
168
|
-
} catch (e) {
|
|
169
|
-
console.error("Error while creating topology graph: " + e);
|
|
170
|
-
} finally {
|
|
171
|
-
emit("loading", false);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
static getFirstTaskId = (source) => {
|
|
176
|
-
return YamlUtils.getFirstTask(source);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
static getNextTaskId = (target, source, edges) => {
|
|
180
|
-
while (YamlUtils.extractTask(source, target) === undefined) {
|
|
181
|
-
const edge = edges.find(e => e.source === target)
|
|
182
|
-
if (!edge) {
|
|
183
|
-
return null
|
|
184
|
-
}
|
|
185
|
-
target = edge.target
|
|
186
|
-
}
|
|
187
|
-
return target
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
static generateDagreGraph = (flowGraph) => {
|
|
191
|
-
const dagreGraph = new dagre.graphlib.Graph({compound: true})
|
|
192
|
-
dagreGraph.setDefaultEdgeLabel(() => ({}))
|
|
193
|
-
dagreGraph.setGraph({rankdir: isHorizontal.value ? "LR" : "TB"})
|
|
194
|
-
|
|
195
|
-
for (const node of flowGraph.nodes) {
|
|
196
|
-
dagreGraph.setNode(node.uid, {
|
|
197
|
-
width: this.getNodeWidth(node),
|
|
198
|
-
height: this.getNodeHeight(node)
|
|
199
|
-
})
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
for (const edge of flowGraph.edges) {
|
|
203
|
-
dagreGraph.setEdge(edge.source, edge.target)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
for (let cluster of (flowGraph.clusters || [])) {
|
|
207
|
-
dagreGraph.setNode(cluster.cluster.uid, {clusterLabelPos: "top"});
|
|
208
|
-
|
|
209
|
-
if (cluster.parents) {
|
|
210
|
-
dagreGraph.setParent(cluster.cluster.uid, cluster.parents[cluster.parents.length - 1]);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
for (let node of (cluster.nodes || [])) {
|
|
214
|
-
dagreGraph.setParent(node, cluster.cluster.uid)
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
dagre.layout(dagreGraph)
|
|
218
|
-
return dagreGraph;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
static getNodePosition = (n, parent) => {
|
|
222
|
-
const position = {x: n.x - n.width / 2, y: n.y - n.height / 2};
|
|
223
|
-
|
|
224
|
-
// bug with parent node,
|
|
225
|
-
if (parent) {
|
|
226
|
-
const parentPosition = this.getNodePosition(parent);
|
|
227
|
-
position.x = position.x - parentPosition.x;
|
|
228
|
-
position.y = position.y - parentPosition.y;
|
|
229
|
-
}
|
|
230
|
-
return position;
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
static getNodeWidth = (node) => {
|
|
234
|
-
return this.isTaskNode(node) || this.isTriggerNode(node) ? 202 : 5;
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
static getNodeHeight = (node, isHorizontal) => {
|
|
238
|
-
return this.isTaskNode(node) || this.isTriggerNode(node) ? 55 : (isHorizontal ? 55 : 5);
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
static isTaskNode = (node) => {
|
|
242
|
-
return node.task !== undefined && (node.type === "io.kestra.core.models.hierarchies.GraphTask" || node.type === "io.kestra.core.models.hierarchies.GraphClusterRoot")
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
static isTriggerNode = (node) => {
|
|
246
|
-
return node.trigger !== undefined && (node.type === "io.kestra.core.models.hierarchies.GraphTrigger");
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
static complexEdgeHaveAdd = (edge) => {
|
|
251
|
-
// Check if edge is an ending flowable
|
|
252
|
-
// If true, enable add button to add a task
|
|
253
|
-
// under the flowable task
|
|
254
|
-
const isEndtoEndEdge = edge.source.includes("_end") && edge.target.includes("_end")
|
|
255
|
-
if (isEndtoEndEdge) {
|
|
256
|
-
// Cluster uid contains the flowable task id
|
|
257
|
-
// So we look for the cluster having this end edge
|
|
258
|
-
// to return his flowable id
|
|
259
|
-
return [getClusterTaskIdWithEndNodeUid(edge.source), "after"];
|
|
260
|
-
}
|
|
261
|
-
if (this.isLinkToFirstFlowableTask(edge)) {
|
|
262
|
-
return [this.getFirstTaskId(), "before"];
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return undefined;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
static getClusterTaskIdWithEndNodeUid = (flowGraph, nodeUid) => {
|
|
269
|
-
const cluster = flowGraph.clusters.find(cluster => cluster.end === nodeUid);
|
|
270
|
-
if (cluster) {
|
|
271
|
-
return Utils.splitFirst(cluster.cluster.uid, "cluster_");
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return undefined;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
static isLinkToFirstFlowableTask = (edge) => {
|
|
278
|
-
const firstTaskId = this.getFirstTaskId();
|
|
279
|
-
|
|
280
|
-
return flowables().includes(firstTaskId) && edge.target === firstTaskId;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Graph interactions functions
|
|
284
|
-
static onMouseOver = (node) => {
|
|
285
|
-
if (!dragging.value) {
|
|
286
|
-
linkedElements(id, node.uid).forEach((n) => {
|
|
287
|
-
if (n.type === "task") {
|
|
288
|
-
n.style = {...n.style, outline: "0.5px solid " + cssVariable("--bs-yellow")}
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
static onMouseLeave = () => {
|
|
296
|
-
resetNodesStyle();
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
static resetNodesStyle = () => {
|
|
300
|
-
getNodes.value.filter(n => n.type === "task" || n.type === " trigger")
|
|
301
|
-
.forEach(n => {
|
|
302
|
-
n.style = {...n.style, opacity: "1", outline: "none"}
|
|
303
|
-
})
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
//onNodeDragStart((e) => {
|
|
307
|
-
// dragging.value = true;
|
|
308
|
-
// resetNodesStyle();
|
|
309
|
-
// e.node.style = {...e.node.style, zIndex: 1976}
|
|
310
|
-
// lastPosition.value = e.node.position;
|
|
311
|
-
// })
|
|
312
|
-
//
|
|
313
|
-
// onNodeDragStop((e) => {
|
|
314
|
-
// dragging.value = false;
|
|
315
|
-
// if (checkIntersections(e.intersections, e.node) === null) {
|
|
316
|
-
// const taskNode1 = e.node;
|
|
317
|
-
// // check multiple intersection with task
|
|
318
|
-
// const taskNode2 = e.intersections.find(n => n.type === "task");
|
|
319
|
-
// if (taskNode2) {
|
|
320
|
-
// try {
|
|
321
|
-
// emit("on-edit", YamlUtils.swapTasks(props.source, taskNode1.id, taskNode2.id))
|
|
322
|
-
// } catch (e) {
|
|
323
|
-
// store.dispatch("core/showMessage", {
|
|
324
|
-
// variant: "error",
|
|
325
|
-
// title: t("cannot swap tasks"),
|
|
326
|
-
// message: t(e.message, e.messageOptions)
|
|
327
|
-
// });
|
|
328
|
-
// taskNode1.position = lastPosition.value;
|
|
329
|
-
// }
|
|
330
|
-
// } else {
|
|
331
|
-
// taskNode1.position = lastPosition.value;
|
|
332
|
-
// }
|
|
333
|
-
// } else {
|
|
334
|
-
// e.node.position = lastPosition.value;
|
|
335
|
-
// }
|
|
336
|
-
// resetNodesStyle();
|
|
337
|
-
// e.node.style = {...e.node.style, zIndex: 1}
|
|
338
|
-
// lastPosition.value = null;
|
|
339
|
-
// })
|
|
340
|
-
//
|
|
341
|
-
// onNodeDrag((e) => {
|
|
342
|
-
// resetNodesStyle();
|
|
343
|
-
// getNodes.value.filter(n => n.id !== e.node.id).forEach(n => {
|
|
344
|
-
// if (n.type === "trigger" || (n.type === "task" && YamlUtils.isParentChildrenRelation(props.source, n.id, e.node.id))) {
|
|
345
|
-
// n.style = {...n.style, opacity: "0.5"}
|
|
346
|
-
// } else {
|
|
347
|
-
// n.style = {...n.style, opacity: "1"}
|
|
348
|
-
// }
|
|
349
|
-
// })
|
|
350
|
-
// if (!checkIntersections(e.intersections, e.node) && e.intersections.filter(n => n.type === "task").length === 1) {
|
|
351
|
-
// e.intersections.forEach(n => {
|
|
352
|
-
// if (n.type === "task") {
|
|
353
|
-
// n.style = {...n.style, outline: "0.5px solid " + cssVariable("--bs-primary")}
|
|
354
|
-
// }
|
|
355
|
-
// })
|
|
356
|
-
// e.node.style = {...e.node.style, outline: "0.5px solid " + cssVariable("--bs-primary")}
|
|
357
|
-
// }
|
|
358
|
-
// })
|
|
359
|
-
|
|
360
|
-
static checkIntersections = (intersections, node) => {
|
|
361
|
-
const tasksMeet = intersections.filter(n => n.type === "task").map(n => n.id);
|
|
362
|
-
if (tasksMeet.length > 1) {
|
|
363
|
-
return "toomuchtaskerror";
|
|
364
|
-
}
|
|
365
|
-
if (tasksMeet.length === 1 && YamlUtils.isParentChildrenRelation(props.source, tasksMeet[0], node.id)) {
|
|
366
|
-
return "parentchildrenerror";
|
|
367
|
-
}
|
|
368
|
-
if (intersections.filter(n => n.type === "trigger").length > 0) {
|
|
369
|
-
return "triggererror";
|
|
370
|
-
}
|
|
371
|
-
return null;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
static toggleOrientation = (isHorizontal) => {
|
|
375
|
-
localStorage.setItem(
|
|
376
|
-
"topology-orientation",
|
|
377
|
-
localStorage.getItem("topology-orientation") !== "0" ? "0" : "1"
|
|
378
|
-
);
|
|
379
|
-
isHorizontal = localStorage.getItem("topology-orientation") === "1";
|
|
380
|
-
// emit an event
|
|
381
|
-
this.generateGraph();
|
|
382
|
-
fitView();
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Flow check functions
|
|
386
|
-
static flowHaveTasks = (source) => {
|
|
387
|
-
return source ? YamlUtils.flowHaveTasks(source) : false;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
}
|