From 49ba29da8d34d68dd4940707308015efad7786ce Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Mon, 18 Dec 2023 20:19:37 +0100 Subject: [PATCH] Add AutoComplete option to workflow bookmarks The AutoComplete option has been added to the workflow bookmarks, which determines whether the activity should automatically be completed if no callback is specified. This change improves the flexibility of bookmark creation and impacts various workflow elements and procedures. The corresponding adjustments have been made at functions where bookmarks are used or created. --- .../Elsa.ProtoActor/Mappers/BookmarkMapper.cs | 2 + .../Proto/WorkflowInstance.Messages.proto | 3 +- .../WorkflowExecutionContextExtensions.cs | 7 +++- .../Elsa.Workflows.Core/Models/Bookmark.cs | 1 + .../Models/CreateBookmarkArgs.cs | 39 +++++++++++++------ .../Activities/BulkDispatchWorkflows.cs | 2 +- .../BackgroundActivityInvokerMiddleware.cs | 4 +- .../Services/DefaultWorkflowInbox.cs | 19 +++++---- 8 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/modules/Elsa.ProtoActor/Mappers/BookmarkMapper.cs b/src/modules/Elsa.ProtoActor/Mappers/BookmarkMapper.cs index b97d99d0a..1be685e7b 100644 --- a/src/modules/Elsa.ProtoActor/Mappers/BookmarkMapper.cs +++ b/src/modules/Elsa.ProtoActor/Mappers/BookmarkMapper.cs @@ -34,6 +34,7 @@ internal class BookmarkMapper ActivityInstanceId = bookmark.ActivityInstanceId, AutoBurn = bookmark.AutoBurn, CallbackMethodName = bookmark.CallbackMethodName.EmptyIfNull(), + AutoComplete = bookmark.AutoComplete, CreatedAt = bookmark.CreatedAt.ToString("O"), Metadata = { bookmark.Metadata ?? new Dictionary() } }); @@ -51,5 +52,6 @@ internal class BookmarkMapper DateTimeOffset.Parse(bookmark.CreatedAt), bookmark.AutoBurn, bookmark.CallbackMethodName.NullIfEmpty(), + bookmark.AutoComplete, bookmark.Metadata.ToDictionary(x => x.Key, x => x.Value))); } \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor/Proto/WorkflowInstance.Messages.proto b/src/modules/Elsa.ProtoActor/Proto/WorkflowInstance.Messages.proto index d9d83a88d..e6090c311 100644 --- a/src/modules/Elsa.ProtoActor/Proto/WorkflowInstance.Messages.proto +++ b/src/modules/Elsa.ProtoActor/Proto/WorkflowInstance.Messages.proto @@ -98,5 +98,6 @@ message Bookmark { map Metadata = 8; optional bool AutoBurn = 9; optional string CallbackMethodName = 10; - string CreatedAt = 11; // ISO 8601 + optional bool AutoComplete = 11; + string CreatedAt = 12; // ISO 8601 } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs index 77b31b1b1..942c7da4c 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs @@ -77,7 +77,12 @@ public static class WorkflowExecutionContextExtensions workflowExecutionContext.Scheduler.Schedule(workItem); // If no resumption point was specified, use a "noop" to prevent the regular "ExecuteAsync" method to be invoked and instead complete the activity. - workflowExecutionContext.ExecuteDelegate = bookmark.CallbackMethodName != null ? bookmarkedActivity.GetResumeActivityDelegate(bookmark.CallbackMethodName) : WorkflowExecutionContext.Noop; + // Unless the bookmark is configured to auto-complete, in which case we'll just complete the activity. + workflowExecutionContext.ExecuteDelegate = bookmark.CallbackMethodName != null + ? bookmarkedActivity.GetResumeActivityDelegate(bookmark.CallbackMethodName) + : bookmark.AutoComplete + ? WorkflowExecutionContext.Complete + : WorkflowExecutionContext.Noop; // Store the bookmark to resume in the context. workflowExecutionContext.ResumedBookmarkContext = new ResumedBookmarkContext(bookmark); diff --git a/src/modules/Elsa.Workflows.Core/Models/Bookmark.cs b/src/modules/Elsa.Workflows.Core/Models/Bookmark.cs index 6f3d8110b..bb20d4c55 100644 --- a/src/modules/Elsa.Workflows.Core/Models/Bookmark.cs +++ b/src/modules/Elsa.Workflows.Core/Models/Bookmark.cs @@ -27,6 +27,7 @@ public record Bookmark( DateTimeOffset CreatedAt, bool AutoBurn = true, string? CallbackMethodName = default, + bool AutoComplete = true, IDictionary? Metadata = default) { /// diff --git a/src/modules/Elsa.Workflows.Core/Models/CreateBookmarkArgs.cs b/src/modules/Elsa.Workflows.Core/Models/CreateBookmarkArgs.cs index 06dcc3a8b..fd652ab08 100644 --- a/src/modules/Elsa.Workflows.Core/Models/CreateBookmarkArgs.cs +++ b/src/modules/Elsa.Workflows.Core/Models/CreateBookmarkArgs.cs @@ -5,15 +5,30 @@ namespace Elsa.Workflows.Core.Models; /// /// Provides bookmark creation options. /// -/// An optional payload to associate with the bookmark. -/// An optional callback to invoke when the bookmark is triggered. -/// An optional name to associate with the bookmark. -/// Whether or not the bookmark should be automatically burned when triggered. -/// Whether or not the activity instance ID should be included in the bookmark payload. -public record CreateBookmarkArgs( - object? Payload = default, - ExecuteActivityDelegate? Callback = default, - string? BookmarkName = default, - bool AutoBurn = true, - bool IncludeActivityInstanceId = true, - IDictionary? Metadata = default); \ No newline at end of file +public class CreateBookmarkArgs +{ + /// An optional payload to associate with the bookmark. + public object? Payload { get; set; } + + /// An optional callback to invoke when the bookmark is triggered. + public ExecuteActivityDelegate? Callback { get; set; } + + /// An optional name to associate with the bookmark. + public string? BookmarkName { get; set; } + + /// Whether or not the bookmark should be automatically burned when triggered. + public bool AutoBurn { get; set; } = true; + + /// Whether or not the activity instance ID should be included in the bookmark payload. + public bool IncludeActivityInstanceId { get; set; } + + /// + /// Whether or not the activity being resumed should be automatically completed if CallBack is not specified. + /// + public bool AutoComplete { get; set; } = true; + + /// + /// An optional dictionary of metadata to associate with the bookmark. + /// + public IDictionary? Metadata { get; set; } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs b/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs index 0ff9cd7fd..d3f579292 100644 --- a/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs +++ b/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs @@ -133,7 +133,7 @@ public class BulkDispatchWorkflows : Activity ScheduledInstanceIdsCount = dispatchedInstancesCount }, IncludeActivityInstanceId = false, - AutoBurn = false + AutoBurn = false, }; context.CreateBookmark(bookmarkOptions); } diff --git a/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs b/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs index d618a85ec..22db0fa84 100644 --- a/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs +++ b/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs @@ -19,7 +19,7 @@ public class BackgroundActivityCollectorMiddleware : DefaultActivityInvokerMiddl /// A key into the activity execution context's transient properties that indicates whether the current activity is being executed in the background. /// public static readonly object IsBackgroundExecution = new(); - + internal static string GetBackgroundActivityOutputKey(string activityId) => $"__BackgroundActivityOutput:{activityId}"; internal static readonly object BackgroundActivitySchedulesKey = new(); internal const string BackgroundActivityBookmarkName = "BackgroundActivity"; @@ -66,7 +66,7 @@ public class BackgroundActivityCollectorMiddleware : DefaultActivityInvokerMiddl var workflowInstanceId = context.WorkflowExecutionContext.Id; var activityNodeId = context.NodeId; var bookmarkPayload = new BackgroundActivityBookmark(); - var bookmarkOptions = new CreateBookmarkArgs { BookmarkName = BackgroundActivityBookmarkName, Payload = bookmarkPayload }; + var bookmarkOptions = new CreateBookmarkArgs { BookmarkName = BackgroundActivityBookmarkName, Payload = bookmarkPayload, AutoComplete = false }; var bookmark = context.CreateBookmark(bookmarkOptions); scheduledBackgroundActivities.Add(new ScheduledBackgroundActivity(workflowInstanceId, activityNodeId, bookmark.Id)); } diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowInbox.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowInbox.cs index 8a0476ac0..2e45cde58 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowInbox.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowInbox.cs @@ -104,17 +104,20 @@ public class DefaultWorkflowInbox : IWorkflowInbox var activityInstanceId = message.ActivityInstanceId; var bookmarkPayload = message.BookmarkPayload; var input = message.Input; - + if (workflowInstanceId != null) { - await _workflowDispatcher.DispatchAsync(new DispatchTriggerWorkflowsRequest(activityTypeName, bookmarkPayload) - { - CorrelationId = correlationId, - WorkflowInstanceId = workflowInstanceId, - ActivityInstanceId = activityInstanceId, - Input = input - }, cancellationToken); + await ResumeWorkflowsAsync(message, cancellationToken); + return; } + + await _workflowDispatcher.DispatchAsync(new DispatchTriggerWorkflowsRequest(activityTypeName, bookmarkPayload) + { + CorrelationId = correlationId, + WorkflowInstanceId = workflowInstanceId, + ActivityInstanceId = activityInstanceId, + Input = input + }, cancellationToken); } private async Task ResumeWorkflowsAsync(WorkflowInboxMessage message, CancellationToken cancellationToken = default)