From fb2169e5a4ce1d9e88f6ca22c34264f2d9bc26b1 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bianchi Date: Thu, 12 Dec 2024 16:05:19 +0100 Subject: [PATCH 1/5] fix(Dashboard): refactoring if transitions Signed-off-by: Jean-Baptiste Bianchi --- .../Synapse.Api.Http/Synapse.Api.Http.csproj | 6 +- .../Synapse.Core.Infrastructure.csproj | 15 +- src/core/Synapse.Core/Synapse.Core.csproj | 4 +- .../Synapse.Correlator.csproj | 16 +- .../Pages/Workflows/Create/State.cs | 1 - .../Services/WorkflowGraphBuilder.cs | 151 ++++++++---------- .../Synapse.Dashboard.csproj | 2 +- .../Synapse.Runner/Synapse.Runner.csproj | 12 +- .../Synapse.UnitTests.csproj | 10 +- 9 files changed, 101 insertions(+), 116 deletions(-) diff --git a/src/api/Synapse.Api.Http/Synapse.Api.Http.csproj b/src/api/Synapse.Api.Http/Synapse.Api.Http.csproj index 79dde15b2..7a14384e3 100644 --- a/src/api/Synapse.Api.Http/Synapse.Api.Http.csproj +++ b/src/api/Synapse.Api.Http/Synapse.Api.Http.csproj @@ -43,9 +43,9 @@ - - - + + + diff --git a/src/core/Synapse.Core.Infrastructure/Synapse.Core.Infrastructure.csproj b/src/core/Synapse.Core.Infrastructure/Synapse.Core.Infrastructure.csproj index b8ee60afc..96e58e80d 100644 --- a/src/core/Synapse.Core.Infrastructure/Synapse.Core.Infrastructure.csproj +++ b/src/core/Synapse.Core.Infrastructure/Synapse.Core.Infrastructure.csproj @@ -44,14 +44,13 @@ - - - - - - - - + + + + + + + diff --git a/src/core/Synapse.Core/Synapse.Core.csproj b/src/core/Synapse.Core/Synapse.Core.csproj index 49735faca..430413e88 100644 --- a/src/core/Synapse.Core/Synapse.Core.csproj +++ b/src/core/Synapse.Core/Synapse.Core.csproj @@ -67,8 +67,8 @@ - - + + diff --git a/src/correlator/Synapse.Correlator/Synapse.Correlator.csproj b/src/correlator/Synapse.Correlator/Synapse.Correlator.csproj index 5fd9a37a0..7edeb893d 100644 --- a/src/correlator/Synapse.Correlator/Synapse.Correlator.csproj +++ b/src/correlator/Synapse.Correlator/Synapse.Correlator.csproj @@ -36,14 +36,14 @@ - - - - - - - - + + + + + + + + diff --git a/src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/State.cs b/src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/State.cs index 38aeae13b..96c06c654 100644 --- a/src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/State.cs +++ b/src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/State.cs @@ -12,7 +12,6 @@ // limitations under the License. using ServerlessWorkflow.Sdk.Models; -using Synapse.Resources; namespace Synapse.Dashboard.Pages.Workflows.Create; diff --git a/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs b/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs index 8fd579c6e..0e2d3b534 100644 --- a/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs +++ b/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs @@ -17,7 +17,6 @@ using ServerlessWorkflow.Sdk.Models; using ServerlessWorkflow.Sdk.Models.Calls; using ServerlessWorkflow.Sdk.Models.Tasks; -using Synapse.Dashboard.Components.DocumentDetailsStateManagement; using System.Diagnostics; namespace Synapse.Dashboard.Services; @@ -32,7 +31,8 @@ public class WorkflowGraphBuilder(ILogger logger, IYamlSer : IWorkflowGraphBuilder { - const string _portSuffix = "-port"; + const string _clusterEntrySuffix = "-cluster-entry"; + const string _clusterExitSuffix = "-cluster-exit"; const string _trySuffix = "-try"; const string _catchSuffix = "-catch"; const double characterSize = 8d; @@ -78,65 +78,51 @@ public IGraphViewModel Build(WorkflowDefinition workflow) nextNode = (NodeViewModel)clusterViewModel.AllNodes.Values.First(); } } - this.BuildEdge(graph, startNode, nextNode); sw.Stop(); this.Logger.LogTrace("WorkflowGraphBuilder.Build took {elapsedTime} ms", sw.ElapsedMilliseconds); return graph; } - /// - /// Builds a new start - /// - /// A boolean indicating whether or not the node has successor - /// A new - protected virtual NodeViewModel BuildStartNode(bool hasSuccessor = false) => new StartNodeViewModel(hasSuccessor); + protected record TaskContext(ClusterViewModel? taskGroup, int taskIndex, string taskName, TaskDefinition definition) + { + public virtual int? Index { get; } = taskIndex; + public virtual string? Name { get; } = taskName; + public virtual TaskDefinition? Definition { get; } = definition; + } - /// - /// Returns the name, index and reference of the next node - /// - /// The rendering context for the task nodes - /// If true, skips with an if clause - /// A transition, if different from the context task definition's - /// The next task - protected TaskIdentity? GetNextTaskIdentity(TaskNodeRenderingContext context, bool ignoreConditionalTasks, string? transition = null) + protected record RenderingContext(WorkflowDefinition workflow, GraphViewModel graph, Map tasksList, INodeViewModel node, TaskContext? task = null, RenderingContext? parent = null, INodeViewModel? entryNode = null, INodeViewModel? exitNode = null) { - transition = !string.IsNullOrWhiteSpace(transition) ? transition : context.TaskDefinition.Then; - if (transition == FlowDirective.End) return null; - if (transition == FlowDirective.Exit) + public virtual WorkflowDefinition Workflow { get; } = workflow; + public virtual GraphViewModel Graph { get; } = graph; + public virtual Map TasksList { get; } = tasksList; + public virtual RenderingContext? Parent { get; } = parent; + public virtual TaskContext? Task { get; } = task; + public virtual INodeViewModel Node { get; } = node; + public virtual INodeViewModel EntryNode { get; } = entryNode; + public virtual INodeViewModel ExitNode { get; } = exitNode; + } + + protected virtual void BuildTransitions(RenderingContext context) + { + Map transitions = []; + MapEntry? nextTask = this.GetNextTask(context.TasksList, context.Task?.Name); + if (nextTask != null) { - if (context.ParentContext == null) + transitions.Add(nextTask); + while (!string.IsNullOrWhiteSpace(nextTask?.Value.If)) { - return null; + nextTask = this.GetNextTask(context.TasksList, nextTask.Key); + if (nextTask != null) + { + transitions.Add(nextTask); + } } - return this.GetNextTaskIdentity(context.ParentContext, ignoreConditionalTasks); } - var nextTaskName = string.IsNullOrWhiteSpace(transition) || transition == FlowDirective.Continue - ? context.Workflow.GetTaskAfter(new(context.TaskName, context.TaskDefinition), context.ParentReference, ignoreConditionalTasks)?.Key - : transition; - if (string.IsNullOrWhiteSpace(nextTaskName)) + foreach (var transition in transitions) { - if (context.ParentContext == null) - { - return null; - } - return this.GetNextTaskIdentity(context.ParentContext, ignoreConditionalTasks); - } - var nextTaskIndex = context.Workflow.IndexOf(nextTaskName, context.ParentReference); - var nextTaskReference = $"{context.ParentReference}/{nextTaskIndex}/{nextTaskName}"; - return new(nextTaskName, nextTaskIndex, nextTaskReference, context); - } + var transitionNode = this.BuildTaskNode(); + this.BuildEdge(context.Graph, context.Node, transitionNode); - /// - /// Gets a by reference in the provided - /// - /// The source - /// The reference to look for - /// - protected NodeViewModel GetNodeByReference(TaskNodeRenderingContext context, string reference) - { - if (context.Graph.AllClusters.ContainsKey(reference)) - { - return (NodeViewModel)context.Graph.AllClusters[reference].AllNodes.First().Value; } if (context.Graph.AllNodes.ContainsKey(reference)) { @@ -145,36 +131,32 @@ protected NodeViewModel GetNodeByReference(TaskNodeRenderingContext context, str throw new IndexOutOfRangeException($"Unable to find the task with reference '{reference}' in the provided context."); } - /// - /// Gets the next in the graph - /// - /// The rendering context for the task nodes - /// The current task node - /// If true, skips with an if clause - /// A transition, if different from the context task definition's - /// The next task - /// - protected NodeViewModel GetNextNode(TaskNodeRenderingContext context, NodeViewModel currentNode, bool ignoreConditionalTasks = false, string? transition = null) + protected virtual MapEntry? GetNextTask(Map tasksList, string? taskName, string? transition = null) //RenderingContext context, string? transition = null) { - var nextTaskIdentity = this.GetNextTaskIdentity(context, ignoreConditionalTasks, transition); - if (nextTaskIdentity == null) + if (transition == FlowDirective.End || transition == FlowDirective.Exit) return null; + int index; + if (!string.IsNullOrWhiteSpace(transition) && transition != FlowDirective.Continue) { - return context.EndNode; + index = tasksList.Keys.ToList().IndexOf(transition); } - var nextTask = context.Workflow.GetComponent(nextTaskIdentity.Reference) ?? throw new Exception($"Failed to find the task at '{nextTaskIdentity.Reference}' in workflow '{context.Workflow.Document.Name}.{context.Workflow.Document.Namespace}:{context.Workflow.Document.Version}'"); - if (!context.Graph.AllNodes.ContainsKey(nextTaskIdentity.Reference) && !context.Graph.AllClusters.ContainsKey(nextTaskIdentity.Reference)) + else if (!string.IsNullOrWhiteSpace(taskName)) { - this.BuildTaskNode(new(nextTaskIdentity.Context.Workflow, nextTaskIdentity.Context.Graph, nextTaskIdentity.Index, nextTaskIdentity.Name, nextTask, nextTaskIdentity.Context.TaskGroup, nextTaskIdentity.Context.ParentReference, nextTaskIdentity.Context.ParentContext, nextTaskIdentity.Context.EndNode, currentNode)); + index = tasksList.Keys.ToList().IndexOf(taskName) + 1; } - if (string.IsNullOrEmpty(nextTask.If)) + else { - return this.GetNodeByReference(context, nextTaskIdentity.Reference); + index = 0; } - var nextNode = this.GetNodeByReference(context, nextTaskIdentity.Reference); - this.BuildEdge(context.Graph, currentNode, nextNode); - return this.GetNextNode(context, currentNode, true, transition); + return tasksList.ElementAt(index); } + /// + /// Builds a new start + /// + /// A boolean indicating whether or not the node has successor + /// A new + protected virtual NodeViewModel BuildStartNode(bool hasSuccessor = false) => new StartNodeViewModel(hasSuccessor); + /// /// Builds a new for the specified task /// @@ -263,25 +245,30 @@ protected virtual NodeViewModel BuildDoTaskNode(TaskNodeRenderingContext 1 ? "s" : "")}"); - var port = new PortNodeViewModel(context.TaskReference + _portSuffix); - cluster.AddChild(port); + var entryPort = new PortNodeViewModel(context.TaskReference + _clusterEntrySuffix); + var exitPort = new PortNodeViewModel(context.TaskReference + _clusterExitSuffix); + cluster.AddChild(entryPort); + cluster.AddChild(exitPort); if (context.TaskGroup == null) context.Graph.AddCluster(cluster); else context.TaskGroup.AddChild(cluster); var innerContext = new TaskNodeRenderingContext(context.Workflow, context.Graph, 0, context.TaskDefinition.Do.First().Key, context.TaskDefinition.Do.First().Value, cluster, context.TaskReference + "/do", context, context.EndNode, context.PreviousNode); this.BuildTaskNode(innerContext); if (taskCount > 0) { - var firstDoNode = (NodeViewModel)cluster.AllNodes.Skip(1).First().Value; - this.BuildEdge(context.Graph, port, firstDoNode); + var firstDoNode = (NodeViewModel)cluster.AllNodes.Skip(2).First().Value; + var lastDoNode = (NodeViewModel)cluster.AllNodes.Last().Value; + this.BuildEdge(context.Graph, entryPort, firstDoNode); + this.BuildEdge(context.Graph, lastDoNode, exitPort); if (taskCount > 1 && !string.IsNullOrWhiteSpace(context.TaskDefinition.Do.First().Value.If)) { - this.BuildEdge(context.Graph, port, this.GetNextNode(innerContext, firstDoNode)); + this.BuildEdge(context.Graph, entryPort, this.GetNextNode(innerContext, firstDoNode)); } } else { - this.BuildEdge(context.Graph, port, this.GetNextNode(context, cluster)); + this.BuildEdge(context.Graph, entryPort, exitPort); } + this.BuildEdge(context.Graph, exitPort, this.GetNextNode(context, cluster)); return cluster; } @@ -324,7 +311,7 @@ protected virtual NodeViewModel BuildForTaskNode(TaskNodeRenderingContext 1 ? "s" : "")}"); - var containerPort = new PortNodeViewModel(context.TaskReference + _portSuffix); + var containerPort = new PortNodeViewModel(context.TaskReference + _clusterEntrySuffix); containerCluster.AddChild(containerPort); if (context.TaskGroup == null) context.Graph.AddCluster(containerCluster); else context.TaskGroup.AddChild(containerCluster); var tryCluster = new TryNodeViewModel(context.TaskReference + _trySuffix, context.TaskName, string.Empty); - var tryPort = new PortNodeViewModel(context.TaskReference + _trySuffix + _portSuffix); + var tryPort = new PortNodeViewModel(context.TaskReference + _trySuffix + _clusterEntrySuffix); tryCluster.AddChild(tryPort); containerCluster.AddChild(tryCluster); this.BuildEdge(context.Graph, containerPort, tryPort); @@ -531,7 +518,7 @@ protected virtual NodeViewModel BuildTryTaskNode(TaskNodeRenderingContext - + diff --git a/src/runner/Synapse.Runner/Synapse.Runner.csproj b/src/runner/Synapse.Runner/Synapse.Runner.csproj index f9225b9e9..cb99ab34d 100644 --- a/src/runner/Synapse.Runner/Synapse.Runner.csproj +++ b/src/runner/Synapse.Runner/Synapse.Runner.csproj @@ -59,12 +59,12 @@ - - - - - - + + + + + + diff --git a/tests/Synapse.UnitTests/Synapse.UnitTests.csproj b/tests/Synapse.UnitTests/Synapse.UnitTests.csproj index f66705d31..0eb0b5554 100644 --- a/tests/Synapse.UnitTests/Synapse.UnitTests.csproj +++ b/tests/Synapse.UnitTests/Synapse.UnitTests.csproj @@ -19,11 +19,11 @@ - - - - - + + + + + From 88887f70a6cd4edd6101049e52840d4f120a0841 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bianchi Date: Thu, 12 Dec 2024 16:06:30 +0100 Subject: [PATCH 2/5] fix(Dashboard): fixed if transitions Signed-off-by: Jean-Baptiste Bianchi --- .../WorkflowDiagram/StartNodeViewModel.cs | 8 +- .../Services/WorkflowGraphBuilder.cs | 364 +++++++++--------- 2 files changed, 185 insertions(+), 187 deletions(-) diff --git a/src/dashboard/Synapse.Dashboard/Components/WorkflowDiagram/StartNodeViewModel.cs b/src/dashboard/Synapse.Dashboard/Components/WorkflowDiagram/StartNodeViewModel.cs index e04dd9a05..2c997f1dd 100644 --- a/src/dashboard/Synapse.Dashboard/Components/WorkflowDiagram/StartNodeViewModel.cs +++ b/src/dashboard/Synapse.Dashboard/Components/WorkflowDiagram/StartNodeViewModel.cs @@ -18,13 +18,7 @@ namespace Synapse.Dashboard.Components; /// /// Represents the object that holds the data required to render the view of a workflow's start node /// -public class StartNodeViewModel(bool hasSuccessor = false) +public class StartNodeViewModel() : WorkflowNodeViewModel("start-node", new() { CssClass = "start-node", Shape = NodeShape.Circle, Width = WorkflowGraphBuilder.StartEndNodeRadius, Height = WorkflowGraphBuilder.StartEndNodeRadius }) { - - /// - /// Gets a boolean indicating whether or not the node has a successor - /// - public bool HasSuccessor { get; } = hasSuccessor; - } \ No newline at end of file diff --git a/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs b/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs index 0e2d3b534..ce5272026 100644 --- a/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs +++ b/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs @@ -31,8 +31,8 @@ public class WorkflowGraphBuilder(ILogger logger, IYamlSer : IWorkflowGraphBuilder { - const string _clusterEntrySuffix = "-cluster-entry"; - const string _clusterExitSuffix = "-cluster-exit"; + const string _clusterEntrySuffix = "-cluster-entry-port"; + const string _clusterExitSuffix = "-cluster-exit-port"; const string _trySuffix = "-try"; const string _catchSuffix = "-catch"; const double characterSize = 8d; @@ -62,67 +62,58 @@ public IGraphViewModel Build(WorkflowDefinition workflow) { ArgumentNullException.ThrowIfNull(workflow); Stopwatch sw = Stopwatch.StartNew(); - var isEmpty = workflow.Do.Count < 1; var graph = new GraphViewModel(); - //graph.EnableProfiling = true; - var startNode = this.BuildStartNode(!isEmpty); + var startNode = this.BuildStartNode(); var endNode = this.BuildEndNode(); graph.AddNode(startNode); graph.AddNode(endNode); - var nextNode = endNode; - if (!isEmpty) - { - nextNode = this.BuildTaskNode(new(workflow, graph, 0, workflow.Do.First().Key, workflow.Do.First().Value, null, "/do", null, endNode, startNode)); - if (nextNode is ClusterViewModel clusterViewModel) - { - nextNode = (NodeViewModel)clusterViewModel.AllNodes.Values.First(); - } - } + this.BuildTransitions(startNode, new(workflow, graph, workflow.Do, null, null, null, null, "/do", null, startNode, endNode)); sw.Stop(); this.Logger.LogTrace("WorkflowGraphBuilder.Build took {elapsedTime} ms", sw.ElapsedMilliseconds); return graph; } - protected record TaskContext(ClusterViewModel? taskGroup, int taskIndex, string taskName, TaskDefinition definition) - { - public virtual int? Index { get; } = taskIndex; - public virtual string? Name { get; } = taskName; - public virtual TaskDefinition? Definition { get; } = definition; - } - - protected record RenderingContext(WorkflowDefinition workflow, GraphViewModel graph, Map tasksList, INodeViewModel node, TaskContext? task = null, RenderingContext? parent = null, INodeViewModel? entryNode = null, INodeViewModel? exitNode = null) + /// + /// Gets the anchor used to attach the provided node. + /// + /// The node to get the anchor of + /// The type of port the anchor should be + /// If the node is a cluster, the corresponding port, the node itself otherwise + protected virtual INodeViewModel GetNodeAnchor(INodeViewModel node, NodePortType portType) { - public virtual WorkflowDefinition Workflow { get; } = workflow; - public virtual GraphViewModel Graph { get; } = graph; - public virtual Map TasksList { get; } = tasksList; - public virtual RenderingContext? Parent { get; } = parent; - public virtual TaskContext? Task { get; } = task; - public virtual INodeViewModel Node { get; } = node; - public virtual INodeViewModel EntryNode { get; } = entryNode; - public virtual INodeViewModel ExitNode { get; } = exitNode; + if (node is IClusterViewModel cluster) + { + return portType == NodePortType.Entry ? cluster.Children.First().Value : cluster.Children.Skip(1).First().Value; + } + return node; } - protected virtual void BuildTransitions(RenderingContext context) + /// + /// Gets the identify of the next task for the provided task/transition + /// + /// The list of tasks to fetch the next task in + /// The current task + /// A specific transition, if any (use for switch cases) + /// The next task identity + protected virtual TaskIdentity? GetNextTask(Map tasksList, string? currentTask, string? transition = null) { - Map transitions = []; - MapEntry? nextTask = this.GetNextTask(context.TasksList, context.Task?.Name); - if (nextTask != null) + if (transition == FlowDirective.End || transition == FlowDirective.Exit) return null; + int index; + if (!string.IsNullOrWhiteSpace(transition) && transition != FlowDirective.Continue) { - transitions.Add(nextTask); - while (!string.IsNullOrWhiteSpace(nextTask?.Value.If)) + index = tasksList.Keys.ToList().IndexOf(transition); + } + else if (!string.IsNullOrWhiteSpace(currentTask)) + { + index = tasksList.Keys.ToList().IndexOf(currentTask) + 1; + if (index >= tasksList.Count) { - nextTask = this.GetNextTask(context.TasksList, nextTask.Key); - if (nextTask != null) - { - transitions.Add(nextTask); - } + return null; } } - foreach (var transition in transitions) + else { - var transitionNode = this.BuildTaskNode(); - this.BuildEdge(context.Graph, context.Node, transitionNode); - + index = 0; } if (context.Graph.AllNodes.ContainsKey(reference)) { @@ -131,40 +122,57 @@ protected virtual void BuildTransitions(RenderingContext context) throw new IndexOutOfRangeException($"Unable to find the task with reference '{reference}' in the provided context."); } - protected virtual MapEntry? GetNextTask(Map tasksList, string? taskName, string? transition = null) //RenderingContext context, string? transition = null) + /// + /// Builds all possible transitions from the specified node + /// + /// The node to transition from + /// The rendering context of the provided node + protected virtual void BuildTransitions(INodeViewModel node, TaskNodeRenderingContext context) { - if (transition == FlowDirective.End || transition == FlowDirective.Exit) return null; - int index; - if (!string.IsNullOrWhiteSpace(transition) && transition != FlowDirective.Continue) - { - index = tasksList.Keys.ToList().IndexOf(transition); - } - else if (!string.IsNullOrWhiteSpace(taskName)) + List transitions = []; + TaskIdentity? nextTask = this.GetNextTask(context.TasksList, context.TaskName); + transitions.Add(nextTask); + while (!string.IsNullOrWhiteSpace(nextTask?.Definition.If)) { - index = tasksList.Keys.ToList().IndexOf(taskName) + 1; + nextTask = this.GetNextTask(context.TasksList, nextTask.Name); + transitions.Add(nextTask); } - else + foreach (var transition in transitions.Distinct()) { - index = 0; + if (transition != null) + { + var transitionNode = this.BuildTaskNode(new(context.Workflow, context.Graph, context.TasksList, transition.Index, transition.Name, transition.Definition, context.TaskGroup, context.ParentReference, context.ParentContext, context.EntryNode, context.ExitNode)); + this.BuildEdge(context.Graph, this.GetNodeAnchor(node, NodePortType.Exit), GetNodeAnchor(transitionNode, NodePortType.Entry)); + } + else + { + this.BuildEdge(context.Graph, this.GetNodeAnchor(node, NodePortType.Exit), context.ExitNode); + } } - return tasksList.ElementAt(index); } /// /// Builds a new start /// - /// A boolean indicating whether or not the node has successor /// A new - protected virtual NodeViewModel BuildStartNode(bool hasSuccessor = false) => new StartNodeViewModel(hasSuccessor); + protected virtual NodeViewModel BuildStartNode() => new StartNodeViewModel(); /// /// Builds a new for the specified task /// /// The rendering context for the task node /// A new - protected NodeViewModel BuildTaskNode(TaskNodeRenderingContext context) + protected INodeViewModel BuildTaskNode(TaskNodeRenderingContext context) { ArgumentNullException.ThrowIfNull(context); + if (context.Graph.AllNodes.ContainsKey(context.TaskReference)) + { + return context.Graph.AllNodes[context.TaskReference]; + } + if (context.Graph.AllClusters.ContainsKey(context.TaskReference)) + { + return context.Graph.AllClusters[context.TaskReference]; + } return context.TaskDefinition switch { CallTaskDefinition => this.BuildCallTaskNode(context.OfType()), @@ -180,7 +188,7 @@ protected NodeViewModel BuildTaskNode(TaskNodeRenderingContext context) SwitchTaskDefinition => this.BuildSwitchTaskNode(context.OfType()), TryTaskDefinition => this.BuildTryTaskNode(context.OfType()), WaitTaskDefinition => this.BuildWaitTaskNode(context.OfType()), - _ => throw new NotSupportedException($"The specified task type '{context.TaskDefinition.GetType()}' is not supported") + _ => throw new NotSupportedException($"The specified task type '{context.TaskDefinition?.GetType()}' is not supported") } ?? throw new Exception($"Unable to define a last node for task '{context.TaskName}'"); } @@ -228,10 +236,10 @@ protected virtual NodeViewModel BuildCallTaskNode(TaskNodeRenderingContext 1 ? "s" : "")}"); + var cluster = new DoTaskNodeViewModel(context.TaskReference, context.TaskName!, $"{taskCount} task{(taskCount > 1 ? "s" : "")}"); var entryPort = new PortNodeViewModel(context.TaskReference + _clusterEntrySuffix); var exitPort = new PortNodeViewModel(context.TaskReference + _clusterExitSuffix); cluster.AddChild(entryPort); cluster.AddChild(exitPort); if (context.TaskGroup == null) context.Graph.AddCluster(cluster); else context.TaskGroup.AddChild(cluster); - var innerContext = new TaskNodeRenderingContext(context.Workflow, context.Graph, 0, context.TaskDefinition.Do.First().Key, context.TaskDefinition.Do.First().Value, cluster, context.TaskReference + "/do", context, context.EndNode, context.PreviousNode); - this.BuildTaskNode(innerContext); - if (taskCount > 0) - { - var firstDoNode = (NodeViewModel)cluster.AllNodes.Skip(2).First().Value; - var lastDoNode = (NodeViewModel)cluster.AllNodes.Last().Value; - this.BuildEdge(context.Graph, entryPort, firstDoNode); - this.BuildEdge(context.Graph, lastDoNode, exitPort); - if (taskCount > 1 && !string.IsNullOrWhiteSpace(context.TaskDefinition.Do.First().Value.If)) - { - this.BuildEdge(context.Graph, entryPort, this.GetNextNode(innerContext, firstDoNode)); - } - } - else - { - this.BuildEdge(context.Graph, entryPort, exitPort); - } - this.BuildEdge(context.Graph, exitPort, this.GetNextNode(context, cluster)); + var innerContext = new TaskNodeRenderingContext(context.Workflow, context.Graph, context.TaskDefinition.Do, null, null, null, cluster, context.TaskReference + "/do", context, entryPort, exitPort); + this.BuildTransitions(entryPort, innerContext); + this.BuildTransitions(cluster, context); return cluster; } @@ -280,10 +273,10 @@ protected virtual NodeViewModel BuildDoTaskNode(TaskNodeRenderingContext context) { ArgumentNullException.ThrowIfNull(context); - var node = new EmitTaskNodeViewModel(context.TaskReference, context.TaskName, this.YamlSerializer.SerializeToText(context.TaskDefinition.Emit.Event.With)); + var node = new EmitTaskNodeViewModel(context.TaskReference, context.TaskName!, this.YamlSerializer.SerializeToText(context.TaskDefinition.Emit.Event.With)); if (context.TaskGroup == null) context.Graph.AddNode(node); else context.TaskGroup.AddChild(node); - this.BuildEdge(context.Graph, node, this.GetNextNode(context, node)); + this.BuildTransitions(node, context); return node; } @@ -295,10 +288,10 @@ protected virtual NodeViewModel BuildEmitTaskNode(TaskNodeRenderingContext context) { ArgumentNullException.ThrowIfNull(context); - var node = new ExtensionTaskNodeViewModel(context.TaskReference, context.TaskName); + var node = new ExtensionTaskNodeViewModel(context.TaskReference, context.TaskName!); if (context.TaskGroup == null) context.Graph.AddNode(node); else context.TaskGroup.AddChild(node); - this.BuildEdge(context.Graph, node, this.GetNextNode(context, node)); + this.BuildTransitions(node, context); return node; } @@ -310,20 +303,16 @@ protected virtual NodeViewModel BuildExtensionTaskNode(TaskNodeRenderingContext< protected virtual NodeViewModel BuildForTaskNode(TaskNodeRenderingContext context) { ArgumentNullException.ThrowIfNull(context); - var cluster = new ForTaskNodeViewModel(context.TaskReference, context.TaskName, this.YamlSerializer.SerializeToText(context.TaskDefinition.For)); - var port = new PortNodeViewModel(context.TaskReference + _clusterEntrySuffix); - cluster.AddChild(port); + var cluster = new ForTaskNodeViewModel(context.TaskReference, context.TaskName!, this.YamlSerializer.SerializeToText(context.TaskDefinition.For)); + var entryPort = new PortNodeViewModel(context.TaskReference + _clusterEntrySuffix); + var exitPort = new PortNodeViewModel(context.TaskReference + _clusterExitSuffix); + cluster.AddChild(entryPort); + cluster.AddChild(exitPort); if (context.TaskGroup == null) context.Graph.AddCluster(cluster); else context.TaskGroup.AddChild(cluster); - this.BuildTaskNode(new(context.Workflow, context.Graph, 0, context.TaskDefinition.Do.First().Key, context.TaskDefinition.Do.First().Value, cluster, context.TaskReference + "/do", context, context.EndNode, context.PreviousNode)); - if (context.TaskDefinition.Do.Count > 0) - { - this.BuildEdge(context.Graph, port, (NodeViewModel)cluster.AllNodes.Skip(1).First().Value); - } - else - { - this.BuildEdge(context.Graph, port, this.GetNextNode(context, cluster)); - } + var innerContext = new TaskNodeRenderingContext(context.Workflow, context.Graph, context.TaskDefinition.Do, null, null, null, cluster, context.TaskReference + "/do", context, entryPort, exitPort); + this.BuildTransitions(entryPort, innerContext); + this.BuildTransitions(cluster, context); return cluster; } @@ -335,16 +324,17 @@ protected virtual NodeViewModel BuildForTaskNode(TaskNodeRenderingContext context) { ArgumentNullException.ThrowIfNull(context); - var cluster = new ForkTaskNodeViewModel(context.TaskReference, context.TaskName, this.YamlSerializer.SerializeToText(context.TaskDefinition.Fork)); + var cluster = new ForkTaskNodeViewModel(context.TaskReference, context.TaskName!, this.YamlSerializer.SerializeToText(context.TaskDefinition.Fork)); var entryPort = new PortNodeViewModel(context.TaskReference + _clusterEntrySuffix); - var exitPort = new PortNodeViewModel(context.TaskReference + "-exit" + _clusterExitSuffix); + var exitPort = new PortNodeViewModel(context.TaskReference + _clusterExitSuffix); cluster.AddChild(entryPort); + cluster.AddChild(exitPort); if (context.TaskGroup == null) context.Graph.AddCluster(cluster); else context.TaskGroup.AddChild(cluster); for (int i = 0, c = context.TaskDefinition.Fork.Branches.Count; i context) { ArgumentNullException.ThrowIfNull(context); - var node = new ListenTaskNodeViewModel(context.TaskReference, context.TaskName, this.YamlSerializer.SerializeToText(context.TaskDefinition.Listen)); + var node = new ListenTaskNodeViewModel(context.TaskReference, context.TaskName!, this.YamlSerializer.SerializeToText(context.TaskDefinition.Listen)); if (context.TaskGroup == null) context.Graph.AddNode(node); else context.TaskGroup.AddChild(node); - this.BuildEdge(context.Graph, node, this.GetNextNode(context, node)); + this.BuildTransitions(node, context); return node; } @@ -384,10 +373,10 @@ protected virtual NodeViewModel BuildListenTaskNode(TaskNodeRenderingContext context) { ArgumentNullException.ThrowIfNull(context); - var node = new RaiseTaskNodeViewModel(context.TaskReference, context.TaskName, this.YamlSerializer.SerializeToText(context.TaskDefinition.Raise.Error)); + var node = new RaiseTaskNodeViewModel(context.TaskReference, context.TaskName!, this.YamlSerializer.SerializeToText(context.TaskDefinition.Raise.Error)); if (context.TaskGroup == null) context.Graph.AddNode(node); else context.TaskGroup.AddChild(node); - this.BuildEdge(context.Graph, node, this.GetNextNode(context, node)); + this.BuildTransitions(node, context); return node; } @@ -431,10 +420,10 @@ protected virtual NodeViewModel BuildRunTaskNode(TaskNodeRenderingContext context) { ArgumentNullException.ThrowIfNull(context); - var node = new SetTaskNodeViewModel(context.TaskReference, context.TaskName, this.YamlSerializer.SerializeToText(context.TaskDefinition.Set)); + var node = new SetTaskNodeViewModel(context.TaskReference, context.TaskName!, this.YamlSerializer.SerializeToText(context.TaskDefinition.Set)); if (context.TaskGroup == null) context.Graph.AddNode(node); else context.TaskGroup.AddChild(node); - this.BuildEdge(context.Graph, node, this.GetNextNode(context, node)); + this.BuildTransitions(node, context); return node; } @@ -461,17 +450,30 @@ protected virtual NodeViewModel BuildSetTaskNode(TaskNodeRenderingContext context) { ArgumentNullException.ThrowIfNull(context); - var node = new SwitchTaskNodeViewModel(context.TaskReference, context.TaskName, this.YamlSerializer.SerializeToText(context.TaskDefinition.Switch)); + var node = new SwitchTaskNodeViewModel(context.TaskReference, context.TaskName!, this.YamlSerializer.SerializeToText(context.TaskDefinition.Switch)); if (context.TaskGroup == null) context.Graph.AddNode(node); else context.TaskGroup.AddChild(node); foreach (var switchCase in context.TaskDefinition.Switch) { - var switchCaseNode = this.GetNextNode(context, node, false, switchCase.Value.Then); - this.BuildEdge(context.Graph, node, switchCaseNode, switchCase.Key); + var switchCaseTask = this.GetNextTask(context.TasksList, context.TaskName, switchCase.Value.Then)!; + var switchCaseNode = this.BuildTaskNode(new( + context.Workflow, + context.Graph, + context.TasksList, + switchCaseTask.Index, + switchCaseTask.Name, + switchCaseTask.Definition, + context.TaskGroup, + context.ParentReference, + context.ParentContext, + context.EntryNode, + context.ExitNode + )); + this.BuildEdge(context.Graph, this.GetNodeAnchor(node, NodePortType.Exit), GetNodeAnchor(switchCaseNode, NodePortType.Entry)); } if (!context.TaskDefinition.Switch.Any(switchCase => string.IsNullOrEmpty(switchCase.Value.When))) { - this.BuildEdge(context.Graph, node, this.GetNextNode(context, node)); + this.BuildTransitions(node, context); } return node; } @@ -485,47 +487,46 @@ protected virtual NodeViewModel BuildTryTaskNode(TaskNodeRenderingContext 1 ? "s" : "")}"); - var containerPort = new PortNodeViewModel(context.TaskReference + _clusterEntrySuffix); - containerCluster.AddChild(containerPort); + var containerCluster = new TryTaskNodeViewModel(context.TaskReference, context.TaskName!, $"{taskCount} task{(taskCount > 1 ? "s" : "")}"); + var containerEntryPort = new PortNodeViewModel(context.TaskReference + _clusterEntrySuffix); + var containerExitPort = new PortNodeViewModel(context.TaskReference + _clusterExitSuffix); + containerCluster.AddChild(containerEntryPort); + containerCluster.AddChild(containerExitPort); if (context.TaskGroup == null) context.Graph.AddCluster(containerCluster); else context.TaskGroup.AddChild(containerCluster); - var tryCluster = new TryNodeViewModel(context.TaskReference + _trySuffix, context.TaskName, string.Empty); - var tryPort = new PortNodeViewModel(context.TaskReference + _trySuffix + _clusterEntrySuffix); - tryCluster.AddChild(tryPort); + var tryCluster = new TryNodeViewModel(context.TaskReference + _trySuffix, context.TaskName!, string.Empty); + var tryEntryPort = new PortNodeViewModel(context.TaskReference + _trySuffix + _clusterEntrySuffix); + var tryExitPort = new PortNodeViewModel(context.TaskReference + _trySuffix + _clusterExitSuffix); + tryCluster.AddChild(tryEntryPort); + tryCluster.AddChild(tryExitPort); containerCluster.AddChild(tryCluster); - this.BuildEdge(context.Graph, containerPort, tryPort); - var innerContext = new TaskNodeRenderingContext(context.Workflow, context.Graph, 0, context.TaskDefinition.Try.First().Key, context.TaskDefinition.Try.First().Value, tryCluster, context.TaskReference + "/try", context, context.EndNode, context.PreviousNode); - this.BuildTaskNode(innerContext); - if (taskCount > 0) - { - var firstNode = (NodeViewModel)tryCluster.AllNodes.Skip(1).First().Value; - this.BuildEdge(context.Graph, tryPort, firstNode); - if (taskCount > 1 && !string.IsNullOrWhiteSpace(context.TaskDefinition.Try.First().Value.If)) - { - this.BuildEdge(context.Graph, tryPort, this.GetNextNode(innerContext, firstNode)); - } - } + this.BuildEdge(context.Graph, containerEntryPort, tryEntryPort); + var innerContext = new TaskNodeRenderingContext(context.Workflow, context.Graph, context.TaskDefinition.Try, null, null, null, tryCluster, context.TaskReference + "/try", context, tryEntryPort, tryExitPort); + this.BuildTransitions(tryEntryPort, innerContext); + var catchContent = this.YamlSerializer.SerializeToText(context.TaskDefinition.Catch); if (context.TaskDefinition.Catch.Do == null || context.TaskDefinition.Catch.Do.Count == 0) { - var catchNode = new CatchNodeViewModel(context.TaskReference + _catchSuffix, context.TaskName, catchContent); + var catchNode = new CatchNodeViewModel(context.TaskReference + _catchSuffix, context.TaskName!, catchContent); containerCluster.AddChild(catchNode); - this.BuildEdge(context.Graph, tryCluster.AllNodes.Values.Last(), catchNode); - this.BuildEdge(context.Graph, catchNode, this.GetNextNode(context, containerCluster)); + this.BuildEdge(context.Graph, tryExitPort, catchNode); + this.BuildEdge(context.Graph, catchNode, containerExitPort); } else { var catchCluster = new CatchDoNodeViewModel(context.TaskReference + _catchSuffix, context.TaskName, catchContent); - var catchPort = new PortNodeViewModel(context.TaskReference + _catchSuffix + _clusterEntrySuffix); - catchCluster.AddChild(catchPort); + var catchEntryPort = new PortNodeViewModel(context.TaskReference + _catchSuffix + _clusterEntrySuffix); + var catchExitPort = new PortNodeViewModel(context.TaskReference + _catchSuffix + _clusterExitSuffix); + catchCluster.AddChild(catchEntryPort); + catchCluster.AddChild(catchExitPort); containerCluster.AddChild(catchCluster); - this.BuildEdge(context.Graph, tryCluster.AllNodes.Values.Last(), catchPort); - this.BuildTaskNode(new(context.Workflow, context.Graph, 0, context.TaskDefinition.Catch.Do.First().Key, context.TaskDefinition.Catch.Do.First().Value, catchCluster, context.TaskReference + "/catch/do", context, context.EndNode, context.PreviousNode)); - this.BuildEdge(context.Graph, catchPort, catchCluster.AllNodes.Values.Skip(1).First()); - this.BuildEdge(context.Graph, catchCluster.AllNodes.Values.Last(), this.GetNextNode(context, containerCluster)); + this.BuildEdge(context.Graph, tryExitPort, catchEntryPort); + var catchContext = new TaskNodeRenderingContext(context.Workflow, context.Graph, context.TaskDefinition.Catch.Do, null, null, null, catchCluster, context.TaskReference + "/catch/do", context, catchEntryPort, catchExitPort); + this.BuildTransitions(catchEntryPort, catchContext); + this.BuildEdge(context.Graph, catchExitPort, containerExitPort); } + this.BuildTransitions(containerCluster, context); return containerCluster; } @@ -537,10 +538,10 @@ protected virtual NodeViewModel BuildTryTaskNode(TaskNodeRenderingContext context) { ArgumentNullException.ThrowIfNull(context); - var node = new WaitTaskNodeViewModel(context.TaskReference, context.TaskName, context.TaskDefinition.Wait.ToTimeSpan().ToString("hh\\:mm\\:ss\\.fff")); + var node = new WaitTaskNodeViewModel(context.TaskReference, context.TaskName!, context.TaskDefinition.Wait.ToTimeSpan().ToString("hh\\:mm\\:ss\\.fff")); if (context.TaskGroup == null) context.Graph.AddNode(node); else context.TaskGroup.AddChild(node); - this.BuildEdge(context.Graph, node, this.GetNextNode(context, node)); + this.BuildTransitions(node, context); return node; } @@ -575,7 +576,7 @@ protected virtual IEdgeViewModel BuildEdge(IGraphViewModel graph, INodeViewModel edge.LabelPosition = EdgeLabelPosition.Center; edge.Width = edge.Label.Length * characterSize; } - if (target.Id.EndsWith(_clusterEntrySuffix)) + if (target.Id.EndsWith(_clusterEntrySuffix) || target.Id.EndsWith(_clusterExitSuffix)) { edge.EndMarkerId = null; } @@ -587,15 +588,16 @@ protected virtual IEdgeViewModel BuildEdge(IGraphViewModel graph, INodeViewModel /// /// The workflow definition. /// The graph view model. + /// The list of tasks in the rendering context. /// The index of the task. /// The name of the task. /// The definition of the task. /// The optional task group. /// The reference to the parent task node. /// The parent rendering context of the task node. - /// The end node view model. - /// The previous node view model. - protected class TaskNodeRenderingContext(WorkflowDefinition workflow, GraphViewModel graph, int taskIndex, string taskName, TaskDefinition taskDefinition, WorkflowClusterViewModel? taskGroup, string parentReference, TaskNodeRenderingContext? parentContext, NodeViewModel endNode, NodeViewModel previousNode) + /// The entry node view model of the context. + /// The exit node view model of the context. + protected class TaskNodeRenderingContext(WorkflowDefinition workflow, GraphViewModel graph, Map tasksList, int? taskIndex, string? taskName, TaskDefinition? taskDefinition, WorkflowClusterViewModel? taskGroup, string parentReference, TaskNodeRenderingContext? parentContext, NodeViewModel entryNode, NodeViewModel exitNode) { /// @@ -608,20 +610,25 @@ protected class TaskNodeRenderingContext(WorkflowDefinition workflow, GraphViewM /// public virtual GraphViewModel Graph { get; } = graph; + /// + /// Gets the list of tasks in the rendering context. + /// + public virtual Map TasksList { get; } = tasksList; + /// /// Gets the index of the task. /// - public virtual int TaskIndex { get; } = taskIndex; + public virtual int? TaskIndex { get; } = taskIndex; /// /// Gets the name of the task. /// - public virtual string TaskName { get; } = taskName; + public virtual string? TaskName { get; } = taskName; /// /// Gets the definition of the task. /// - public virtual TaskDefinition TaskDefinition { get; } = taskDefinition; + public virtual TaskDefinition? TaskDefinition { get; } = taskDefinition; /// /// Gets the optional task group. @@ -644,21 +651,22 @@ protected class TaskNodeRenderingContext(WorkflowDefinition workflow, GraphViewM public virtual TaskNodeRenderingContext? ParentContext { get; } = parentContext; /// - /// Gets the end node view model. + /// Gets the entry node view model. /// - public virtual NodeViewModel EndNode { get; } = endNode; + public virtual NodeViewModel EntryNode { get; } = entryNode; /// - /// Gets the previous node view model. + /// Gets the exit node view model. /// - public virtual NodeViewModel PreviousNode { get; } = previousNode; + public virtual NodeViewModel ExitNode { get; } = exitNode; + /// /// Creates a new instance of with the specified task definition type. /// /// The type of the task definition. /// A new instance of . - public virtual TaskNodeRenderingContext OfType() where TDefinition : TaskDefinition => new(this.Workflow, this.Graph, this.TaskIndex, this.TaskName, this.TaskDefinition, this.TaskGroup, this.ParentReference, this.ParentContext, this.EndNode, this.PreviousNode); + public virtual TaskNodeRenderingContext OfType() where TDefinition : TaskDefinition => new(this.Workflow, this.Graph, this.TasksList, this.TaskIndex, this.TaskName, this.TaskDefinition, this.TaskGroup, this.ParentReference, this.ParentContext, this.EntryNode, this.ExitNode); } @@ -668,52 +676,48 @@ protected class TaskNodeRenderingContext(WorkflowDefinition workflow, GraphViewM /// The type of the task definition. /// The workflow definition. /// The graph view model. + /// The list of tasks in the rendering context. /// The index of the task. /// The name of the task. /// The definition of the task. /// The optional task group. /// The reference to the parent task node. /// The parent rendering context of the task node. - /// The end node view model. - /// The previous node view model. - protected class TaskNodeRenderingContext(WorkflowDefinition workflow, GraphViewModel graph, int taskIndex, string taskName, TaskDefinition taskDefinition, WorkflowClusterViewModel? taskGroup, string parentReference, TaskNodeRenderingContext? parentContext, NodeViewModel endNode, NodeViewModel previousNode) - : TaskNodeRenderingContext(workflow, graph, taskIndex, taskName, taskDefinition, taskGroup, parentReference, parentContext, endNode, previousNode) + /// The end node view model. + /// The previous node view model. + protected class TaskNodeRenderingContext(WorkflowDefinition workflow, GraphViewModel graph, Map tasksList, int? taskIndex, string? taskName, TaskDefinition? taskDefinition, WorkflowClusterViewModel? taskGroup, string parentReference, TaskNodeRenderingContext? parentContext, NodeViewModel entryNode, NodeViewModel exitNode) + : TaskNodeRenderingContext(workflow, graph, tasksList, taskIndex, taskName, taskDefinition, taskGroup, parentReference, parentContext, entryNode, exitNode) where TDefinition : TaskDefinition { /// /// Gets the task definition of type . /// - public new virtual TDefinition TaskDefinition => (TDefinition)base.TaskDefinition; + public new virtual TDefinition TaskDefinition => (TDefinition)base.TaskDefinition!; } /// /// Represents the identity of a task /// - /// The task name - /// The task index - /// The task reference - /// The task rendering context - protected class TaskIdentity(string name, int index, string reference, TaskNodeRenderingContext context) + /// The task name + /// The task index + /// The task definition + protected record TaskIdentity(string Name, int Index, TaskDefinition Definition) { - /// - /// Gets the task name - /// - public string Name { get; } = name; - - /// - /// Gets the task index - /// - public int Index { get; } = index; + } + /// + /// Represents a port type + /// + protected enum NodePortType + { /// - /// Gets the task reference + /// The entry port of a cluster /// - public string Reference { get; } = reference; - + Entry = 0, /// - /// Get the task rendering context + /// The exit port of a cluster /// - public TaskNodeRenderingContext Context { get; } = context; + Exit = 1 } } From df4b68557a8961cd49c3ca15045c4ecf32bab4e0 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bianchi Date: Thu, 12 Dec 2024 16:06:58 +0100 Subject: [PATCH 3/5] fix(Dashboard): fixed few transition bugs Signed-off-by: Jean-Baptiste Bianchi --- .../Services/WorkflowGraphBuilder.cs | 90 +++++++++++++------ 1 file changed, 65 insertions(+), 25 deletions(-) diff --git a/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs b/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs index ce5272026..31726aafa 100644 --- a/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs +++ b/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs @@ -61,6 +61,7 @@ public class WorkflowGraphBuilder(ILogger logger, IYamlSer public IGraphViewModel Build(WorkflowDefinition workflow) { ArgumentNullException.ThrowIfNull(workflow); + this.Logger.LogTrace("Starting WorkflowGraphBuilder.Build"); Stopwatch sw = Stopwatch.StartNew(); var graph = new GraphViewModel(); var startNode = this.BuildStartNode(); @@ -95,9 +96,15 @@ protected virtual INodeViewModel GetNodeAnchor(INodeViewModel node, NodePortType /// The current task /// A specific transition, if any (use for switch cases) /// The next task identity - protected virtual TaskIdentity? GetNextTask(Map tasksList, string? currentTask, string? transition = null) + protected virtual TaskIdentity GetNextTask(Map tasksList, string? currentTask, string? transition = null) { - if (transition == FlowDirective.End || transition == FlowDirective.Exit) return null; + ArgumentNullException.ThrowIfNull(tasksList); + var taskDefinition = tasksList.FirstOrDefault(taskEntry => taskEntry.Key == currentTask)?.Value; + transition = !string.IsNullOrWhiteSpace(transition) ? transition : taskDefinition != null ? taskDefinition.Then : null; + if (transition == FlowDirective.End || transition == FlowDirective.Exit) + { + return new TaskIdentity(transition, -1, null); + } int index; if (!string.IsNullOrWhiteSpace(transition) && transition != FlowDirective.Continue) { @@ -108,7 +115,7 @@ protected virtual INodeViewModel GetNodeAnchor(INodeViewModel node, NodePortType index = tasksList.Keys.ToList().IndexOf(currentTask) + 1; if (index >= tasksList.Count) { - return null; + return new TaskIdentity(FlowDirective.Exit, -1, null); } } else @@ -129,26 +136,45 @@ protected virtual INodeViewModel GetNodeAnchor(INodeViewModel node, NodePortType /// The rendering context of the provided node protected virtual void BuildTransitions(INodeViewModel node, TaskNodeRenderingContext context) { - List transitions = []; - TaskIdentity? nextTask = this.GetNextTask(context.TasksList, context.TaskName); + ArgumentNullException.ThrowIfNull(node); + ArgumentNullException.ThrowIfNull(context); + this.Logger.LogTrace($"Starting WorkflowGraphBuilder.BuildTransitions from '{node.Id}'"); + List transitions = []; + TaskIdentity nextTask = this.GetNextTask(context.TasksList, context.TaskName); transitions.Add(nextTask); - while (!string.IsNullOrWhiteSpace(nextTask?.Definition.If)) + this.Logger.LogTrace($"[WorkflowGraphBuilder.BuildTransitions][{node.Id}] found transition to '{nextTask?.Name}'"); + while (!string.IsNullOrWhiteSpace(nextTask?.Definition?.If)) { + this.Logger.LogTrace($"[WorkflowGraphBuilder.BuildTransitions][{node.Id}] if clause found, looking up next task."); nextTask = this.GetNextTask(context.TasksList, nextTask.Name); transitions.Add(nextTask); + this.Logger.LogTrace($"[WorkflowGraphBuilder.BuildTransitions][{node.Id}] found transition to '{nextTask?.Name}'"); } - foreach (var transition in transitions.Distinct()) + foreach (var transition in transitions.Distinct(new TaskIdentityComparer())) { - if (transition != null) + if (transition.Index != -1) { + this.Logger.LogTrace($"[WorkflowGraphBuilder.BuildTransitions][{node.Id}] Building node '{transition.Name}'"); var transitionNode = this.BuildTaskNode(new(context.Workflow, context.Graph, context.TasksList, transition.Index, transition.Name, transition.Definition, context.TaskGroup, context.ParentReference, context.ParentContext, context.EntryNode, context.ExitNode)); + this.Logger.LogTrace($"[WorkflowGraphBuilder.BuildTransitions][{node.Id}] Building edge to node '{transition.Name}'"); this.BuildEdge(context.Graph, this.GetNodeAnchor(node, NodePortType.Exit), GetNodeAnchor(transitionNode, NodePortType.Entry)); } - else + else if(transition.Name == FlowDirective.Exit) { + this.Logger.LogTrace($"[WorkflowGraphBuilder.BuildTransitions][{node.Id}] Exit transition, building edge to node '{context.ExitNode}'"); this.BuildEdge(context.Graph, this.GetNodeAnchor(node, NodePortType.Exit), context.ExitNode); } + else if (transition.Name == FlowDirective.End) + { + this.Logger.LogTrace($"[WorkflowGraphBuilder.BuildTransitions][{node.Id}] End transition, building edge to node '{context.ExitNode}'"); + this.BuildEdge(context.Graph, this.GetNodeAnchor(node, NodePortType.Exit), context.Graph.AllNodes.Skip(1).First().Value); + } + else + { + throw new IndexOutOfRangeException("Invalid transition"); + } } + this.Logger.LogTrace($"Exiting WorkflowGraphBuilder.BuildTransitions from '{node.Id}'"); } /// @@ -165,12 +191,15 @@ protected virtual void BuildTransitions(INodeViewModel node, TaskNodeRenderingCo protected INodeViewModel BuildTaskNode(TaskNodeRenderingContext context) { ArgumentNullException.ThrowIfNull(context); + this.Logger.LogTrace($"Starting WorkflowGraphBuilder.BuildTaskNode for {context.TaskName}"); if (context.Graph.AllNodes.ContainsKey(context.TaskReference)) { + this.Logger.LogTrace($"Exiting WorkflowGraphBuilder.BuildTaskNode for {context.TaskName}, found existing node."); return context.Graph.AllNodes[context.TaskReference]; } if (context.Graph.AllClusters.ContainsKey(context.TaskReference)) { + this.Logger.LogTrace($"Exiting WorkflowGraphBuilder.BuildTaskNode for {context.TaskName}, found existing cluster."); return context.Graph.AllClusters[context.TaskReference]; } return context.TaskDefinition switch @@ -233,7 +262,7 @@ protected virtual NodeViewModel BuildCallTaskNode(TaskNodeRenderingContext string.IsNullOrEmpty(switchCase.Value.When))) @@ -702,7 +719,7 @@ protected class TaskNodeRenderingContext(WorkflowDefinition workflo /// The task name /// The task index /// The task definition - protected record TaskIdentity(string Name, int Index, TaskDefinition Definition) + protected record TaskIdentity(string Name, int Index, TaskDefinition? Definition) { } @@ -720,4 +737,27 @@ protected enum NodePortType /// Exit = 1 } + + /// + /// The object used to compare + /// + protected class TaskIdentityComparer : IEqualityComparer + { + /// + public bool Equals(TaskIdentity? identity1, TaskIdentity? identity2) + { + if (ReferenceEquals(identity1, identity2)) + return true; + + if (identity1 is null || identity2 is null) + return false; + + return identity1.Name == identity2.Name && + identity1.Index == identity2.Index && + identity1.Definition == identity2.Definition; + } + + /// + public int GetHashCode(TaskIdentity identity) => identity.Name.GetHashCode(); + } } From c8c96db0c4d9d4456f386dc70e7c25927acc6083 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bianchi Date: Thu, 12 Dec 2024 16:17:28 +0100 Subject: [PATCH 4/5] fix(Dashboard): Fixed logs format - Fixed logging messages format in the graph builder Signed-off-by: Jean-Baptiste Bianchi --- .../Services/WorkflowGraphBuilder.cs | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs b/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs index 31726aafa..8f3325084 100644 --- a/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs +++ b/src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs @@ -100,7 +100,7 @@ protected virtual TaskIdentity GetNextTask(Map tasksList { ArgumentNullException.ThrowIfNull(tasksList); var taskDefinition = tasksList.FirstOrDefault(taskEntry => taskEntry.Key == currentTask)?.Value; - transition = !string.IsNullOrWhiteSpace(transition) ? transition : taskDefinition != null ? taskDefinition.Then : null; + transition = !string.IsNullOrWhiteSpace(transition) ? transition : taskDefinition?.Then; if (transition == FlowDirective.End || transition == FlowDirective.Exit) { return new TaskIdentity(transition, -1, null); @@ -122,11 +122,8 @@ protected virtual TaskIdentity GetNextTask(Map tasksList { index = 0; } - if (context.Graph.AllNodes.ContainsKey(reference)) - { - return (NodeViewModel)context.Graph.AllNodes[reference]; - } - throw new IndexOutOfRangeException($"Unable to find the task with reference '{reference}' in the provided context."); + var taskEntry = tasksList.ElementAt(index); + return new TaskIdentity(taskEntry.Key, index, taskEntry.Value); } /// @@ -138,35 +135,35 @@ protected virtual void BuildTransitions(INodeViewModel node, TaskNodeRenderingCo { ArgumentNullException.ThrowIfNull(node); ArgumentNullException.ThrowIfNull(context); - this.Logger.LogTrace($"Starting WorkflowGraphBuilder.BuildTransitions from '{node.Id}'"); + this.Logger.LogTrace("Starting WorkflowGraphBuilder.BuildTransitions from '{nodeId}'", node.Id); List transitions = []; TaskIdentity nextTask = this.GetNextTask(context.TasksList, context.TaskName); transitions.Add(nextTask); - this.Logger.LogTrace($"[WorkflowGraphBuilder.BuildTransitions][{node.Id}] found transition to '{nextTask?.Name}'"); + this.Logger.LogTrace("[WorkflowGraphBuilder.BuildTransitions][{nodeId}] found transition to '{nextTaskName}'", node.Id, nextTask?.Name); while (!string.IsNullOrWhiteSpace(nextTask?.Definition?.If)) { - this.Logger.LogTrace($"[WorkflowGraphBuilder.BuildTransitions][{node.Id}] if clause found, looking up next task."); + this.Logger.LogTrace("[WorkflowGraphBuilder.BuildTransitions][{nodeId}] if clause found, looking up next task.", node.Id); nextTask = this.GetNextTask(context.TasksList, nextTask.Name); transitions.Add(nextTask); - this.Logger.LogTrace($"[WorkflowGraphBuilder.BuildTransitions][{node.Id}] found transition to '{nextTask?.Name}'"); + this.Logger.LogTrace("[WorkflowGraphBuilder.BuildTransitions][{nodeId}] found transition to '{nextTaskName}'", node.Id, nextTask?.Name); } foreach (var transition in transitions.Distinct(new TaskIdentityComparer())) { if (transition.Index != -1) { - this.Logger.LogTrace($"[WorkflowGraphBuilder.BuildTransitions][{node.Id}] Building node '{transition.Name}'"); + this.Logger.LogTrace("[WorkflowGraphBuilder.BuildTransitions][{nodeId}] Building node '{transitionName}'", node.Id, transition.Name); var transitionNode = this.BuildTaskNode(new(context.Workflow, context.Graph, context.TasksList, transition.Index, transition.Name, transition.Definition, context.TaskGroup, context.ParentReference, context.ParentContext, context.EntryNode, context.ExitNode)); - this.Logger.LogTrace($"[WorkflowGraphBuilder.BuildTransitions][{node.Id}] Building edge to node '{transition.Name}'"); + this.Logger.LogTrace("[WorkflowGraphBuilder.BuildTransitions][{nodeId}] Building edge to node '{transitionName}'", node.Id, transition.Name); this.BuildEdge(context.Graph, this.GetNodeAnchor(node, NodePortType.Exit), GetNodeAnchor(transitionNode, NodePortType.Entry)); } - else if(transition.Name == FlowDirective.Exit) + else if (transition.Name == FlowDirective.Exit) { - this.Logger.LogTrace($"[WorkflowGraphBuilder.BuildTransitions][{node.Id}] Exit transition, building edge to node '{context.ExitNode}'"); + this.Logger.LogTrace("[WorkflowGraphBuilder.BuildTransitions][{nodeId}] Exit transition, building edge to node '{contextExitNode}'", node.Id, context.ExitNode); this.BuildEdge(context.Graph, this.GetNodeAnchor(node, NodePortType.Exit), context.ExitNode); } else if (transition.Name == FlowDirective.End) { - this.Logger.LogTrace($"[WorkflowGraphBuilder.BuildTransitions][{node.Id}] End transition, building edge to node '{context.ExitNode}'"); + this.Logger.LogTrace("[WorkflowGraphBuilder.BuildTransitions][{nodeId}] End transition, building edge to node '{contextExitNode}'", node.Id, context.ExitNode); this.BuildEdge(context.Graph, this.GetNodeAnchor(node, NodePortType.Exit), context.Graph.AllNodes.Skip(1).First().Value); } else @@ -174,7 +171,7 @@ protected virtual void BuildTransitions(INodeViewModel node, TaskNodeRenderingCo throw new IndexOutOfRangeException("Invalid transition"); } } - this.Logger.LogTrace($"Exiting WorkflowGraphBuilder.BuildTransitions from '{node.Id}'"); + this.Logger.LogTrace("Exiting WorkflowGraphBuilder.BuildTransitions from '{nodeId}'", node.Id); } /// @@ -191,15 +188,15 @@ protected virtual void BuildTransitions(INodeViewModel node, TaskNodeRenderingCo protected INodeViewModel BuildTaskNode(TaskNodeRenderingContext context) { ArgumentNullException.ThrowIfNull(context); - this.Logger.LogTrace($"Starting WorkflowGraphBuilder.BuildTaskNode for {context.TaskName}"); + this.Logger.LogTrace("Starting WorkflowGraphBuilder.BuildTaskNode for '{contextTaskName}'", context.TaskName); if (context.Graph.AllNodes.ContainsKey(context.TaskReference)) { - this.Logger.LogTrace($"Exiting WorkflowGraphBuilder.BuildTaskNode for {context.TaskName}, found existing node."); + this.Logger.LogTrace("Exiting WorkflowGraphBuilder.BuildTaskNode for '{contextTaskName}', found existing node.", context.TaskName); return context.Graph.AllNodes[context.TaskReference]; } if (context.Graph.AllClusters.ContainsKey(context.TaskReference)) { - this.Logger.LogTrace($"Exiting WorkflowGraphBuilder.BuildTaskNode for {context.TaskName}, found existing cluster."); + this.Logger.LogTrace("Exiting WorkflowGraphBuilder.BuildTaskNode for '{contextTaskName}', found existing cluster.", context.TaskName); return context.Graph.AllClusters[context.TaskReference]; } return context.TaskDefinition switch @@ -360,7 +357,7 @@ protected virtual NodeViewModel BuildForkTaskNode(TaskNodeRenderingContext string.IsNullOrEmpty(switchCase.Value.When))) @@ -532,7 +529,7 @@ protected virtual NodeViewModel BuildTryTaskNode(TaskNodeRenderingContext keyValuePair.Value).FirstOrDefault(edge => edge.SourceId == source.Id && edge.TargetId == target.Id); if (edge != null) { - if (!string.IsNullOrEmpty(label)) { + if (!string.IsNullOrEmpty(label)) + { edge.Label = edge.Label + " / " + label; edge.Width = edge.Label.Length * characterSize; } @@ -752,7 +750,7 @@ public bool Equals(TaskIdentity? identity1, TaskIdentity? identity2) if (identity1 is null || identity2 is null) return false; - return identity1.Name == identity2.Name && + return identity1.Name == identity2.Name && identity1.Index == identity2.Index && identity1.Definition == identity2.Definition; } From 539cf5728dba3c60b8d94e909fa81dc913d652d3 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bianchi Date: Thu, 12 Dec 2024 16:28:41 +0100 Subject: [PATCH 5/5] fix(Global): Updated Neuroglia packages to latest version Signed-off-by: Jean-Baptiste Bianchi --- src/api/Synapse.Api.Http/Synapse.Api.Http.csproj | 6 +++--- .../Synapse.Core.Infrastructure.csproj | 15 ++++++++------- src/core/Synapse.Core/Synapse.Core.csproj | 4 ++-- .../Synapse.Correlator/Synapse.Correlator.csproj | 14 +++++++------- .../Synapse.Dashboard/Synapse.Dashboard.csproj | 2 +- src/runner/Synapse.Runner/Synapse.Runner.csproj | 10 +++++----- tests/Synapse.UnitTests/Synapse.UnitTests.csproj | 10 +++++----- 7 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/api/Synapse.Api.Http/Synapse.Api.Http.csproj b/src/api/Synapse.Api.Http/Synapse.Api.Http.csproj index 7a14384e3..56f696639 100644 --- a/src/api/Synapse.Api.Http/Synapse.Api.Http.csproj +++ b/src/api/Synapse.Api.Http/Synapse.Api.Http.csproj @@ -43,9 +43,9 @@ - - - + + + diff --git a/src/core/Synapse.Core.Infrastructure/Synapse.Core.Infrastructure.csproj b/src/core/Synapse.Core.Infrastructure/Synapse.Core.Infrastructure.csproj index 96e58e80d..f4a3c6039 100644 --- a/src/core/Synapse.Core.Infrastructure/Synapse.Core.Infrastructure.csproj +++ b/src/core/Synapse.Core.Infrastructure/Synapse.Core.Infrastructure.csproj @@ -44,13 +44,14 @@ - - - - - - - + + + + + + + + diff --git a/src/core/Synapse.Core/Synapse.Core.csproj b/src/core/Synapse.Core/Synapse.Core.csproj index 430413e88..b652a32b4 100644 --- a/src/core/Synapse.Core/Synapse.Core.csproj +++ b/src/core/Synapse.Core/Synapse.Core.csproj @@ -67,8 +67,8 @@ - - + + diff --git a/src/correlator/Synapse.Correlator/Synapse.Correlator.csproj b/src/correlator/Synapse.Correlator/Synapse.Correlator.csproj index 7edeb893d..e0f77911b 100644 --- a/src/correlator/Synapse.Correlator/Synapse.Correlator.csproj +++ b/src/correlator/Synapse.Correlator/Synapse.Correlator.csproj @@ -36,13 +36,13 @@ - - - - - - - + + + + + + + diff --git a/src/dashboard/Synapse.Dashboard/Synapse.Dashboard.csproj b/src/dashboard/Synapse.Dashboard/Synapse.Dashboard.csproj index 2c3580980..8d30a8062 100644 --- a/src/dashboard/Synapse.Dashboard/Synapse.Dashboard.csproj +++ b/src/dashboard/Synapse.Dashboard/Synapse.Dashboard.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/runner/Synapse.Runner/Synapse.Runner.csproj b/src/runner/Synapse.Runner/Synapse.Runner.csproj index cb99ab34d..3ef64dc1b 100644 --- a/src/runner/Synapse.Runner/Synapse.Runner.csproj +++ b/src/runner/Synapse.Runner/Synapse.Runner.csproj @@ -60,11 +60,11 @@ - - - - - + + + + + diff --git a/tests/Synapse.UnitTests/Synapse.UnitTests.csproj b/tests/Synapse.UnitTests/Synapse.UnitTests.csproj index 0eb0b5554..8952fcaf4 100644 --- a/tests/Synapse.UnitTests/Synapse.UnitTests.csproj +++ b/tests/Synapse.UnitTests/Synapse.UnitTests.csproj @@ -19,11 +19,11 @@ - - - - - + + + + +