1717using ServerlessWorkflow . Sdk . Models ;
1818using ServerlessWorkflow . Sdk . Models . Calls ;
1919using ServerlessWorkflow . Sdk . Models . Tasks ;
20- using Synapse . Dashboard . Components . DocumentDetailsStateManagement ;
2120using System . Diagnostics ;
2221
2322namespace Synapse . Dashboard . Services ;
@@ -32,7 +31,8 @@ public class WorkflowGraphBuilder(ILogger<WorkflowGraphBuilder> logger, IYamlSer
3231 : IWorkflowGraphBuilder
3332{
3433
35- const string _portSuffix = "-port" ;
34+ const string _clusterEntrySuffix = "-cluster-entry" ;
35+ const string _clusterExitSuffix = "-cluster-exit" ;
3636 const string _trySuffix = "-try" ;
3737 const string _catchSuffix = "-catch" ;
3838 const double characterSize = 8d ;
@@ -78,65 +78,51 @@ public IGraphViewModel Build(WorkflowDefinition workflow)
7878 nextNode = ( NodeViewModel ) clusterViewModel . AllNodes . Values . First ( ) ;
7979 }
8080 }
81- this . BuildEdge ( graph , startNode , nextNode ) ;
8281 sw . Stop ( ) ;
8382 this . Logger . LogTrace ( "WorkflowGraphBuilder.Build took {elapsedTime} ms" , sw . ElapsedMilliseconds ) ;
8483 return graph ;
8584 }
8685
87- /// <summary>
88- /// Builds a new start <see cref="NodeViewModel"/>
89- /// </summary>
90- /// <param name="hasSuccessor">A boolean indicating whether or not the node has successor</param>
91- /// <returns>A new <see cref="NodeViewModel"/></returns>
92- protected virtual NodeViewModel BuildStartNode ( bool hasSuccessor = false ) => new StartNodeViewModel ( hasSuccessor ) ;
86+ protected record TaskContext ( ClusterViewModel ? taskGroup , int taskIndex , string taskName , TaskDefinition definition )
87+ {
88+ public virtual int ? Index { get ; } = taskIndex ;
89+ public virtual string ? Name { get ; } = taskName ;
90+ public virtual TaskDefinition ? Definition { get ; } = definition ;
91+ }
9392
94- /// <summary>
95- /// Returns the name, index and reference of the next node
96- /// </summary>
97- /// <param name="context">The rendering context for the task nodes</param>
98- /// <param name="ignoreConditionalTasks">If true, skips <see cref="TaskDefinition"/> with an if clause</param>
99- /// <param name="transition">A transition, if different from the context task definition's</param>
100- /// <returns>The next task <see cref="TaskIdentity"/></returns>
101- protected TaskIdentity ? GetNextTaskIdentity ( TaskNodeRenderingContext context , bool ignoreConditionalTasks , string ? transition = null )
93+ protected record RenderingContext ( WorkflowDefinition workflow , GraphViewModel graph , Map < string , TaskDefinition > tasksList , INodeViewModel node , TaskContext ? task = null , RenderingContext ? parent = null , INodeViewModel ? entryNode = null , INodeViewModel ? exitNode = null )
10294 {
103- transition = ! string . IsNullOrWhiteSpace ( transition ) ? transition : context . TaskDefinition . Then ;
104- if ( transition == FlowDirective . End ) return null ;
105- if ( transition == FlowDirective . Exit )
95+ public virtual WorkflowDefinition Workflow { get ; } = workflow ;
96+ public virtual GraphViewModel Graph { get ; } = graph ;
97+ public virtual Map < string , TaskDefinition > TasksList { get ; } = tasksList ;
98+ public virtual RenderingContext ? Parent { get ; } = parent ;
99+ public virtual TaskContext ? Task { get ; } = task ;
100+ public virtual INodeViewModel Node { get ; } = node ;
101+ public virtual INodeViewModel EntryNode { get ; } = entryNode ;
102+ public virtual INodeViewModel ExitNode { get ; } = exitNode ;
103+ }
104+
105+ protected virtual void BuildTransitions ( RenderingContext context )
106+ {
107+ Map < string , TaskDefinition > transitions = [ ] ;
108+ MapEntry < string , TaskDefinition > ? nextTask = this . GetNextTask ( context . TasksList , context . Task ? . Name ) ;
109+ if ( nextTask != null )
106110 {
107- if ( context . ParentContext == null )
111+ transitions . Add ( nextTask ) ;
112+ while ( ! string . IsNullOrWhiteSpace ( nextTask ? . Value . If ) )
108113 {
109- return null ;
114+ nextTask = this . GetNextTask ( context . TasksList , nextTask . Key ) ;
115+ if ( nextTask != null )
116+ {
117+ transitions . Add ( nextTask ) ;
118+ }
110119 }
111- return this . GetNextTaskIdentity ( context . ParentContext , ignoreConditionalTasks ) ;
112120 }
113- var nextTaskName = string . IsNullOrWhiteSpace ( transition ) || transition == FlowDirective . Continue
114- ? context . Workflow . GetTaskAfter ( new ( context . TaskName , context . TaskDefinition ) , context . ParentReference , ignoreConditionalTasks ) ? . Key
115- : transition ;
116- if ( string . IsNullOrWhiteSpace ( nextTaskName ) )
121+ foreach ( var transition in transitions )
117122 {
118- if ( context . ParentContext == null )
119- {
120- return null ;
121- }
122- return this . GetNextTaskIdentity ( context . ParentContext , ignoreConditionalTasks ) ;
123- }
124- var nextTaskIndex = context . Workflow . IndexOf ( nextTaskName , context . ParentReference ) ;
125- var nextTaskReference = $ "{ context . ParentReference } /{ nextTaskIndex } /{ nextTaskName } ";
126- return new ( nextTaskName , nextTaskIndex , nextTaskReference , context ) ;
127- }
123+ var transitionNode = this . BuildTaskNode ( ) ;
124+ this . BuildEdge ( context . Graph , context . Node , transitionNode ) ;
128125
129- /// <summary>
130- /// Gets a <see cref="NodeViewModel"/> by reference in the provided <see cref="TaskNodeRenderingContext"/>
131- /// </summary>
132- /// <param name="context">The source <see cref="TaskNodeRenderingContext"/></param>
133- /// <param name="reference">The reference to look for</param>
134- /// <returns></returns>
135- protected NodeViewModel GetNodeByReference ( TaskNodeRenderingContext context , string reference )
136- {
137- if ( context . Graph . AllClusters . ContainsKey ( reference ) )
138- {
139- return ( NodeViewModel ) context . Graph . AllClusters [ reference ] . AllNodes . First ( ) . Value ;
140126 }
141127 if ( context . Graph . AllNodes . ContainsKey ( reference ) )
142128 {
@@ -145,36 +131,32 @@ protected NodeViewModel GetNodeByReference(TaskNodeRenderingContext context, str
145131 throw new IndexOutOfRangeException ( $ "Unable to find the task with reference '{ reference } ' in the provided context.") ;
146132 }
147133
148- /// <summary>
149- /// Gets the next <see cref="NodeViewModel"/> in the graph
150- /// </summary>
151- /// <param name="context">The rendering context for the task nodes</param>
152- /// <param name="currentNode">The current task node</param>
153- /// <param name="ignoreConditionalTasks">If true, skips <see cref="TaskDefinition"/> with an if clause</param>
154- /// <param name="transition">A transition, if different from the context task definition's</param>
155- /// <returns>The next task <see cref="NodeViewModel"/></returns>
156- /// <exception cref="Exception"></exception>
157- protected NodeViewModel GetNextNode ( TaskNodeRenderingContext context , NodeViewModel currentNode , bool ignoreConditionalTasks = false , string ? transition = null )
134+ protected virtual MapEntry < string , TaskDefinition > ? GetNextTask ( Map < string , TaskDefinition > tasksList , string ? taskName , string ? transition = null ) //RenderingContext context, string? transition = null)
158135 {
159- var nextTaskIdentity = this . GetNextTaskIdentity ( context , ignoreConditionalTasks , transition ) ;
160- if ( nextTaskIdentity == null )
136+ if ( transition == FlowDirective . End || transition == FlowDirective . Exit ) return null ;
137+ int index ;
138+ if ( ! string . IsNullOrWhiteSpace ( transition ) && transition != FlowDirective . Continue )
161139 {
162- return context . EndNode ;
140+ index = tasksList . Keys . ToList ( ) . IndexOf ( transition ) ;
163141 }
164- var nextTask = context . Workflow . GetComponent < TaskDefinition > ( 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 } '") ;
165- if ( ! context . Graph . AllNodes . ContainsKey ( nextTaskIdentity . Reference ) && ! context . Graph . AllClusters . ContainsKey ( nextTaskIdentity . Reference ) )
142+ else if ( ! string . IsNullOrWhiteSpace ( taskName ) )
166143 {
167- 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 ) ) ;
144+ index = tasksList . Keys . ToList ( ) . IndexOf ( taskName ) + 1 ;
168145 }
169- if ( string . IsNullOrEmpty ( nextTask . If ) )
146+ else
170147 {
171- return this . GetNodeByReference ( context , nextTaskIdentity . Reference ) ;
148+ index = 0 ;
172149 }
173- var nextNode = this . GetNodeByReference ( context , nextTaskIdentity . Reference ) ;
174- this . BuildEdge ( context . Graph , currentNode , nextNode ) ;
175- return this . GetNextNode ( context , currentNode , true , transition ) ;
150+ return tasksList . ElementAt ( index ) ;
176151 }
177152
153+ /// <summary>
154+ /// Builds a new start <see cref="NodeViewModel"/>
155+ /// </summary>
156+ /// <param name="hasSuccessor">A boolean indicating whether or not the node has successor</param>
157+ /// <returns>A new <see cref="NodeViewModel"/></returns>
158+ protected virtual NodeViewModel BuildStartNode ( bool hasSuccessor = false ) => new StartNodeViewModel ( hasSuccessor ) ;
159+
178160 /// <summary>
179161 /// Builds a new <see cref="WorkflowClusterViewModel"/> for the specified task
180162 /// </summary>
@@ -263,25 +245,30 @@ protected virtual NodeViewModel BuildDoTaskNode(TaskNodeRenderingContext<DoTaskD
263245 ArgumentNullException . ThrowIfNull ( context ) ;
264246 var taskCount = context . TaskDefinition . Do . Count ;
265247 var cluster = new DoTaskNodeViewModel ( context . TaskReference , context . TaskName , $ "{ taskCount } task{ ( taskCount > 1 ? "s" : "" ) } ") ;
266- var port = new PortNodeViewModel ( context . TaskReference + _portSuffix ) ;
267- cluster . AddChild ( port ) ;
248+ var entryPort = new PortNodeViewModel ( context . TaskReference + _clusterEntrySuffix ) ;
249+ var exitPort = new PortNodeViewModel ( context . TaskReference + _clusterExitSuffix ) ;
250+ cluster . AddChild ( entryPort ) ;
251+ cluster . AddChild ( exitPort ) ;
268252 if ( context . TaskGroup == null ) context . Graph . AddCluster ( cluster ) ;
269253 else context . TaskGroup . AddChild ( cluster ) ;
270254 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 ) ;
271255 this . BuildTaskNode ( innerContext ) ;
272256 if ( taskCount > 0 )
273257 {
274- var firstDoNode = ( NodeViewModel ) cluster . AllNodes . Skip ( 1 ) . First ( ) . Value ;
275- this . BuildEdge ( context . Graph , port , firstDoNode ) ;
258+ var firstDoNode = ( NodeViewModel ) cluster . AllNodes . Skip ( 2 ) . First ( ) . Value ;
259+ var lastDoNode = ( NodeViewModel ) cluster . AllNodes . Last ( ) . Value ;
260+ this . BuildEdge ( context . Graph , entryPort , firstDoNode ) ;
261+ this . BuildEdge ( context . Graph , lastDoNode , exitPort ) ;
276262 if ( taskCount > 1 && ! string . IsNullOrWhiteSpace ( context . TaskDefinition . Do . First ( ) . Value . If ) )
277263 {
278- this . BuildEdge ( context . Graph , port , this . GetNextNode ( innerContext , firstDoNode ) ) ;
264+ this . BuildEdge ( context . Graph , entryPort , this . GetNextNode ( innerContext , firstDoNode ) ) ;
279265 }
280266 }
281267 else
282268 {
283- this . BuildEdge ( context . Graph , port , this . GetNextNode ( context , cluster ) ) ;
269+ this . BuildEdge ( context . Graph , entryPort , exitPort ) ;
284270 }
271+ this . BuildEdge ( context . Graph , exitPort , this . GetNextNode ( context , cluster ) ) ;
285272 return cluster ;
286273 }
287274
@@ -324,7 +311,7 @@ protected virtual NodeViewModel BuildForTaskNode(TaskNodeRenderingContext<ForTas
324311 {
325312 ArgumentNullException . ThrowIfNull ( context ) ;
326313 var cluster = new ForTaskNodeViewModel ( context . TaskReference , context . TaskName , this . YamlSerializer . SerializeToText ( context . TaskDefinition . For ) ) ;
327- var port = new PortNodeViewModel ( context . TaskReference + _portSuffix ) ;
314+ var port = new PortNodeViewModel ( context . TaskReference + _clusterEntrySuffix ) ;
328315 cluster . AddChild ( port ) ;
329316 if ( context . TaskGroup == null ) context . Graph . AddCluster ( cluster ) ;
330317 else context . TaskGroup . AddChild ( cluster ) ;
@@ -349,8 +336,8 @@ protected virtual NodeViewModel BuildForkTaskNode(TaskNodeRenderingContext<ForkT
349336 {
350337 ArgumentNullException . ThrowIfNull ( context ) ;
351338 var cluster = new ForkTaskNodeViewModel ( context . TaskReference , context . TaskName , this . YamlSerializer . SerializeToText ( context . TaskDefinition . Fork ) ) ;
352- var entryPort = new PortNodeViewModel ( context . TaskReference + _portSuffix ) ;
353- var exitPort = new PortNodeViewModel ( context . TaskReference + "-exit" + _portSuffix ) ;
339+ var entryPort = new PortNodeViewModel ( context . TaskReference + _clusterEntrySuffix ) ;
340+ var exitPort = new PortNodeViewModel ( context . TaskReference + "-exit" + _clusterExitSuffix ) ;
354341 cluster . AddChild ( entryPort ) ;
355342 if ( context . TaskGroup == null ) context . Graph . AddCluster ( cluster ) ;
356343 else context . TaskGroup . AddChild ( cluster ) ;
@@ -499,13 +486,13 @@ protected virtual NodeViewModel BuildTryTaskNode(TaskNodeRenderingContext<TryTas
499486 ArgumentNullException . ThrowIfNull ( context ) ;
500487 var taskCount = context . TaskDefinition . Try . Count ;
501488 var containerCluster = new TryTaskNodeViewModel ( context . TaskReference , context . TaskName , $ "{ taskCount } task{ ( taskCount > 1 ? "s" : "" ) } ") ;
502- var containerPort = new PortNodeViewModel ( context . TaskReference + _portSuffix ) ;
489+ var containerPort = new PortNodeViewModel ( context . TaskReference + _clusterEntrySuffix ) ;
503490 containerCluster . AddChild ( containerPort ) ;
504491 if ( context . TaskGroup == null ) context . Graph . AddCluster ( containerCluster ) ;
505492 else context . TaskGroup . AddChild ( containerCluster ) ;
506493
507494 var tryCluster = new TryNodeViewModel ( context . TaskReference + _trySuffix , context . TaskName , string . Empty ) ;
508- var tryPort = new PortNodeViewModel ( context . TaskReference + _trySuffix + _portSuffix ) ;
495+ var tryPort = new PortNodeViewModel ( context . TaskReference + _trySuffix + _clusterEntrySuffix ) ;
509496 tryCluster . AddChild ( tryPort ) ;
510497 containerCluster . AddChild ( tryCluster ) ;
511498 this . BuildEdge ( context . Graph , containerPort , tryPort ) ;
@@ -531,7 +518,7 @@ protected virtual NodeViewModel BuildTryTaskNode(TaskNodeRenderingContext<TryTas
531518 else
532519 {
533520 var catchCluster = new CatchDoNodeViewModel ( context . TaskReference + _catchSuffix , context . TaskName , catchContent ) ;
534- var catchPort = new PortNodeViewModel ( context . TaskReference + _catchSuffix + _portSuffix ) ;
521+ var catchPort = new PortNodeViewModel ( context . TaskReference + _catchSuffix + _clusterEntrySuffix ) ;
535522 catchCluster . AddChild ( catchPort ) ;
536523 containerCluster . AddChild ( catchCluster ) ;
537524 this . BuildEdge ( context . Graph , tryCluster . AllNodes . Values . Last ( ) , catchPort ) ;
@@ -588,7 +575,7 @@ protected virtual IEdgeViewModel BuildEdge(IGraphViewModel graph, INodeViewModel
588575 edge . LabelPosition = EdgeLabelPosition . Center ;
589576 edge . Width = edge . Label . Length * characterSize ;
590577 }
591- if ( target . Id . EndsWith ( _portSuffix ) )
578+ if ( target . Id . EndsWith ( _clusterEntrySuffix ) )
592579 {
593580 edge . EndMarkerId = null ;
594581 }
0 commit comments