Improves Flowchart activity robustness (#6938)
* Bump workflow base version and branch references to `3.5.2`. * Refactor flowchart tests and `Flowchart` activity for improved readability and consistency, alongside minor code cleanup. * Adds comment for clarity. Adds a comment to explain the continue statement within the flowchart execution logic. This improves code readability and maintainability. * Refactor flowchart test to remove unused cases, update switch behavior, and adjust expected output for improved consistency and clarity.
This commit is contained in:
parent
d18809baa1
commit
c6974a4e34
|
|
@ -151,7 +151,7 @@ public class Flowchart : Container
|
|||
|
||||
if (flowchartContext.Activity != this)
|
||||
{
|
||||
throw new Exception("Target context activity must be this flowchart");
|
||||
throw new("Target context activity must be this flowchart");
|
||||
}
|
||||
|
||||
// If the completed activity's status is anything but "Completed", do not schedule its outbound activities.
|
||||
|
|
@ -167,16 +167,16 @@ public class Flowchart : Container
|
|||
return;
|
||||
}
|
||||
|
||||
// Determine the outcomes from the completed activity
|
||||
// Determine the outcomes from the completed activity.
|
||||
var outcomes = result is Outcomes o ? o : Outcomes.Default;
|
||||
|
||||
// Schedule the outbound activities
|
||||
var flowGraph = GetFlowGraph(flowchartContext);
|
||||
var flowScope = GetFlowScope(flowchartContext);
|
||||
var completedActivityExcecutedByBackwardConnection = completedActivityContext.ActivityInput.GetValueOrDefault<bool>(BackwardConnectionActivityInput);
|
||||
bool hasScheduledActivity = await ScheduleOutboundActivitiesAsync(flowGraph, flowScope, flowchartContext, completedActivity, outcomes, completedActivityExcecutedByBackwardConnection);
|
||||
var hasScheduledActivity = await ScheduleOutboundActivitiesAsync(flowGraph, flowScope, flowchartContext, completedActivity, outcomes, completedActivityExcecutedByBackwardConnection);
|
||||
|
||||
// If there are not any outbound connections, complete the flowchart activity if there is no other pending work
|
||||
// If there are not any outbound connections, complete the flowchart activity if there is no other pending work.
|
||||
if (!hasScheduledActivity)
|
||||
{
|
||||
await CompleteIfNoPendingWorkAsync(flowchartContext);
|
||||
|
|
@ -198,12 +198,12 @@ public class Flowchart : Container
|
|||
/// <returns>True if at least one activity was scheduled; otherwise, false.</returns>
|
||||
private async ValueTask<bool> ScheduleOutboundActivitiesAsync(FlowGraph flowGraph, FlowScope flowScope, ActivityExecutionContext flowchartContext, IActivity activity, Outcomes outcomes, bool completedActivityExecutedByBackwardConnection = false)
|
||||
{
|
||||
bool hasScheduledActivity = false;
|
||||
var hasScheduledActivity = false;
|
||||
|
||||
// Check if the activity is dangling (i.e., it is not reachable from the flowchart graph)
|
||||
if (flowGraph.IsDanglingActivity(activity))
|
||||
{
|
||||
throw new Exception($"Activity {activity.Id} is not reachable from the flowchart graph. Unable to schedule it's outbound activities.");
|
||||
throw new($"Activity {activity.Id} is not reachable from the flowchart graph. Unable to schedule it's outbound activities.");
|
||||
}
|
||||
|
||||
// Register the activity as visited unless it was executed due to a backward connection
|
||||
|
|
@ -215,12 +215,16 @@ public class Flowchart : Container
|
|||
// Process each outbound connection from the current activity
|
||||
foreach (var outboundConnection in flowGraph.GetOutboundConnections(activity))
|
||||
{
|
||||
bool connectionFollowed = outcomes.Names.Contains(outboundConnection.Source.Port);
|
||||
var connectionFollowed = outcomes.Names.Contains(outboundConnection.Source.Port);
|
||||
flowScope.RegisterConnectionVisit(outboundConnection, connectionFollowed);
|
||||
|
||||
if(!connectionFollowed)
|
||||
continue; // Skip if connection was not followed.
|
||||
|
||||
var outboundActivity = outboundConnection.Target.Activity;
|
||||
|
||||
// Determine scheduling strategy based on connection type
|
||||
if (flowGraph.IsBackwardConnection(outboundConnection, out bool backwardConnectionIsValid))
|
||||
// Determine the scheduling strategy based on connection-type.
|
||||
if (flowGraph.IsBackwardConnection(outboundConnection, out var backwardConnectionIsValid))
|
||||
{
|
||||
hasScheduledActivity |= await ScheduleBackwardConnectionActivityAsync(flowGraph, flowchartContext, outboundConnection, outboundActivity, connectionFollowed, backwardConnectionIsValid);
|
||||
}
|
||||
|
|
@ -248,7 +252,7 @@ public class Flowchart : Container
|
|||
|
||||
if (!backwardConnectionIsValid)
|
||||
{
|
||||
throw new Exception($"Invalid backward connection: Every path from the source ('{outboundConnection.Source.Activity.Id}') must go through the target ('{outboundConnection.Target.Activity.Id}') when tracing back to the start.");
|
||||
throw new($"Invalid backward connection: Every path from the source ('{outboundConnection.Source.Activity.Id}') must go through the target ('{outboundConnection.Target.Activity.Id}') when tracing back to the start.");
|
||||
}
|
||||
|
||||
var scheduleWorkOptions = new ScheduleWorkOptions
|
||||
|
|
@ -395,7 +399,7 @@ public class Flowchart : Container
|
|||
|
||||
if (activityExecutionContext != null)
|
||||
{
|
||||
await flowchartContext.ScheduleActivityAsync(activityExecutionContext.Activity, new ScheduleWorkOptions
|
||||
await flowchartContext.ScheduleActivityAsync(activityExecutionContext.Activity, new()
|
||||
{
|
||||
ExistingActivityExecutionContext = activityExecutionContext,
|
||||
CompletionCallback = OnChildCompletedAsync,
|
||||
|
|
@ -404,7 +408,7 @@ public class Flowchart : Container
|
|||
}
|
||||
else
|
||||
{
|
||||
await flowchartContext.ScheduleActivityAsync(activity, new ScheduleWorkOptions
|
||||
await flowchartContext.ScheduleActivityAsync(activity, new()
|
||||
{
|
||||
CompletionCallback = OnChildCompletedAsync,
|
||||
Input = signal.Input
|
||||
|
|
|
|||
|
|
@ -32,7 +32,10 @@ public class FlowchartNextActivityTests
|
|||
await _services.PopulateRegistriesAsync();
|
||||
await _workflowRunner.RunAsync<FlowchartWorkflow>();
|
||||
var lines = _capturingTextWriter.Lines.ToList();
|
||||
Assert.Equal(new[] { "Line 1" }, lines);
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
"Line 1"
|
||||
}, lines);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Flowchart with backward connections and a dangling activity")]
|
||||
|
|
@ -46,15 +49,14 @@ public class FlowchartNextActivityTests
|
|||
var dangling = new WriteLine("dangling");
|
||||
var writeLineDecision = new FlowSwitch()
|
||||
{
|
||||
Cases = {
|
||||
new FlowSwitchCase("LessThanThree", new Expression("JavaScript", "getVariable('LoopCount') < 3")),
|
||||
new FlowSwitchCase("LessThanOne", new Expression("JavaScript", "getVariable('LoopCount') < 1")),
|
||||
},
|
||||
Cases =
|
||||
{
|
||||
new FlowSwitchCase("LessThanThree", new Expression("JavaScript", "getVariable('LoopCount') < 3"))
|
||||
},
|
||||
Mode = new(SwitchMode.MatchAny)
|
||||
};
|
||||
var a = new WriteLine("A");
|
||||
var b = new WriteLine("B");
|
||||
var c = new WriteLine("C");
|
||||
var incrementLoop = new SetVariable()
|
||||
{
|
||||
Variable = loopVariable,
|
||||
|
|
@ -62,56 +64,54 @@ public class FlowchartNextActivityTests
|
|||
};
|
||||
var loopbackDecision = new FlowSwitch()
|
||||
{
|
||||
Cases = {
|
||||
Cases =
|
||||
{
|
||||
new FlowSwitchCase("EqualOne", new Expression("JavaScript", "getVariable('LoopCount') == 1")),
|
||||
new FlowSwitchCase("LessThanFour", new Expression("JavaScript", "getVariable('LoopCount') < 4")),
|
||||
},
|
||||
Mode = new(SwitchMode.MatchFirst)
|
||||
new FlowSwitchCase("EqualThree", new Expression("JavaScript", "getVariable('LoopCount') == 3")),
|
||||
},
|
||||
Mode = new(SwitchMode.MatchAny)
|
||||
};
|
||||
var d = new WriteLine("D");
|
||||
var e = new WriteLine("E");
|
||||
var f = new WriteLine("F");
|
||||
var end = new End();
|
||||
|
||||
|
||||
workflowBuilder.Root = new Flowchart
|
||||
{
|
||||
Variables =
|
||||
{
|
||||
loopVariable
|
||||
},
|
||||
{
|
||||
loopVariable
|
||||
},
|
||||
Activities =
|
||||
{
|
||||
start,
|
||||
dangling,
|
||||
writeLineDecision,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
incrementLoop,
|
||||
loopbackDecision,
|
||||
d,
|
||||
e,
|
||||
f,
|
||||
end
|
||||
},
|
||||
{
|
||||
start,
|
||||
dangling,
|
||||
writeLineDecision,
|
||||
a,
|
||||
b,
|
||||
incrementLoop,
|
||||
loopbackDecision,
|
||||
d,
|
||||
e,
|
||||
f,
|
||||
end
|
||||
},
|
||||
Connections =
|
||||
{
|
||||
new(start, writeLineDecision),
|
||||
new(dangling, writeLineDecision),
|
||||
new(new Endpoint(writeLineDecision, "LessThanThree"), new Endpoint(a)),
|
||||
new(new Endpoint(writeLineDecision, "LessThanThree"), new Endpoint(b)),
|
||||
new(new Endpoint(writeLineDecision, "LessThanOne"), new Endpoint(c)),
|
||||
new(new Endpoint(writeLineDecision, "Default"), new Endpoint(incrementLoop)),
|
||||
new(a, incrementLoop),
|
||||
new(b, incrementLoop),
|
||||
new(c, incrementLoop),
|
||||
new(incrementLoop, loopbackDecision),
|
||||
new(new Endpoint(loopbackDecision, "EqualOne"), new Endpoint(d)),
|
||||
new(d, incrementLoop),
|
||||
new(new Endpoint(loopbackDecision, "LessThanFour"), new Endpoint(e)),
|
||||
new(e, writeLineDecision),
|
||||
new(new Endpoint(loopbackDecision, "Default"), new Endpoint(f)),
|
||||
new(new Endpoint(loopbackDecision, "EqualThree"), new Endpoint(f)),
|
||||
new(f, end),
|
||||
}
|
||||
};
|
||||
|
|
@ -121,7 +121,10 @@ public class FlowchartNextActivityTests
|
|||
var result = await _workflowRunner.RunAsync(workflow);
|
||||
var lines = _capturingTextWriter.Lines.ToList();
|
||||
Assert.Equal(WorkflowSubStatus.Finished, result.WorkflowState.SubStatus);
|
||||
Assert.Equal(new[] { "A", "B", "C", "D", "E", "A", "B", "E", "F" }, lines);
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
"A", "B", "D", "E", "A", "B", "E", "E", "F"
|
||||
}, lines);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Flowchart with an invalid backward connection")]
|
||||
|
|
@ -129,24 +132,42 @@ public class FlowchartNextActivityTests
|
|||
{
|
||||
var workflow = new TestWorkflow(workflowBuilder =>
|
||||
{
|
||||
var start = new Start() { Id = "Start" };
|
||||
var a = new WriteLine("A") { Id = "WriteLineA" };
|
||||
var b = new WriteLine("B") { Id = "WriteLineB" };
|
||||
var c = new WriteLine("C") { Id = "WriteLineC" };
|
||||
var d = new WriteLine("D") { Id = "WriteLineD" };
|
||||
var e = new WriteLine("E") { Id = "WriteLineE" };
|
||||
var start = new Start()
|
||||
{
|
||||
Id = "Start"
|
||||
};
|
||||
var a = new WriteLine("A")
|
||||
{
|
||||
Id = "WriteLineA"
|
||||
};
|
||||
var b = new WriteLine("B")
|
||||
{
|
||||
Id = "WriteLineB"
|
||||
};
|
||||
var c = new WriteLine("C")
|
||||
{
|
||||
Id = "WriteLineC"
|
||||
};
|
||||
var d = new WriteLine("D")
|
||||
{
|
||||
Id = "WriteLineD"
|
||||
};
|
||||
var e = new WriteLine("E")
|
||||
{
|
||||
Id = "WriteLineE"
|
||||
};
|
||||
|
||||
workflowBuilder.Root = new Flowchart
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
start,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
},
|
||||
{
|
||||
start,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
},
|
||||
Connections =
|
||||
{
|
||||
new(start, a),
|
||||
|
|
@ -166,7 +187,10 @@ public class FlowchartNextActivityTests
|
|||
Assert.Equal(WorkflowSubStatus.Faulted, result.WorkflowState.SubStatus);
|
||||
Assert.Equal(1, result.WorkflowState.Incidents.Count());
|
||||
Assert.Equal("Invalid backward connection: Every path from the source ('WriteLineE') must go through the target ('WriteLineC') when tracing back to the start.", result.WorkflowState.Incidents.First().Message);
|
||||
Assert.Equal(new[] { "A", "B", "C", "D", "E" }, lines);
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
"A", "B", "C", "D", "E"
|
||||
}, lines);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "Flowchart with a Join activity executed multiple times")]
|
||||
|
|
@ -194,7 +218,8 @@ public class FlowchartNextActivityTests
|
|||
};
|
||||
var loopbackDecision = new FlowSwitch()
|
||||
{
|
||||
Cases = {
|
||||
Cases =
|
||||
{
|
||||
new FlowSwitchCase("LessThanThree", new Expression("JavaScript", "getVariable('LoopCount') < 3")),
|
||||
},
|
||||
Mode = new(SwitchMode.MatchFirst)
|
||||
|
|
@ -205,21 +230,21 @@ public class FlowchartNextActivityTests
|
|||
workflowBuilder.Root = new Flowchart
|
||||
{
|
||||
Variables =
|
||||
{
|
||||
loopVariable
|
||||
},
|
||||
{
|
||||
loopVariable
|
||||
},
|
||||
Activities =
|
||||
{
|
||||
start,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
join,
|
||||
incrementLoop,
|
||||
loopbackDecision,
|
||||
end
|
||||
},
|
||||
{
|
||||
start,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
join,
|
||||
incrementLoop,
|
||||
loopbackDecision,
|
||||
end
|
||||
},
|
||||
Connections =
|
||||
{
|
||||
new(start, a),
|
||||
|
|
@ -230,7 +255,7 @@ public class FlowchartNextActivityTests
|
|||
new(c, join),
|
||||
new(d, join),
|
||||
new(join, incrementLoop),
|
||||
new(incrementLoop,loopbackDecision),
|
||||
new(incrementLoop, loopbackDecision),
|
||||
new(new Endpoint(loopbackDecision, "LessThanThree"), new Endpoint(a)),
|
||||
new(new Endpoint(loopbackDecision, "Default"), new Endpoint(end)),
|
||||
}
|
||||
|
|
@ -241,8 +266,10 @@ public class FlowchartNextActivityTests
|
|||
var result = await _workflowRunner.RunAsync(workflow);
|
||||
var lines = _capturingTextWriter.Lines.ToList();
|
||||
Assert.Equal(WorkflowSubStatus.Finished, result.WorkflowState.SubStatus);
|
||||
Assert.Equal(new[] { "A", "B", "C", "D", "A", "B", "C", "D", "A", "B", "C", "D"}, lines);
|
||||
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
"A", "B", "C", "D", "A", "B", "C", "D", "A", "B", "C", "D"
|
||||
}, lines);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "Flowchart with a Join activity executed multiple times, bug 6479")]
|
||||
|
|
@ -257,7 +284,8 @@ public class FlowchartNextActivityTests
|
|||
var start = new Start();
|
||||
var loopbackSwitch = new FlowSwitch()
|
||||
{
|
||||
Cases = {
|
||||
Cases =
|
||||
{
|
||||
new FlowSwitchCase("DoLoopback", new Expression("JavaScript", "getVariable('LoopCount') < 3")),
|
||||
},
|
||||
Mode = new(SwitchMode.MatchFirst)
|
||||
|
|
@ -279,19 +307,19 @@ public class FlowchartNextActivityTests
|
|||
workflowBuilder.Root = new Flowchart
|
||||
{
|
||||
Variables =
|
||||
{
|
||||
loopVariable
|
||||
},
|
||||
{
|
||||
loopVariable
|
||||
},
|
||||
Activities =
|
||||
{
|
||||
start,
|
||||
loopbackSwitch,
|
||||
a,
|
||||
incrementLoop,
|
||||
join,
|
||||
b,
|
||||
end
|
||||
},
|
||||
{
|
||||
start,
|
||||
loopbackSwitch,
|
||||
a,
|
||||
incrementLoop,
|
||||
join,
|
||||
b,
|
||||
end
|
||||
},
|
||||
Connections =
|
||||
{
|
||||
new(start, loopbackSwitch),
|
||||
|
|
@ -310,6 +338,9 @@ public class FlowchartNextActivityTests
|
|||
var result = await _workflowRunner.RunAsync(workflow);
|
||||
var lines = _capturingTextWriter.Lines.ToList();
|
||||
Assert.Equal(WorkflowSubStatus.Finished, result.WorkflowState.SubStatus);
|
||||
Assert.Equal(new[] { "A", "A", "A", "B" }, lines);
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
"A", "A", "A", "B"
|
||||
}, lines);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue