From f37ad7ebb56cfc07c5d008440123ff84880cfb9f Mon Sep 17 00:00:00 2001 From: Pablo Cuadrado Date: Tue, 3 May 2022 15:38:14 -0300 Subject: [PATCH 1/2] Add visual Loop representation. --- ui/src/components/diagram/WorkflowDAG.js | 78 +++++++++++++++++++-- ui/src/components/diagram/WorkflowGraph.jsx | 12 ++++ ui/src/components/diagram/diagram.scss | 2 +- 3 files changed, 86 insertions(+), 6 deletions(-) diff --git a/ui/src/components/diagram/WorkflowDAG.js b/ui/src/components/diagram/WorkflowDAG.js index d4b709bdf2..31a5d802af 100644 --- a/ui/src/components/diagram/WorkflowDAG.js +++ b/ui/src/components/diagram/WorkflowDAG.js @@ -9,6 +9,7 @@ export default class WorkflowDAG { this.graph = new graphlib.Graph({ directed: true, compound: false }); this.taskResults = new Map(); + this.loopTaskRefs = []; this.constructGraph(); } @@ -38,9 +39,20 @@ export default class WorkflowDAG { else if (execution) { let isTerminated = false; for (let task of execution.tasks) { - if (task.taskType === "TERMINATE") isTerminated = true; - - this.addTaskResult(task.referenceTaskName, task); + if (task["taskType"] === "TERMINATE") isTerminated = true; + if (task["loopOverTask"]) { + let refTaskName = task["referenceTaskName"]; + let refTaskNameSansIter = refTaskName.substring( + 0, + refTaskName.lastIndexOf("__") + ); + let taskModel = { + ...task, + referenceTaskName: refTaskNameSansIter, + }; + this.addTaskResult(refTaskNameSansIter, taskModel); + } + this.addTaskResult(task["referenceTaskName"], task); } if (execution.status) { @@ -143,7 +155,11 @@ export default class WorkflowDAG { // Special case - When the antecedent of an executed node is a SWITCH, the edge may not necessarily be highlighted. // E.g. the default edge not taken. // SWITCH is the newer version of DECISION and DECISION is deprecated - if (antecedent.type === "SWITCH" || antecedent.type === "DECISION") { + if ( + antecedent.type === "SWITCH" || + antecedent.type === "DECISION" || + antecedents.type === "DO_WHILE" + ) { edgeParams.caseValue = getCaseValue( taskConfig.taskReferenceName, antecedent @@ -196,7 +212,7 @@ export default class WorkflowDAG { retval.push(decisionTask); // Empty default path } else { retval.push( - ...this.processTaskList(decisionTask.defaultCase, [decisionTask], null) + ...this.processTaskList(decisionTask.defaultCase, [decisionTask]) ); } @@ -246,6 +262,54 @@ export default class WorkflowDAG { } } + processDoWhileTask(doWhileTask, antecedents) { + console.assert(Array.isArray(antecedents)); + + let doWhileTaskResult = _.last( + this.taskResults.get(doWhileTask.taskReferenceName) + ); + let startDoWhileTask = { + ...doWhileTask, + taskReferenceName: doWhileTask.taskReferenceName + "-START", + }; + this.addTaskResult(startDoWhileTask.taskReferenceName, { + ...doWhileTaskResult, + }); + this.graph.setEdge( + doWhileTask.taskReferenceName + "-START", + doWhileTask.taskReferenceName, + { + caseValue: "LOOP", + executed: true, + } + ); + this.addVertex(startDoWhileTask, antecedents); + + antecedents = [startDoWhileTask]; + + const retval = []; + + if (_.isEmpty(doWhileTask.loopOver)) { + retval.push(doWhileTask); // Empty default path + } else { + this.loopTaskRefs.push(doWhileTask.taskReferenceName); + retval.push(...this.processTaskList(doWhileTask.loopOver, antecedents)); + this.loopTaskRefs.pop(); + } + // Set an edge from the do_while task to the first task + this.graph.setEdge( + doWhileTask.taskReferenceName, + doWhileTask.taskReferenceName + "-START", + { + caseValue: "LOOP", + executed: true, + } + ); + // Add do_while final state at the end + this.addVertex(doWhileTask, retval); + return [doWhileTask]; + } + processForkJoin(forkJoinTask, antecedents) { let outerForkTasks = forkJoinTask.forkTasks || []; @@ -281,6 +345,10 @@ export default class WorkflowDAG { return []; } + case "DO_WHILE": { + return this.processDoWhileTask(task, antecedents); + } + /* case "TERMINAL": case "JOIN": diff --git a/ui/src/components/diagram/WorkflowGraph.jsx b/ui/src/components/diagram/WorkflowGraph.jsx index 4da3ec0eb2..14721b56c5 100644 --- a/ui/src/components/diagram/WorkflowGraph.jsx +++ b/ui/src/components/diagram/WorkflowGraph.jsx @@ -414,6 +414,11 @@ class WorkflowGraph extends React.Component { retval.firstDfRef = v.firstDfRef; retval.shape = "stack"; break; + case "DO_WHILE": + retval = composeBarNode(v, "bidir"); + retval.label = `${retval.label} [DO_WHILE]`; + this.barNodes.push(v.ref); + break; default: retval.label = `${v.ref}\n(${v.name})`; retval.shape = "rect"; @@ -446,6 +451,13 @@ class WorkflowGraph extends React.Component { ); return _.first(points); }); + } else if (barNode.fanDir === "bidir") { + fanOut = this.graph.inEdges(barRef).map((e) => { + const points = parseSvgPath( + this.graph.edge(e).elem.querySelector("path").getAttribute("d") + ); + return _.last(points); + }); } else { fanOut = this.graph.inEdges(barRef).map((e) => { const points = parseSvgPath( diff --git a/ui/src/components/diagram/diagram.scss b/ui/src/components/diagram/diagram.scss index dfed898374..52b3ec23e4 100644 --- a/ui/src/components/diagram/diagram.scss +++ b/ui/src/components/diagram/diagram.scss @@ -149,7 +149,7 @@ $node-text-size: 12px; &.dimmed { rect { stroke: $light-color; - fill: #fff; + fill: #fff; } text { fill: $light-color; From 088f38a7b5b5244bf256d6adf7b2fa57cb4cdb4b Mon Sep 17 00:00:00 2001 From: Pablo Cuadrado Date: Tue, 3 May 2022 16:00:18 -0300 Subject: [PATCH 2/2] Check for predecessors before accessing parent. --- ui/src/pages/execution/Timeline.jsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/src/pages/execution/Timeline.jsx b/ui/src/pages/execution/Timeline.jsx index ee94ceb6c1..1b2348e27a 100644 --- a/ui/src/pages/execution/Timeline.jsx +++ b/ui/src/pages/execution/Timeline.jsx @@ -40,9 +40,10 @@ export default function TimelineComponent({ const items = tasks .filter((t) => t.startTime > 0 || t.endTime > 0) .map((task) => { - const dfParent = dag.graph - .predecessors(task.referenceTaskName) - .map((t) => dag.graph.node(t)) + const predecessors = dag.graph.predecessors(task.referenceTaskName); + + const dfParent = predecessors + ?.map((t) => dag.graph.node(t)) .find((t) => t.type === "FORK_JOIN_DYNAMIC"); const startTime = task.startTime > 0