@kestra-io/ui-libs 0.0.222 → 0.0.224
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/dist/{FlowYamlUtils-B2iMnYvW.js → FlowYamlUtils-BnXiRqSX.js} +44 -44
- package/dist/{FlowYamlUtils-B2iMnYvW.js.map → FlowYamlUtils-BnXiRqSX.js.map} +1 -1
- package/dist/VueFlowUtils-DKrM_RaI.js +4513 -0
- package/dist/VueFlowUtils-DKrM_RaI.js.map +1 -0
- package/dist/VueFlowUtils-Dh7ybprm.cjs +2 -0
- package/dist/VueFlowUtils-Dh7ybprm.cjs.map +1 -0
- package/dist/components/nodes/BasicNode.vue.d.ts +1 -0
- package/dist/components/nodes/BasicNode.vue.d.ts.map +1 -1
- package/dist/components/nodes/CollapsedClusterNode.vue.d.ts +1 -0
- package/dist/components/nodes/CollapsedClusterNode.vue.d.ts.map +1 -1
- package/dist/components/nodes/EdgeNode.vue.d.ts +1 -0
- package/dist/components/nodes/TaskNode.vue.d.ts +1 -0
- package/dist/components/nodes/TaskNode.vue.d.ts.map +1 -1
- package/dist/components/nodes/TriggerNode.vue.d.ts +1 -0
- package/dist/components/nodes/TriggerNode.vue.d.ts.map +1 -1
- package/dist/components/topology/Topology.vue.d.ts +15 -4
- package/dist/components/topology/Topology.vue.d.ts.map +1 -1
- package/dist/components/topology/injectionKeys.d.ts +1 -0
- package/dist/components/topology/injectionKeys.d.ts.map +1 -1
- package/dist/kestra-flowyamlutils.es.js +6 -6
- package/dist/kestra-index.cjs.js +16 -16
- package/dist/kestra-index.cjs.js.map +1 -1
- package/dist/kestra-index.es.js +3940 -8270
- package/dist/kestra-index.es.js.map +1 -1
- package/dist/kestra-vueflowutils.cjs.js +2 -0
- package/dist/kestra-vueflowutils.cjs.js.map +1 -0
- package/dist/kestra-vueflowutils.es.js +33 -0
- package/dist/kestra-vueflowutils.es.js.map +1 -0
- package/dist/ui-libs.css +1 -1
- package/dist/utils/VueFlowUtils.d.ts +102 -49
- package/dist/utils/VueFlowUtils.d.ts.map +1 -1
- package/dist/utils/constants.d.ts +1 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/package.json +9 -1
- package/src/components/misc/ExecutionInformations.vue +2 -2
- package/src/components/nodes/TaskNode.vue +33 -1
- package/src/components/topology/Topology.vue +11 -3
- package/src/components/topology/injectionKeys.ts +2 -1
- package/src/scss/vue-material-design-icon.scss +26 -0
- package/src/utils/VueFlowUtils.test.ts +86 -0
- package/src/utils/VueFlowUtils.ts +566 -424
- package/src/utils/constants.ts +1 -0
|
@@ -2,7 +2,7 @@ import {GraphNode, GraphEdge, MarkerType, Position, useVueFlow, Elements} from "
|
|
|
2
2
|
import dagre from "dagre";
|
|
3
3
|
import Utils from "./Utils";
|
|
4
4
|
import {CLUSTER_PREFIX, NODE_SIZES} from "./constants";
|
|
5
|
-
import {flowHaveTasks} from "./FlowYamlUtils";
|
|
5
|
+
import {flowHaveTasks as yamlFlowHaveTask} from "./FlowYamlUtils";
|
|
6
6
|
|
|
7
7
|
const TRIGGERS_NODE_UID = "root.Triggers";
|
|
8
8
|
|
|
@@ -19,6 +19,7 @@ interface MinimalNode {
|
|
|
19
19
|
uid: string;
|
|
20
20
|
type: string;
|
|
21
21
|
task?: {
|
|
22
|
+
id?: string;
|
|
22
23
|
type: string;
|
|
23
24
|
namespace: string;
|
|
24
25
|
flowId: string;
|
|
@@ -54,90 +55,90 @@ export interface FlowGraph {
|
|
|
54
55
|
|
|
55
56
|
type EdgeReplacer = Record<string, string>
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
|
|
59
|
+
export function predecessorsEdge(vueFlowId: string, nodeUid: string): GraphEdge[] {
|
|
59
60
|
const {getEdges} = useVueFlow(vueFlowId);
|
|
60
61
|
|
|
61
62
|
const nodes = [];
|
|
62
63
|
|
|
63
64
|
for (const edge of getEdges.value) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
if (edge.target === nodeUid) {
|
|
66
|
+
nodes.push(edge);
|
|
67
|
+
const recursiveEdge = predecessorsEdge(vueFlowId, edge.source);
|
|
68
|
+
if (recursiveEdge.length > 0) {
|
|
69
|
+
nodes.push(...recursiveEdge);
|
|
70
|
+
}
|
|
69
71
|
}
|
|
70
|
-
}
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
return nodes;
|
|
74
|
-
|
|
75
|
+
}
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
export function successorsEdge(vueFlowId: string, nodeUid: string): GraphEdge[] {
|
|
77
78
|
const {getEdges} = useVueFlow(vueFlowId);
|
|
78
79
|
|
|
79
80
|
const nodes = [];
|
|
80
81
|
|
|
81
82
|
for (const edge of getEdges.value) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
if (edge.source === nodeUid) {
|
|
84
|
+
nodes.push(edge);
|
|
85
|
+
const recursiveEdge = successorsEdge(vueFlowId, edge.target);
|
|
86
|
+
if (recursiveEdge.length > 0) {
|
|
87
|
+
nodes.push(...recursiveEdge);
|
|
88
|
+
}
|
|
87
89
|
}
|
|
88
|
-
}
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
return nodes;
|
|
92
|
-
|
|
93
|
+
}
|
|
93
94
|
|
|
94
|
-
|
|
95
|
+
export function predecessorsNode(vueFlowId: string, nodeUid: string): (GraphEdge | GraphNode)[] {
|
|
95
96
|
const {getEdges, findNode} = useVueFlow(vueFlowId);
|
|
96
97
|
|
|
97
98
|
const foundNode = findNode(nodeUid)
|
|
98
99
|
const nodes: (GraphEdge | GraphNode)[] = foundNode ? [foundNode] : [];
|
|
99
100
|
|
|
100
101
|
for (const edge of getEdges.value) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
if (edge.target === nodeUid) {
|
|
103
|
+
nodes.push(edge.sourceNode);
|
|
104
|
+
const recursiveEdge = predecessorsNode(vueFlowId, edge.source);
|
|
105
|
+
if (recursiveEdge.length > 0) {
|
|
106
|
+
nodes.push(...recursiveEdge);
|
|
107
|
+
}
|
|
106
108
|
}
|
|
107
|
-
}
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
return nodes;
|
|
111
|
-
|
|
112
|
+
}
|
|
112
113
|
|
|
113
|
-
|
|
114
|
+
export function successorsNode(vueFlowId: string, nodeUid: string) {
|
|
114
115
|
const {getEdges, findNode} = useVueFlow(vueFlowId);
|
|
115
116
|
|
|
116
117
|
const nodes = [findNode(nodeUid)];
|
|
117
118
|
|
|
118
119
|
for (const edge of getEdges.value) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
if (edge.source === nodeUid) {
|
|
121
|
+
nodes.push(edge.targetNode);
|
|
122
|
+
const recursiveEdge = successorsNode(vueFlowId, edge.target);
|
|
123
|
+
if (recursiveEdge.length > 0) {
|
|
124
|
+
nodes.push(...recursiveEdge);
|
|
125
|
+
}
|
|
124
126
|
}
|
|
125
|
-
}
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
return nodes;
|
|
129
|
-
|
|
130
|
+
}
|
|
130
131
|
|
|
131
|
-
|
|
132
|
+
export function linkedElements(vueFlowId: string, nodeUid: string) {
|
|
132
133
|
return [
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
...predecessorsEdge(vueFlowId, nodeUid),
|
|
135
|
+
...predecessorsNode(vueFlowId, nodeUid),
|
|
136
|
+
...successorsEdge(vueFlowId, nodeUid),
|
|
137
|
+
...successorsNode(vueFlowId, nodeUid),
|
|
137
138
|
];
|
|
138
|
-
|
|
139
|
+
}
|
|
139
140
|
|
|
140
|
-
|
|
141
|
+
export function generateDagreGraph(
|
|
141
142
|
flowGraph: { nodes: any; clusters: any; edges: any },
|
|
142
143
|
hiddenNodes: string[],
|
|
143
144
|
isHorizontal: boolean,
|
|
@@ -145,78 +146,78 @@ export default {
|
|
|
145
146
|
edgeReplacer: EdgeReplacer,
|
|
146
147
|
collapsed: Set<string>,
|
|
147
148
|
clusterToNode: MinimalNode[]
|
|
148
|
-
|
|
149
|
+
) {
|
|
149
150
|
const dagreGraph = new dagre.graphlib.Graph({compound: true});
|
|
150
151
|
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
|
151
152
|
dagreGraph.setGraph({rankdir: isHorizontal ? "LR" : "TB"});
|
|
152
153
|
|
|
153
154
|
for (const node of flowGraph.nodes) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
if (!hiddenNodes.includes(node.uid)) {
|
|
156
|
+
dagreGraph.setNode(node.uid, {
|
|
157
|
+
width: getNodeWidth(node),
|
|
158
|
+
height: getNodeHeight(node),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
for (const cluster of flowGraph.clusters || []) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
164
|
+
const nodeUid = cluster.cluster.uid.replace(CLUSTER_PREFIX, "");
|
|
165
|
+
if (
|
|
166
|
+
clustersWithoutRootNode.includes(cluster.cluster.uid) &&
|
|
167
|
+
collapsed.has(nodeUid)
|
|
168
|
+
) {
|
|
169
|
+
const node = {uid: nodeUid, type: "collapsedcluster"};
|
|
170
|
+
dagreGraph.setNode(nodeUid, {
|
|
171
|
+
width: getNodeWidth(node),
|
|
172
|
+
height: getNodeHeight(node),
|
|
173
|
+
});
|
|
174
|
+
clusterToNode.push(node);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (!edgeReplacer[cluster.cluster.uid]) {
|
|
178
|
+
dagreGraph.setNode(cluster.cluster.uid, {clusterLabelPos: "top"});
|
|
179
|
+
|
|
180
|
+
for (const node of cluster.nodes || []) {
|
|
181
|
+
if (!hiddenNodes.includes(node)) {
|
|
182
|
+
dagreGraph.setParent(node, cluster.cluster.uid);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
183
185
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
186
|
+
if (cluster.parents) {
|
|
187
|
+
const nodeChild = edgeReplacer[cluster.cluster.uid]
|
|
188
|
+
? edgeReplacer[cluster.cluster.uid]
|
|
189
|
+
: cluster.cluster.uid;
|
|
190
|
+
if (!hiddenNodes.includes(nodeChild)) {
|
|
191
|
+
dagreGraph.setParent(
|
|
192
|
+
nodeChild,
|
|
193
|
+
cluster.parents[cluster.parents.length - 1]
|
|
194
|
+
);
|
|
195
|
+
}
|
|
194
196
|
}
|
|
195
|
-
}
|
|
196
197
|
}
|
|
197
198
|
|
|
198
199
|
for (const edge of flowGraph.edges || []) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
200
|
+
const newEdge = replaceIfCollapsed(
|
|
201
|
+
edge.source,
|
|
202
|
+
edge.target,
|
|
203
|
+
edgeReplacer,
|
|
204
|
+
hiddenNodes
|
|
205
|
+
);
|
|
206
|
+
if (newEdge) {
|
|
207
|
+
dagreGraph.setEdge(newEdge.source, newEdge.target);
|
|
208
|
+
}
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
dagre.layout(dagreGraph);
|
|
211
212
|
return dagreGraph;
|
|
212
|
-
|
|
213
|
+
}
|
|
213
214
|
|
|
214
|
-
|
|
215
|
+
export function getNodePosition(n: {
|
|
215
216
|
x: number;
|
|
216
217
|
y: number;
|
|
217
218
|
width: number;
|
|
218
219
|
height: number;
|
|
219
|
-
|
|
220
|
+
}, parent?: {
|
|
220
221
|
x: number;
|
|
221
222
|
y: number;
|
|
222
223
|
width: number;
|
|
@@ -226,126 +227,126 @@ export default {
|
|
|
226
227
|
|
|
227
228
|
// bug with parent node,
|
|
228
229
|
if (parent) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
const parentPosition = getNodePosition(parent);
|
|
231
|
+
position.x = position.x - parentPosition.x;
|
|
232
|
+
position.y = position.y - parentPosition.y;
|
|
232
233
|
}
|
|
233
234
|
return position;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
return
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function getNodeWidth(node: MinimalNode) {
|
|
238
|
+
return isTaskNode(node) || isTriggerNode(node)
|
|
239
|
+
? NODE_SIZES.TASK_WIDTH
|
|
240
|
+
: (isCollapsedCluster(node)
|
|
241
|
+
? NODE_SIZES.COLLAPSED_CLUSTER_WIDTH
|
|
242
|
+
: NODE_SIZES.DOT_WIDTH);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function getNodeHeight(node: MinimalNode) {
|
|
246
|
+
return isTaskNode(node) || isTriggerNode(node)
|
|
247
|
+
? NODE_SIZES.TASK_HEIGHT
|
|
248
|
+
: (isCollapsedCluster(node)
|
|
249
|
+
? NODE_SIZES.COLLAPSED_CLUSTER_HEIGHT
|
|
250
|
+
: NODE_SIZES.DOT_HEIGHT);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function isTaskNode(node: MinimalNode) {
|
|
253
254
|
return ["GraphTask", "SubflowGraphTask$1"].some((t) => node.type.endsWith(t));
|
|
254
|
-
|
|
255
|
+
}
|
|
255
256
|
|
|
256
|
-
|
|
257
|
+
export function isTriggerNode(node: MinimalNode) {
|
|
257
258
|
return node.type.endsWith("GraphTrigger");
|
|
258
|
-
|
|
259
|
+
}
|
|
259
260
|
|
|
260
|
-
|
|
261
|
+
export function isCollapsedCluster(node: MinimalNode) {
|
|
261
262
|
return node.type === "collapsedcluster";
|
|
262
|
-
|
|
263
|
+
}
|
|
263
264
|
|
|
264
|
-
|
|
265
|
+
export function replaceIfCollapsed(source: string, target: string, edgeReplacer: EdgeReplacer, hiddenNodes: string[]) {
|
|
265
266
|
const newSource = edgeReplacer[source] ? edgeReplacer[source] : source;
|
|
266
267
|
const newTarget = edgeReplacer[target] ? edgeReplacer[target] : target;
|
|
267
268
|
|
|
268
269
|
if (
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
270
|
+
newSource === newTarget ||
|
|
271
|
+
hiddenNodes.includes(newSource) ||
|
|
272
|
+
hiddenNodes.includes(newTarget)
|
|
272
273
|
) {
|
|
273
|
-
|
|
274
|
+
return null;
|
|
274
275
|
}
|
|
275
276
|
return {target: newTarget, source: newSource};
|
|
276
|
-
|
|
277
|
+
}
|
|
277
278
|
|
|
278
|
-
|
|
279
|
+
export function cleanGraph(vueflowId: string) {
|
|
279
280
|
const {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
281
|
+
getEdges,
|
|
282
|
+
getNodes,
|
|
283
|
+
getElements,
|
|
284
|
+
removeEdges,
|
|
285
|
+
removeNodes,
|
|
286
|
+
removeSelectedElements,
|
|
286
287
|
} = useVueFlow(vueflowId);
|
|
287
288
|
removeEdges(getEdges.value);
|
|
288
289
|
removeNodes(getNodes.value);
|
|
289
290
|
removeSelectedElements(getElements.value);
|
|
290
|
-
|
|
291
|
+
}
|
|
291
292
|
|
|
292
|
-
|
|
293
|
-
return source ?
|
|
294
|
-
|
|
293
|
+
export function flowHaveTasks(source: string) {
|
|
294
|
+
return source ? yamlFlowHaveTask(source) : false;
|
|
295
|
+
}
|
|
295
296
|
|
|
296
|
-
|
|
297
|
+
export function nodeColor(node: MinimalNode, collapsed: Set<string>) {
|
|
297
298
|
if (node.uid === TRIGGERS_NODE_UID) {
|
|
298
|
-
|
|
299
|
+
return "success";
|
|
299
300
|
}
|
|
300
301
|
|
|
301
|
-
if (
|
|
302
|
-
|
|
302
|
+
if (isTriggerNode(node) || isCollapsedCluster(node)) {
|
|
303
|
+
return "success";
|
|
303
304
|
}
|
|
304
305
|
|
|
305
306
|
if (node.type.endsWith("SubflowGraphTask")) {
|
|
306
|
-
|
|
307
|
+
return "primary";
|
|
307
308
|
}
|
|
308
309
|
|
|
309
310
|
if (node.branchType == BranchType.ERROR) {
|
|
310
|
-
|
|
311
|
+
return "danger";
|
|
311
312
|
}
|
|
312
313
|
|
|
313
314
|
if (node.branchType == BranchType.FINALLY) {
|
|
314
|
-
|
|
315
|
+
return "warning";
|
|
315
316
|
}
|
|
316
317
|
|
|
317
318
|
if (collapsed.has(node.uid)) {
|
|
318
|
-
|
|
319
|
+
return "blue";
|
|
319
320
|
}
|
|
320
321
|
|
|
321
322
|
return "default";
|
|
322
|
-
|
|
323
|
+
}
|
|
323
324
|
|
|
324
|
-
|
|
325
|
-
nodeByUid:Record<string, MinimalNode>,
|
|
326
|
-
clustersRootTaskUids:string[],
|
|
327
|
-
readOnlyUidPrefixes:string[]) {
|
|
325
|
+
export function haveAdd(edge: GraphEdge,
|
|
326
|
+
nodeByUid: Record<string, MinimalNode>,
|
|
327
|
+
clustersRootTaskUids: string[],
|
|
328
|
+
readOnlyUidPrefixes: string[]) {
|
|
328
329
|
// prevent subflow edit (edge = subflowNode -> subflowNode)
|
|
329
330
|
if (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
331
|
+
readOnlyUidPrefixes.some(
|
|
332
|
+
(prefix) =>
|
|
333
|
+
edge.source.startsWith(prefix) && edge.target.startsWith(prefix)
|
|
334
|
+
)
|
|
334
335
|
) {
|
|
335
|
-
|
|
336
|
+
return undefined;
|
|
336
337
|
}
|
|
337
338
|
|
|
338
339
|
// edge = clusterRoot -> clusterRootTask
|
|
339
340
|
if (clustersRootTaskUids.includes(edge.target)) {
|
|
340
|
-
|
|
341
|
+
return undefined;
|
|
341
342
|
}
|
|
342
343
|
|
|
343
344
|
// edge = Triggers cluster -> something || edge = something -> Triggers cluster
|
|
344
345
|
if (
|
|
345
|
-
|
|
346
|
-
|
|
346
|
+
edge.source.startsWith(TRIGGERS_NODE_UID) ||
|
|
347
|
+
edge.target.startsWith(TRIGGERS_NODE_UID)
|
|
347
348
|
) {
|
|
348
|
-
|
|
349
|
+
return undefined;
|
|
349
350
|
}
|
|
350
351
|
|
|
351
352
|
const dotSplitTarget = edge.target.split(".");
|
|
@@ -356,60 +357,60 @@ export default {
|
|
|
356
357
|
// edge = task of parallel -> end of parallel, we only add + symbol right after the parallel cluster root task node
|
|
357
358
|
const targetNode = nodeByUid[edge.target];
|
|
358
359
|
if (
|
|
359
|
-
|
|
360
|
-
|
|
360
|
+
targetNode.type.endsWith("GraphClusterEnd") &&
|
|
361
|
+
nodeByUid[targetNodeClusterUid]?.task?.type?.endsWith("Parallel")
|
|
361
362
|
) {
|
|
362
|
-
|
|
363
|
+
return undefined;
|
|
363
364
|
}
|
|
364
365
|
|
|
365
366
|
// edge = something -> clusterRoot ==> we insert before the cluster
|
|
366
367
|
// clusterUid = clusterTraversalPrefix.{rootTaskUid}
|
|
367
368
|
// clusterRoot.uid = clusterUid.someUid = clusterTraversalPrefix.{rootTaskUid}.someUid
|
|
368
369
|
if (targetNode.type.endsWith("GraphClusterRoot")) {
|
|
369
|
-
|
|
370
|
+
return [clusterRootTaskId, "before"];
|
|
370
371
|
}
|
|
371
372
|
|
|
372
373
|
const sourceIsEndOfCluster =
|
|
373
|
-
|
|
374
|
+
nodeByUid[edge.source].type.endsWith("GraphClusterEnd");
|
|
374
375
|
// edge = clusterTask -> clusterEnd ==> we insert after the previous task
|
|
375
376
|
if (!sourceIsEndOfCluster && targetNode.type.endsWith("GraphClusterEnd")) {
|
|
376
|
-
|
|
377
|
+
return [Utils.afterLastDot(edge.source), "after"];
|
|
377
378
|
}
|
|
378
379
|
|
|
379
380
|
// edge = cluster1End -> something ==> we insert after cluster1
|
|
380
381
|
if (sourceIsEndOfCluster) {
|
|
381
|
-
|
|
382
|
-
|
|
382
|
+
const dotSplitSource = edge.source.split(".");
|
|
383
|
+
return [dotSplitSource[dotSplitSource.length - 2], "after"];
|
|
383
384
|
}
|
|
384
385
|
|
|
385
386
|
return [Utils.afterLastDot(edge.target), "before"];
|
|
386
|
-
|
|
387
|
+
}
|
|
387
388
|
|
|
388
|
-
|
|
389
|
+
export function getEdgeColor(edge: GraphEdge, nodeByUid: Record<string, MinimalNode>, clusterByNodeUid: Record<string, Cluster>) {
|
|
389
390
|
const findRootBranchType = (nodeId: string): BranchType | null => {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
391
|
+
const uidParts = nodeId.split(".");
|
|
392
|
+
for (let i = 1; i <= uidParts.length; i++) {
|
|
393
|
+
const parentUid = uidParts.slice(0, i).join(".");
|
|
394
|
+
const branchType = clusterByNodeUid[parentUid]?.branchType;
|
|
395
|
+
if (branchType) return branchType;
|
|
396
|
+
}
|
|
397
|
+
return nodeByUid[nodeId]?.branchType ?? null;
|
|
397
398
|
};
|
|
398
399
|
|
|
399
400
|
const sourceBranchType = findRootBranchType(edge.source);
|
|
400
401
|
const targetBranchType = findRootBranchType(edge.target);
|
|
401
402
|
|
|
402
|
-
return [sourceBranchType, targetBranchType].includes(BranchType.ERROR) ? "danger"
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
403
|
+
return [sourceBranchType, targetBranchType].includes(BranchType.ERROR) ? "danger"
|
|
404
|
+
: [sourceBranchType, targetBranchType].includes(BranchType.FINALLY) ? "warning"
|
|
405
|
+
: null;
|
|
406
|
+
}
|
|
406
407
|
|
|
407
|
-
|
|
408
|
+
export function generateGraph(
|
|
408
409
|
_vueFlowId: string,
|
|
409
|
-
flowId:string | undefined,
|
|
410
|
-
namespace:string| undefined,
|
|
410
|
+
flowId: string | undefined,
|
|
411
|
+
namespace: string | undefined,
|
|
411
412
|
flowGraph: FlowGraph | undefined,
|
|
412
|
-
flowSource:string | undefined,
|
|
413
|
+
flowSource: string | undefined,
|
|
413
414
|
hiddenNodes: string[],
|
|
414
415
|
isHorizontal: boolean,
|
|
415
416
|
edgeReplacer: EdgeReplacer,
|
|
@@ -418,300 +419,441 @@ export default {
|
|
|
418
419
|
isReadOnly: boolean,
|
|
419
420
|
isAllowedEdit: boolean,
|
|
420
421
|
enableSubflowInteraction: boolean
|
|
421
|
-
|
|
422
|
-
const elements:Elements = [];
|
|
422
|
+
): Elements | undefined {
|
|
423
|
+
const elements: Elements = [];
|
|
423
424
|
|
|
424
425
|
const clustersWithoutRootNode = [CLUSTER_PREFIX + TRIGGERS_NODE_UID];
|
|
425
426
|
|
|
426
|
-
if (!flowGraph || (flowSource && !
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
},
|
|
448
|
-
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
|
449
|
-
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
|
450
|
-
parentNode: undefined,
|
|
451
|
-
draggable: false,
|
|
452
|
-
});
|
|
453
|
-
elements.push({
|
|
454
|
-
id: "start|end",
|
|
455
|
-
source: "start",
|
|
456
|
-
target: "end",
|
|
457
|
-
type: "edge",
|
|
458
|
-
data: {
|
|
459
|
-
edge: {
|
|
460
|
-
relation: {
|
|
461
|
-
relationType: "SEQUENTIAL",
|
|
427
|
+
if (!flowGraph || (flowSource && !flowHaveTasks(flowSource))) {
|
|
428
|
+
elements.push({
|
|
429
|
+
id: "start",
|
|
430
|
+
type: "dot",
|
|
431
|
+
position: {x: 0, y: 0},
|
|
432
|
+
style: {
|
|
433
|
+
width: "5px",
|
|
434
|
+
height: "5px",
|
|
435
|
+
},
|
|
436
|
+
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
|
437
|
+
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
|
438
|
+
parentNode: undefined,
|
|
439
|
+
draggable: false,
|
|
440
|
+
});
|
|
441
|
+
elements.push({
|
|
442
|
+
id: "end",
|
|
443
|
+
type: "dot",
|
|
444
|
+
position: isHorizontal ? {x: 50, y: 0} : {x: 0, y: 50},
|
|
445
|
+
style: {
|
|
446
|
+
width: "5px",
|
|
447
|
+
height: "5px",
|
|
462
448
|
},
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
449
|
+
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
|
450
|
+
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
|
451
|
+
parentNode: undefined,
|
|
452
|
+
draggable: false,
|
|
453
|
+
});
|
|
454
|
+
elements.push({
|
|
455
|
+
id: "start|end",
|
|
456
|
+
source: "start",
|
|
457
|
+
target: "end",
|
|
458
|
+
type: "edge",
|
|
459
|
+
data: {
|
|
460
|
+
edge: {
|
|
461
|
+
relation: {
|
|
462
|
+
relationType: "SEQUENTIAL",
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
isFlowable: false,
|
|
466
|
+
initTask: true,
|
|
467
|
+
color: "primary",
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const dagreGraph = generateDagreGraph(
|
|
475
|
+
flowGraph,
|
|
476
|
+
hiddenNodes,
|
|
477
|
+
isHorizontal,
|
|
478
|
+
clustersWithoutRootNode,
|
|
479
|
+
edgeReplacer,
|
|
480
|
+
collapsed,
|
|
481
|
+
clusterToNode
|
|
481
482
|
);
|
|
482
483
|
|
|
483
|
-
const clusterByNodeUid:Record<string, Cluster>
|
|
484
|
+
const clusterByNodeUid: Record<string, Cluster> = {};
|
|
484
485
|
const clusters = flowGraph.clusters || [];
|
|
485
486
|
const rawClusters = clusters.map((c) => c.cluster);
|
|
486
487
|
const readOnlyUidPrefixes = rawClusters
|
|
487
|
-
|
|
488
|
-
|
|
488
|
+
.filter((c) => c.type.endsWith("SubflowGraphCluster"))
|
|
489
|
+
.map((c) => c.taskNode.uid);
|
|
489
490
|
|
|
490
491
|
const nodeByUid = Object.fromEntries(
|
|
491
|
-
|
|
492
|
+
flowGraph.nodes.concat(clusterToNode).map((node) => [node.uid, node])
|
|
492
493
|
);
|
|
493
494
|
for (const cluster of clusters) {
|
|
494
|
-
if (
|
|
495
|
-
!edgeReplacer[cluster.cluster.uid] &&
|
|
496
|
-
!collapsed.has(cluster.cluster.uid)
|
|
497
|
-
) {
|
|
498
495
|
if (
|
|
499
|
-
|
|
500
|
-
|
|
496
|
+
!edgeReplacer[cluster.cluster.uid] &&
|
|
497
|
+
!collapsed.has(cluster.cluster.uid)
|
|
501
498
|
) {
|
|
502
|
-
|
|
499
|
+
if (
|
|
500
|
+
cluster.cluster.taskNode?.task?.type ===
|
|
501
|
+
"io.kestra.core.tasks.flows.Dag"
|
|
502
|
+
) {
|
|
503
|
+
readOnlyUidPrefixes.push(cluster.cluster.taskNode.uid);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
for (const nodeUid of cluster.nodes) {
|
|
507
|
+
clusterByNodeUid[nodeUid] = cluster.cluster;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const clusterUid = cluster.cluster.uid;
|
|
511
|
+
const dagreNode = dagreGraph.node(clusterUid);
|
|
512
|
+
const parentNode = cluster.parents
|
|
513
|
+
? cluster.parents[cluster.parents.length - 1]
|
|
514
|
+
: undefined;
|
|
515
|
+
|
|
516
|
+
const clusterColor = computeClusterColor(cluster.cluster);
|
|
517
|
+
|
|
518
|
+
elements.push({
|
|
519
|
+
id: clusterUid,
|
|
520
|
+
type: "cluster",
|
|
521
|
+
parentNode: parentNode,
|
|
522
|
+
position: getNodePosition(
|
|
523
|
+
dagreNode,
|
|
524
|
+
parentNode ? dagreGraph.node(parentNode) : undefined
|
|
525
|
+
),
|
|
526
|
+
style: {
|
|
527
|
+
width:
|
|
528
|
+
clusterUid === TRIGGERS_NODE_UID && isHorizontal
|
|
529
|
+
? NODE_SIZES.TRIGGER_CLUSTER_WIDTH + "px"
|
|
530
|
+
: dagreNode.width + "px",
|
|
531
|
+
height:
|
|
532
|
+
clusterUid === TRIGGERS_NODE_UID && !isHorizontal
|
|
533
|
+
? NODE_SIZES.TRIGGER_CLUSTER_HEIGHT + "px"
|
|
534
|
+
: dagreNode.height + "px",
|
|
535
|
+
},
|
|
536
|
+
data: {
|
|
537
|
+
collapsable: true,
|
|
538
|
+
color: clusterColor,
|
|
539
|
+
taskNode: cluster.cluster.taskNode,
|
|
540
|
+
unused: cluster.cluster.taskNode
|
|
541
|
+
? nodeByUid[cluster.cluster.taskNode.uid].unused
|
|
542
|
+
: false,
|
|
543
|
+
},
|
|
544
|
+
class: `ks-topology-${clusterColor}-border rounded p-2`,
|
|
545
|
+
} as any);
|
|
503
546
|
}
|
|
504
|
-
|
|
505
|
-
for (const nodeUid of cluster.nodes) {
|
|
506
|
-
clusterByNodeUid[nodeUid] = cluster.cluster;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
const clusterUid = cluster.cluster.uid;
|
|
510
|
-
const dagreNode = dagreGraph.node(clusterUid);
|
|
511
|
-
const parentNode = cluster.parents
|
|
512
|
-
? cluster.parents[cluster.parents.length - 1]
|
|
513
|
-
: undefined;
|
|
514
|
-
|
|
515
|
-
const clusterColor = this.computeClusterColor(cluster.cluster);
|
|
516
|
-
|
|
517
|
-
elements.push({
|
|
518
|
-
id: clusterUid,
|
|
519
|
-
type: "cluster",
|
|
520
|
-
parentNode: parentNode,
|
|
521
|
-
position: this.getNodePosition(
|
|
522
|
-
dagreNode,
|
|
523
|
-
parentNode ? dagreGraph.node(parentNode) : undefined
|
|
524
|
-
),
|
|
525
|
-
style: {
|
|
526
|
-
width:
|
|
527
|
-
clusterUid === TRIGGERS_NODE_UID && isHorizontal
|
|
528
|
-
? NODE_SIZES.TRIGGER_CLUSTER_WIDTH + "px"
|
|
529
|
-
: dagreNode.width + "px",
|
|
530
|
-
height:
|
|
531
|
-
clusterUid === TRIGGERS_NODE_UID && !isHorizontal
|
|
532
|
-
? NODE_SIZES.TRIGGER_CLUSTER_HEIGHT + "px"
|
|
533
|
-
: dagreNode.height + "px",
|
|
534
|
-
},
|
|
535
|
-
data: {
|
|
536
|
-
collapsable: true,
|
|
537
|
-
color: clusterColor,
|
|
538
|
-
taskNode: cluster.cluster.taskNode,
|
|
539
|
-
unused: cluster.cluster.taskNode
|
|
540
|
-
? nodeByUid[cluster.cluster.taskNode.uid].unused
|
|
541
|
-
: false,
|
|
542
|
-
},
|
|
543
|
-
class: `ks-topology-${clusterColor}-border rounded p-2`,
|
|
544
|
-
} as any);
|
|
545
|
-
}
|
|
546
547
|
}
|
|
547
548
|
|
|
548
549
|
for (const node of flowGraph.nodes.concat(clusterToNode)) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
550
|
+
if (!hiddenNodes.includes(node.uid)) {
|
|
551
|
+
const dagreNode = dagreGraph.node(node.uid);
|
|
552
|
+
let nodeType = "task";
|
|
553
|
+
if (isClusterRootOrEnd(node)) {
|
|
554
|
+
nodeType = "dot";
|
|
555
|
+
} else if (node.type.includes("GraphTrigger")) {
|
|
556
|
+
nodeType = "trigger";
|
|
557
|
+
} else if (node.type === "collapsedcluster") {
|
|
558
|
+
nodeType = "collapsedcluster";
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const color = nodeColor(node, collapsed);
|
|
562
|
+
// If task type includes '$', it's an inner class so it's probably an internal class not supposed to be editable
|
|
563
|
+
// In such case, only the root task will be editable
|
|
564
|
+
const isReadOnlyTask =
|
|
565
|
+
isReadOnly ||
|
|
566
|
+
node.task?.type?.includes("$") ||
|
|
567
|
+
readOnlyUidPrefixes.some((prefix) =>
|
|
568
|
+
node.uid.startsWith(prefix + ".")
|
|
569
|
+
);
|
|
570
|
+
elements.push({
|
|
571
|
+
id: node.uid,
|
|
572
|
+
type: nodeType,
|
|
573
|
+
position: getNodePosition(
|
|
574
|
+
dagreNode,
|
|
575
|
+
clusterByNodeUid[node.uid]
|
|
576
|
+
? dagreGraph.node(clusterByNodeUid[node.uid].uid)
|
|
577
|
+
: undefined
|
|
578
|
+
),
|
|
579
|
+
style: {
|
|
580
|
+
width: getNodeWidth(node) + "px",
|
|
581
|
+
height: getNodeHeight(node) + "px",
|
|
582
|
+
},
|
|
583
|
+
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
|
584
|
+
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
|
585
|
+
parentNode: clusterByNodeUid[node.uid]
|
|
586
|
+
? clusterByNodeUid[node.uid].uid
|
|
587
|
+
: undefined,
|
|
588
|
+
draggable: nodeType === "task" ? !isReadOnlyTask : false,
|
|
589
|
+
data: {
|
|
590
|
+
node: node,
|
|
591
|
+
parent: clusterByNodeUid[node.uid] ? clusterByNodeUid[node.uid] : undefined,
|
|
592
|
+
namespace:
|
|
593
|
+
clusterByNodeUid[node.uid]?.taskNode?.task?.namespace ??
|
|
594
|
+
namespace,
|
|
595
|
+
flowId:
|
|
596
|
+
clusterByNodeUid[node.uid]?.taskNode?.task?.flowId ?? flowId,
|
|
597
|
+
isFlowable:
|
|
598
|
+
clusterByNodeUid[node.uid]?.uid === CLUSTER_PREFIX + node.uid &&
|
|
599
|
+
!node.type.endsWith("SubflowGraphTask"),
|
|
600
|
+
color: color,
|
|
601
|
+
expandable: isExpandableTask(
|
|
602
|
+
node,
|
|
603
|
+
clusterByNodeUid,
|
|
604
|
+
edgeReplacer,
|
|
605
|
+
enableSubflowInteraction
|
|
606
|
+
),
|
|
607
|
+
isReadOnly: isReadOnlyTask,
|
|
608
|
+
iconComponent: isCollapsedCluster(node)
|
|
609
|
+
? "lightning-bolt"
|
|
610
|
+
: null,
|
|
611
|
+
executionId: node.executionId,
|
|
612
|
+
unused: node.unused,
|
|
613
|
+
},
|
|
614
|
+
class:
|
|
615
|
+
node.type === "collapsedcluster"
|
|
616
|
+
? `ks-topology-${color}-border rounded`
|
|
617
|
+
: "",
|
|
618
|
+
});
|
|
558
619
|
}
|
|
559
|
-
|
|
560
|
-
const color = this.nodeColor(node, collapsed);
|
|
561
|
-
// If task type includes '$', it's an inner class so it's probably an internal class not supposed to be editable
|
|
562
|
-
// In such case, only the root task will be editable
|
|
563
|
-
const isReadOnlyTask =
|
|
564
|
-
isReadOnly ||
|
|
565
|
-
node.task?.type?.includes("$") ||
|
|
566
|
-
readOnlyUidPrefixes.some((prefix) =>
|
|
567
|
-
node.uid.startsWith(prefix + ".")
|
|
568
|
-
);
|
|
569
|
-
elements.push({
|
|
570
|
-
id: node.uid,
|
|
571
|
-
type: nodeType,
|
|
572
|
-
position: this.getNodePosition(
|
|
573
|
-
dagreNode,
|
|
574
|
-
clusterByNodeUid[node.uid]
|
|
575
|
-
? dagreGraph.node(clusterByNodeUid[node.uid].uid)
|
|
576
|
-
: undefined
|
|
577
|
-
),
|
|
578
|
-
style: {
|
|
579
|
-
width: this.getNodeWidth(node) + "px",
|
|
580
|
-
height: this.getNodeHeight(node) + "px",
|
|
581
|
-
},
|
|
582
|
-
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
|
583
|
-
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
|
584
|
-
parentNode: clusterByNodeUid[node.uid]
|
|
585
|
-
? clusterByNodeUid[node.uid].uid
|
|
586
|
-
: undefined,
|
|
587
|
-
draggable: nodeType === "task" ? !isReadOnlyTask : false,
|
|
588
|
-
data: {
|
|
589
|
-
node: node,
|
|
590
|
-
parent: clusterByNodeUid[node.uid] ? clusterByNodeUid[node.uid] : undefined,
|
|
591
|
-
namespace:
|
|
592
|
-
clusterByNodeUid[node.uid]?.taskNode?.task?.namespace ??
|
|
593
|
-
namespace,
|
|
594
|
-
flowId:
|
|
595
|
-
clusterByNodeUid[node.uid]?.taskNode?.task?.flowId ?? flowId,
|
|
596
|
-
isFlowable:
|
|
597
|
-
clusterByNodeUid[node.uid]?.uid === CLUSTER_PREFIX + node.uid &&
|
|
598
|
-
!node.type.endsWith("SubflowGraphTask"),
|
|
599
|
-
color: color,
|
|
600
|
-
expandable: this.isExpandableTask(
|
|
601
|
-
node,
|
|
602
|
-
clusterByNodeUid,
|
|
603
|
-
edgeReplacer,
|
|
604
|
-
enableSubflowInteraction
|
|
605
|
-
),
|
|
606
|
-
isReadOnly: isReadOnlyTask,
|
|
607
|
-
iconComponent: this.isCollapsedCluster(node)
|
|
608
|
-
? "lightning-bolt"
|
|
609
|
-
: null,
|
|
610
|
-
executionId: node.executionId,
|
|
611
|
-
unused: node.unused,
|
|
612
|
-
},
|
|
613
|
-
class:
|
|
614
|
-
node.type === "collapsedcluster"
|
|
615
|
-
? `ks-topology-${color}-border rounded`
|
|
616
|
-
: "",
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
620
|
}
|
|
620
621
|
|
|
621
622
|
const clusterRootTaskNodeUids = rawClusters
|
|
622
|
-
|
|
623
|
-
|
|
623
|
+
.filter((c) => c.taskNode)
|
|
624
|
+
.map((c) => c.taskNode.uid);
|
|
624
625
|
const edges = flowGraph.edges ?? [];
|
|
625
626
|
|
|
626
627
|
for (const edge of edges) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
628
|
+
const newEdge = replaceIfCollapsed(
|
|
629
|
+
edge.source,
|
|
630
|
+
edge.target,
|
|
631
|
+
edgeReplacer,
|
|
632
|
+
hiddenNodes
|
|
633
|
+
);
|
|
634
|
+
if (newEdge) {
|
|
635
|
+
const edgeColor = getEdgeColor(edge, nodeByUid, clusterByNodeUid);
|
|
636
|
+
elements.push({
|
|
637
|
+
id: newEdge.source + "|" + newEdge.target,
|
|
638
|
+
source: newEdge.source,
|
|
639
|
+
target: newEdge.target,
|
|
640
|
+
type: "edge",
|
|
641
|
+
markerEnd: isClusterRootOrEnd(nodeByUid[newEdge.target])
|
|
642
|
+
? ""
|
|
643
|
+
: {
|
|
644
|
+
id: "marker-" + (nodeByUid[newEdge.target].branchType ? nodeByUid[newEdge.target].branchType?.toLocaleLowerCase() : "custom"),
|
|
645
|
+
type: MarkerType.ArrowClosed,
|
|
646
|
+
color: edgeColor ? `var(--ks-border-${edgeColor})` : "var(--ks-topology-edge-color)"
|
|
647
|
+
},
|
|
648
|
+
data: {
|
|
649
|
+
haveAdd:
|
|
650
|
+
!isReadOnly &&
|
|
651
|
+
isAllowedEdit &&
|
|
652
|
+
haveAdd(
|
|
653
|
+
edge,
|
|
654
|
+
nodeByUid,
|
|
655
|
+
clusterRootTaskNodeUids,
|
|
656
|
+
readOnlyUidPrefixes
|
|
657
|
+
),
|
|
658
|
+
haveDashArray:
|
|
659
|
+
nodeByUid[edge.source].type.endsWith("GraphTrigger") ||
|
|
660
|
+
nodeByUid[edge.target].type.endsWith("GraphTrigger") ||
|
|
661
|
+
edge.source.startsWith(TRIGGERS_NODE_UID),
|
|
662
|
+
color: edgeColor,
|
|
663
|
+
unused: (edge as any).unused,
|
|
664
|
+
},
|
|
665
|
+
style: {
|
|
666
|
+
zIndex: 10,
|
|
667
|
+
},
|
|
668
|
+
});
|
|
669
|
+
}
|
|
669
670
|
}
|
|
670
671
|
|
|
671
672
|
return elements;
|
|
672
|
-
|
|
673
|
+
}
|
|
673
674
|
|
|
674
|
-
|
|
675
|
+
export function isClusterRootOrEnd(node: MinimalNode) {
|
|
675
676
|
return ["GraphClusterRoot", "GraphClusterFinally", "GraphClusterAfterExecution", "GraphClusterEnd"].some((s) =>
|
|
676
|
-
|
|
677
|
+
node.type.endsWith(s)
|
|
677
678
|
);
|
|
678
|
-
|
|
679
|
+
}
|
|
679
680
|
|
|
680
|
-
|
|
681
|
+
export function computeClusterColor(cluster: Cluster) {
|
|
681
682
|
if (cluster.uid === CLUSTER_PREFIX + TRIGGERS_NODE_UID) {
|
|
682
|
-
|
|
683
|
+
return "success";
|
|
683
684
|
}
|
|
684
685
|
|
|
685
686
|
if (cluster.type.endsWith("SubflowGraphCluster")) {
|
|
686
|
-
|
|
687
|
+
return "primary";
|
|
687
688
|
}
|
|
688
689
|
|
|
689
690
|
if (cluster.branchType === BranchType.ERROR) {
|
|
690
|
-
|
|
691
|
+
return "danger";
|
|
691
692
|
}
|
|
692
693
|
|
|
693
694
|
return "blue";
|
|
694
|
-
|
|
695
|
+
}
|
|
695
696
|
|
|
696
|
-
|
|
697
|
-
node:MinimalNode,
|
|
697
|
+
export function isExpandableTask(
|
|
698
|
+
node: MinimalNode,
|
|
698
699
|
clusterByNodeUid: Record<string, Cluster>,
|
|
699
700
|
edgeReplacer: EdgeReplacer,
|
|
700
701
|
enableSubflowInteraction?: boolean
|
|
701
|
-
|
|
702
|
+
) {
|
|
702
703
|
if (Object.values(edgeReplacer).includes(node.uid)) {
|
|
703
|
-
|
|
704
|
+
return true;
|
|
704
705
|
}
|
|
705
706
|
|
|
706
|
-
if (
|
|
707
|
-
|
|
707
|
+
if (isCollapsedCluster(node)) {
|
|
708
|
+
return true;
|
|
708
709
|
}
|
|
709
710
|
|
|
710
711
|
return (
|
|
711
|
-
|
|
712
|
-
|
|
712
|
+
node.type.endsWith("SubflowGraphTask") &&
|
|
713
|
+
clusterByNodeUid[node.uid]?.uid?.replace(CLUSTER_PREFIX, "") !==
|
|
713
714
|
node.uid &&
|
|
714
|
-
|
|
715
|
+
enableSubflowInteraction
|
|
715
716
|
);
|
|
716
|
-
|
|
717
|
-
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Get nodes that have no incoming edges, i.e., root nodes of the graph.
|
|
721
|
+
*/
|
|
722
|
+
export function getRootNodes(graph: FlowGraph) {
|
|
723
|
+
const nodeUIDs = graph.nodes.map((node) => node.uid);
|
|
724
|
+
const rootUIDs = nodeUIDs.filter((uid) => {
|
|
725
|
+
return !graph.edges.some((edge) => edge.target === uid);
|
|
726
|
+
});
|
|
727
|
+
return graph.nodes.filter((node) => rootUIDs.includes(node.uid));
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Get the edges connected as the source to a specific node. (outward facing arrows)
|
|
732
|
+
* @param graph The flow graph.
|
|
733
|
+
* @param nodeUid The UID of the node.
|
|
734
|
+
* @returns An array of edges connected to the node.
|
|
735
|
+
*/
|
|
736
|
+
export function getTargetNodesEdges(graph: FlowGraph, nodeUid?: string) {
|
|
737
|
+
if (!nodeUid) {
|
|
738
|
+
return undefined;
|
|
739
|
+
}
|
|
740
|
+
return graph.edges.filter((edge) => edge.source === nodeUid && edge.target);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Follow the graph from a specific node to find the next task nodes.
|
|
745
|
+
* This function traverses the graph until it finds a node that is not a cluster.
|
|
746
|
+
* @param graph
|
|
747
|
+
* @param initialNode The initial node to start the search from.
|
|
748
|
+
* @returns An array of the next task nodes found.
|
|
749
|
+
*/
|
|
750
|
+
export function getNextTaskNodes(graph: FlowGraph, initialNode: MinimalNode) {
|
|
751
|
+
let edges: GraphEdge[], nextTaskNodes: MinimalNode[], nodeUIDs: string[] = [initialNode.uid];
|
|
752
|
+
// loop until we find a node that is not a cluster
|
|
753
|
+
do {
|
|
754
|
+
// find all the edges that are connected to this task
|
|
755
|
+
edges = nodeUIDs.flatMap((uid) => getTargetNodesEdges(graph, uid)).filter(Boolean) as GraphEdge[];
|
|
756
|
+
// if there are no edges, return undefined
|
|
757
|
+
if (edges.length === 0) {
|
|
758
|
+
return [];
|
|
759
|
+
}
|
|
760
|
+
nodeUIDs = edges.map((edge) => edge.target);
|
|
761
|
+
nextTaskNodes = graph.nodes.filter((node) => nodeUIDs.includes(node.uid) && node.task);
|
|
762
|
+
} while (!nextTaskNodes.length);
|
|
763
|
+
|
|
764
|
+
return nextTaskNodes
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Check if the tasks in the current graph are identical to the previous graph until the specified task.
|
|
769
|
+
* @param previousGraph The graph from the previous execution.
|
|
770
|
+
* @param currentGraph The graph from the current execution.
|
|
771
|
+
* @param taskId The ID of the task to check.
|
|
772
|
+
* @returns True if all tasks are identical, false otherwise.
|
|
773
|
+
*/
|
|
774
|
+
export function areTasksIdenticalInGraphUntilTask(previousGraph: FlowGraph, currentGraph: FlowGraph, taskId?: string) {
|
|
775
|
+
if (!taskId) {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
let previousRootTaskNodes = getRootNodes(previousGraph);
|
|
780
|
+
let currentRootTaskNodes = getRootNodes(currentGraph);
|
|
781
|
+
|
|
782
|
+
// if the root nodes are not the same, we cannot compare
|
|
783
|
+
if (previousRootTaskNodes.length !== currentRootTaskNodes.length) {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// avoid infinite loop
|
|
788
|
+
let failIndex = 120
|
|
789
|
+
|
|
790
|
+
// walk the graph until we find the taskId in the current root task nodes
|
|
791
|
+
// or until we run out of nodes to compare
|
|
792
|
+
do {
|
|
793
|
+
currentRootTaskNodes = currentRootTaskNodes.flatMap((node) => getNextTaskNodes(currentGraph, node));
|
|
794
|
+
|
|
795
|
+
// stop if we find the taskId in the current root task nodes
|
|
796
|
+
if (currentRootTaskNodes.some((node: any) => node.task.id === taskId)) {
|
|
797
|
+
return true;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
previousRootTaskNodes = previousRootTaskNodes.flatMap((node) => getNextTaskNodes(previousGraph, node));
|
|
801
|
+
|
|
802
|
+
if (previousRootTaskNodes.length !== currentRootTaskNodes.length) {
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
for (const currentTaskNode of currentRootTaskNodes) {
|
|
807
|
+
const prevTaskNode = previousRootTaskNodes.find((taskNode) => taskNode.task?.id === currentTaskNode.task?.id);
|
|
808
|
+
const prevTaskValue = prevTaskNode?.task as Record<string, any> ?? {};
|
|
809
|
+
const currentTaskValue = currentTaskNode.task as Record<string, any> ?? {};
|
|
810
|
+
|
|
811
|
+
// if any member of the task is different, tasks are different
|
|
812
|
+
if (!prevTaskNode
|
|
813
|
+
|| Object.keys(prevTaskValue).length !== Object.keys(currentTaskValue).length
|
|
814
|
+
){
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
for (const key in currentTaskNode.task) {
|
|
818
|
+
if (prevTaskValue[key] !== currentTaskValue[key]) {
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
} while (previousRootTaskNodes.length && currentRootTaskNodes.length && failIndex-- > 0);
|
|
824
|
+
|
|
825
|
+
if (failIndex <= 0) {
|
|
826
|
+
console.warn("areTasksIdenticalInGraphUntilTask: Infinite loop detected, stopping comparison.");
|
|
827
|
+
return false;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return true;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* @deprecated prefer using VueFlowUtils directly for tree shaking
|
|
835
|
+
*/
|
|
836
|
+
export default {
|
|
837
|
+
isClusterRootOrEnd,
|
|
838
|
+
computeClusterColor,
|
|
839
|
+
isExpandableTask,
|
|
840
|
+
generateGraph,
|
|
841
|
+
generateDagreGraph,
|
|
842
|
+
getNodePosition,
|
|
843
|
+
getNodeWidth,
|
|
844
|
+
getNodeHeight,
|
|
845
|
+
isTaskNode,
|
|
846
|
+
isTriggerNode,
|
|
847
|
+
isCollapsedCluster,
|
|
848
|
+
replaceIfCollapsed,
|
|
849
|
+
cleanGraph,
|
|
850
|
+
flowHaveTasks,
|
|
851
|
+
nodeColor,
|
|
852
|
+
haveAdd,
|
|
853
|
+
getEdgeColor,
|
|
854
|
+
predecessorsEdge,
|
|
855
|
+
successorsEdge,
|
|
856
|
+
predecessorsNode,
|
|
857
|
+
successorsNode,
|
|
858
|
+
linkedElements,
|
|
859
|
+
}
|