From dd2004dd89ec4a6ce76b1293a2b933bfbe7ef492 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Mon, 21 Dec 2020 12:10:41 +0100 Subject: [PATCH] Fix CompositeActivity behavior and update sample --- .../Converters/StackJsonConverter.cs | 27 +++++++++++++++ .../Services/CompositeActivity.cs | 16 +++++++-- .../Models/WorkflowExecutionContext.cs | 8 +++-- .../Serialization/DefaultContentSerializer.cs | 1 + .../CustomJsonContentSerializer.cs | 11 ++++-- .../Activities/MakePhoneCall.cs | 34 +++++++++++++++++++ .../Elsa.Samples.WhileLoopWorker.csproj | 5 +-- .../Elsa.Samples.WhileLoopWorker/Program.cs | 5 ++- .../Workflows/PhoneCallWorkflow.cs | 18 ++-------- 9 files changed, 101 insertions(+), 24 deletions(-) create mode 100644 src/core/Elsa.Abstractions/Converters/StackJsonConverter.cs create mode 100644 src/samples/worker/Elsa.Samples.WhileLoopWorker/Activities/MakePhoneCall.cs diff --git a/src/core/Elsa.Abstractions/Converters/StackJsonConverter.cs b/src/core/Elsa.Abstractions/Converters/StackJsonConverter.cs new file mode 100644 index 000000000..26cecb75d --- /dev/null +++ b/src/core/Elsa.Abstractions/Converters/StackJsonConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Elsa.Converters +{ + /// + /// Correctly deserializes a maintaining the order of the items. + /// + public class StackJsonConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + { + var newSerializer = new JsonSerializer(); + newSerializer.Serialize(writer, value); + } + + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + { + var newSerializer = new JsonSerializer(); + var list = newSerializer.Deserialize(reader, objectType)!; + return Activator.CreateInstance(objectType, list); + } + + public override bool CanConvert(Type objectType) => objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Stack<>); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/CompositeActivity.cs b/src/core/Elsa.Abstractions/Services/CompositeActivity.cs index 5beaee27b..3e36b5206 100644 --- a/src/core/Elsa.Abstractions/Services/CompositeActivity.cs +++ b/src/core/Elsa.Abstractions/Services/CompositeActivity.cs @@ -21,12 +21,15 @@ namespace Elsa.Services { if (IsScheduled) { + if (HasPendingChildren(context)) + return PostSchedule(Id); + context.WorkflowExecutionContext.WorkflowInstance.ParentActivities.Pop(); IsScheduled = false; return Complete(context); } - - var compositeActivityBlueprint = (ICompositeActivityBlueprint)context.ActivityBlueprint; + + var compositeActivityBlueprint = (ICompositeActivityBlueprint) context.ActivityBlueprint; var startActivities = compositeActivityBlueprint.GetStartActivities().Select(x => x.Id).ToList(); context.WorkflowExecutionContext.WorkflowInstance.ParentActivities.Push(Id); context.WorkflowExecutionContext.PostScheduleActivity(Id); @@ -35,5 +38,14 @@ namespace Elsa.Services } protected virtual IActivityExecutionResult Complete(ActivityExecutionContext context) => Done(); + + private static bool HasPendingChildren(ActivityExecutionContext context) + { + var children = ((CompositeActivityBlueprint) context.ActivityBlueprint).Activities.Select(x => x.Id).ToList(); + var workflowInstance = context.WorkflowExecutionContext.WorkflowInstance; + var hasPendingPostScheduledChildren = workflowInstance.PostScheduledActivities.Any(x => children.Contains(x.ActivityId)); + var hasPendingScheduledChildren = workflowInstance.ScheduledActivities.Any(x => children.Contains(x.ActivityId)); + return hasPendingPostScheduledChildren || hasPendingScheduledChildren; + } } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs b/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs index 12627b691..e66e5c26c 100644 --- a/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs +++ b/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs @@ -97,8 +97,12 @@ namespace Elsa.Services.Models public void SchedulePostActivities() { - while (HasPostScheduledActivities) - ScheduleActivity(WorkflowInstance.PostScheduledActivities.Pop()); + var activities = WorkflowInstance.PostScheduledActivities.Reverse(); + + foreach (var activity in activities) + ScheduleActivity(activity); + + WorkflowInstance.PostScheduledActivities.Clear(); } public object? GetOutputFrom(string activityName) diff --git a/src/core/Elsa.Core/Serialization/DefaultContentSerializer.cs b/src/core/Elsa.Core/Serialization/DefaultContentSerializer.cs index b4ea892e0..14204989c 100644 --- a/src/core/Elsa.Core/Serialization/DefaultContentSerializer.cs +++ b/src/core/Elsa.Core/Serialization/DefaultContentSerializer.cs @@ -46,6 +46,7 @@ namespace Elsa.Serialization settings.Converters.Add(new StringEnumConverter(new DefaultNamingStrategy())); settings.Converters.Add(new TypeJsonConverter()); settings.Converters.Add(new VersionOptionsJsonConverter()); + settings.Converters.Add(new StackJsonConverter()); } public static JsonSerializerSettings CreateDefaultJsonSerializationSettings() diff --git a/src/persistence/Elsa.Persistence.YesSql/CustomJsonContentSerializer.cs b/src/persistence/Elsa.Persistence.YesSql/CustomJsonContentSerializer.cs index 78ac03685..0d2248a5d 100644 --- a/src/persistence/Elsa.Persistence.YesSql/CustomJsonContentSerializer.cs +++ b/src/persistence/Elsa.Persistence.YesSql/CustomJsonContentSerializer.cs @@ -1,6 +1,7 @@ using System; - +using Elsa.Converters; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using NodaTime; @@ -19,7 +20,13 @@ namespace Elsa.Persistence.YesSql JsonSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto - }.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); + }; + + JsonSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); + JsonSettings.Converters.Add(new StackJsonConverter()); + JsonSettings.Converters.Add(new VersionOptionsJsonConverter()); + JsonSettings.Converters.Add(new TypeJsonConverter()); + JsonSettings.Converters.Add(new StringEnumConverter(new DefaultNamingStrategy())); JsonSettings.ContractResolver = new DefaultContractResolver { diff --git a/src/samples/worker/Elsa.Samples.WhileLoopWorker/Activities/MakePhoneCall.cs b/src/samples/worker/Elsa.Samples.WhileLoopWorker/Activities/MakePhoneCall.cs new file mode 100644 index 000000000..062f1a180 --- /dev/null +++ b/src/samples/worker/Elsa.Samples.WhileLoopWorker/Activities/MakePhoneCall.cs @@ -0,0 +1,34 @@ +using Elsa.Activities.Console; +using Elsa.Activities.ControlFlow; +using Elsa.Activities.Timers; +using Elsa.Builders; +using Elsa.Samples.WhileLoopWorker.Services; +using Elsa.Services; +using NodaTime; + +namespace Elsa.Samples.WhileLoopWorker.Activities +{ + public class MakePhoneCall : CompositeActivity + { + private readonly PhoneCallService _phoneCallService; + + public MakePhoneCall(PhoneCallService phoneCallService) + { + _phoneCallService = phoneCallService; + } + + public override void Build(ICompositeActivityBuilder composite) + { + composite + .While(() => _phoneCallService.CallStatus != PhoneCallStatus.Finished, + @while => + { + @while + .WriteLine("Ringgggg ringgg.") + .Timer(Duration.FromSeconds(5)) + .Then(() => _phoneCallService.Progress()) + .WriteLine(() => $"Call status: {_phoneCallService.CallStatus}"); + }); + } + } +} \ No newline at end of file diff --git a/src/samples/worker/Elsa.Samples.WhileLoopWorker/Elsa.Samples.WhileLoopWorker.csproj b/src/samples/worker/Elsa.Samples.WhileLoopWorker/Elsa.Samples.WhileLoopWorker.csproj index 0c4b09476..5d05f57bb 100644 --- a/src/samples/worker/Elsa.Samples.WhileLoopWorker/Elsa.Samples.WhileLoopWorker.csproj +++ b/src/samples/worker/Elsa.Samples.WhileLoopWorker/Elsa.Samples.WhileLoopWorker.csproj @@ -6,10 +6,11 @@ - + - + + diff --git a/src/samples/worker/Elsa.Samples.WhileLoopWorker/Program.cs b/src/samples/worker/Elsa.Samples.WhileLoopWorker/Program.cs index fbf37c630..e35cd2afb 100644 --- a/src/samples/worker/Elsa.Samples.WhileLoopWorker/Program.cs +++ b/src/samples/worker/Elsa.Samples.WhileLoopWorker/Program.cs @@ -3,6 +3,8 @@ using Elsa.Samples.WhileLoopWorker.Workflows; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Elsa.Persistence.InMemory; +using Elsa.Persistence.YesSql.Extensions; +using Elsa.Samples.WhileLoopWorker.Activities; namespace Elsa.Samples.WhileLoopWorker { @@ -16,11 +18,12 @@ namespace Elsa.Samples.WhileLoopWorker (_, services) => { services - .AddElsa() + .AddElsa(options => options.UseYesSqlPersistence()) .AddConsoleActivities() .AddTimerActivities() .AddSingleton() .AddHostedService() + .AddActivity() .AddWorkflow(); }); } diff --git a/src/samples/worker/Elsa.Samples.WhileLoopWorker/Workflows/PhoneCallWorkflow.cs b/src/samples/worker/Elsa.Samples.WhileLoopWorker/Workflows/PhoneCallWorkflow.cs index 637c5e6b6..4f03081a2 100644 --- a/src/samples/worker/Elsa.Samples.WhileLoopWorker/Workflows/PhoneCallWorkflow.cs +++ b/src/samples/worker/Elsa.Samples.WhileLoopWorker/Workflows/PhoneCallWorkflow.cs @@ -1,9 +1,7 @@ using Elsa.Activities.Console; -using Elsa.Activities.ControlFlow; -using Elsa.Activities.Timers; using Elsa.Builders; +using Elsa.Samples.WhileLoopWorker.Activities; using Elsa.Samples.WhileLoopWorker.Services; -using NodaTime; namespace Elsa.Samples.WhileLoopWorker.Workflows { @@ -23,18 +21,8 @@ namespace Elsa.Samples.WhileLoopWorker.Workflows public void Build(IWorkflowBuilder workflow) { workflow - .WriteLine("Simulating a phone call... ringgg ringgg.") - .While(() => _phoneCallService.CallStatus != PhoneCallStatus.Finished, - @while => - { - @while - .WriteLine("Ringgggg ringgg.") - .WriteLine(() => $"Call status: {_phoneCallService.CallStatus}") - .Timer(Duration.FromSeconds(5)) - .Then(() => _phoneCallService.Progress()); - - }) - .WriteLine(() => $"Call status: {_phoneCallService.CallStatus}") + .WriteLine("Simulating a phone call...") + .Then() .WriteLine("Workflow finished."); } }