17
17
using ServerlessWorkflow . Sdk . Models ;
18
18
using ServerlessWorkflow . Sdk . Models . Calls ;
19
19
using ServerlessWorkflow . Sdk . Models . Tasks ;
20
- using Synapse . Dashboard . Components . DocumentDetailsStateManagement ;
21
20
using System . Diagnostics ;
22
21
23
22
namespace Synapse . Dashboard . Services ;
@@ -32,7 +31,8 @@ public class WorkflowGraphBuilder(ILogger<WorkflowGraphBuilder> logger, IYamlSer
32
31
: IWorkflowGraphBuilder
33
32
{
34
33
35
- const string _portSuffix = "-port" ;
34
+ const string _clusterEntrySuffix = "-cluster-entry" ;
35
+ const string _clusterExitSuffix = "-cluster-exit" ;
36
36
const string _trySuffix = "-try" ;
37
37
const string _catchSuffix = "-catch" ;
38
38
const double characterSize = 8d ;
@@ -78,65 +78,51 @@ public IGraphViewModel Build(WorkflowDefinition workflow)
78
78
nextNode = ( NodeViewModel ) clusterViewModel . AllNodes . Values . First ( ) ;
79
79
}
80
80
}
81
- this . BuildEdge ( graph , startNode , nextNode ) ;
82
81
sw . Stop ( ) ;
83
82
this . Logger . LogTrace ( "WorkflowGraphBuilder.Build took {elapsedTime} ms" , sw . ElapsedMilliseconds ) ;
84
83
return graph ;
85
84
}
86
85
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
+ }
93
92
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 )
102
94
{
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 )
106
110
{
107
- if ( context . ParentContext == null )
111
+ transitions . Add ( nextTask ) ;
112
+ while ( ! string . IsNullOrWhiteSpace ( nextTask ? . Value . If ) )
108
113
{
109
- return null ;
114
+ nextTask = this . GetNextTask ( context . TasksList , nextTask . Key ) ;
115
+ if ( nextTask != null )
116
+ {
117
+ transitions . Add ( nextTask ) ;
118
+ }
110
119
}
111
- return this . GetNextTaskIdentity ( context . ParentContext , ignoreConditionalTasks ) ;
112
120
}
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 )
117
122
{
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 ) ;
128
125
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 ;
140
126
}
141
127
if ( context . Graph . AllNodes . ContainsKey ( reference ) )
142
128
{
@@ -145,36 +131,32 @@ protected NodeViewModel GetNodeByReference(TaskNodeRenderingContext context, str
145
131
throw new IndexOutOfRangeException ( $ "Unable to find the task with reference '{ reference } ' in the provided context.") ;
146
132
}
147
133
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)
158
135
{
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 )
161
139
{
162
- return context . EndNode ;
140
+ index = tasksList . Keys . ToList ( ) . IndexOf ( transition ) ;
163
141
}
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 ) )
166
143
{
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 ;
168
145
}
169
- if ( string . IsNullOrEmpty ( nextTask . If ) )
146
+ else
170
147
{
171
- return this . GetNodeByReference ( context , nextTaskIdentity . Reference ) ;
148
+ index = 0 ;
172
149
}
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 ) ;
176
151
}
177
152
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
+
178
160
/// <summary>
179
161
/// Builds a new <see cref="WorkflowClusterViewModel"/> for the specified task
180
162
/// </summary>
@@ -263,25 +245,30 @@ protected virtual NodeViewModel BuildDoTaskNode(TaskNodeRenderingContext<DoTaskD
263
245
ArgumentNullException . ThrowIfNull ( context ) ;
264
246
var taskCount = context . TaskDefinition . Do . Count ;
265
247
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 ) ;
268
252
if ( context . TaskGroup == null ) context . Graph . AddCluster ( cluster ) ;
269
253
else context . TaskGroup . AddChild ( cluster ) ;
270
254
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 ) ;
271
255
this . BuildTaskNode ( innerContext ) ;
272
256
if ( taskCount > 0 )
273
257
{
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 ) ;
276
262
if ( taskCount > 1 && ! string . IsNullOrWhiteSpace ( context . TaskDefinition . Do . First ( ) . Value . If ) )
277
263
{
278
- this . BuildEdge ( context . Graph , port , this . GetNextNode ( innerContext , firstDoNode ) ) ;
264
+ this . BuildEdge ( context . Graph , entryPort , this . GetNextNode ( innerContext , firstDoNode ) ) ;
279
265
}
280
266
}
281
267
else
282
268
{
283
- this . BuildEdge ( context . Graph , port , this . GetNextNode ( context , cluster ) ) ;
269
+ this . BuildEdge ( context . Graph , entryPort , exitPort ) ;
284
270
}
271
+ this . BuildEdge ( context . Graph , exitPort , this . GetNextNode ( context , cluster ) ) ;
285
272
return cluster ;
286
273
}
287
274
@@ -324,7 +311,7 @@ protected virtual NodeViewModel BuildForTaskNode(TaskNodeRenderingContext<ForTas
324
311
{
325
312
ArgumentNullException . ThrowIfNull ( context ) ;
326
313
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 ) ;
328
315
cluster . AddChild ( port ) ;
329
316
if ( context . TaskGroup == null ) context . Graph . AddCluster ( cluster ) ;
330
317
else context . TaskGroup . AddChild ( cluster ) ;
@@ -349,8 +336,8 @@ protected virtual NodeViewModel BuildForkTaskNode(TaskNodeRenderingContext<ForkT
349
336
{
350
337
ArgumentNullException . ThrowIfNull ( context ) ;
351
338
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 ) ;
354
341
cluster . AddChild ( entryPort ) ;
355
342
if ( context . TaskGroup == null ) context . Graph . AddCluster ( cluster ) ;
356
343
else context . TaskGroup . AddChild ( cluster ) ;
@@ -499,13 +486,13 @@ protected virtual NodeViewModel BuildTryTaskNode(TaskNodeRenderingContext<TryTas
499
486
ArgumentNullException . ThrowIfNull ( context ) ;
500
487
var taskCount = context . TaskDefinition . Try . Count ;
501
488
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 ) ;
503
490
containerCluster . AddChild ( containerPort ) ;
504
491
if ( context . TaskGroup == null ) context . Graph . AddCluster ( containerCluster ) ;
505
492
else context . TaskGroup . AddChild ( containerCluster ) ;
506
493
507
494
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 ) ;
509
496
tryCluster . AddChild ( tryPort ) ;
510
497
containerCluster . AddChild ( tryCluster ) ;
511
498
this . BuildEdge ( context . Graph , containerPort , tryPort ) ;
@@ -531,7 +518,7 @@ protected virtual NodeViewModel BuildTryTaskNode(TaskNodeRenderingContext<TryTas
531
518
else
532
519
{
533
520
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 ) ;
535
522
catchCluster . AddChild ( catchPort ) ;
536
523
containerCluster . AddChild ( catchCluster ) ;
537
524
this . BuildEdge ( context . Graph , tryCluster . AllNodes . Values . Last ( ) , catchPort ) ;
@@ -588,7 +575,7 @@ protected virtual IEdgeViewModel BuildEdge(IGraphViewModel graph, INodeViewModel
588
575
edge . LabelPosition = EdgeLabelPosition . Center ;
589
576
edge . Width = edge . Label . Length * characterSize ;
590
577
}
591
- if ( target . Id . EndsWith ( _portSuffix ) )
578
+ if ( target . Id . EndsWith ( _clusterEntrySuffix ) )
592
579
{
593
580
edge . EndMarkerId = null ;
594
581
}
0 commit comments