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.
This commit is contained in:
Sipke Schoorstra 2023-12-18 20:19:37 +01:00
parent a9ccd4fac8
commit 49ba29da8d
8 changed files with 52 additions and 25 deletions

View file

@ -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<string, string>() }
});
@ -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)));
}

View file

@ -98,5 +98,6 @@ message Bookmark {
map<string, string> Metadata = 8;
optional bool AutoBurn = 9;
optional string CallbackMethodName = 10;
string CreatedAt = 11; // ISO 8601
optional bool AutoComplete = 11;
string CreatedAt = 12; // ISO 8601
}

View file

@ -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);

View file

@ -27,6 +27,7 @@ public record Bookmark(
DateTimeOffset CreatedAt,
bool AutoBurn = true,
string? CallbackMethodName = default,
bool AutoComplete = true,
IDictionary<string, string>? Metadata = default)
{
/// <inheritdoc />

View file

@ -5,15 +5,30 @@ namespace Elsa.Workflows.Core.Models;
/// <summary>
/// Provides bookmark creation options.
/// </summary>
/// <param name="Payload">An optional payload to associate with the bookmark.</param>
/// <param name="Callback">An optional callback to invoke when the bookmark is triggered.</param>
/// <param name="BookmarkName">An optional name to associate with the bookmark.</param>
/// <param name="AutoBurn">Whether or not the bookmark should be automatically burned when triggered.</param>
/// <param name="IncludeActivityInstanceId">Whether or not the activity instance ID should be included in the bookmark payload.</param>
public record CreateBookmarkArgs(
object? Payload = default,
ExecuteActivityDelegate? Callback = default,
string? BookmarkName = default,
bool AutoBurn = true,
bool IncludeActivityInstanceId = true,
IDictionary<string, string>? Metadata = default);
public class CreateBookmarkArgs
{
/// <summary>An optional payload to associate with the bookmark.</summary>
public object? Payload { get; set; }
/// <summary>An optional callback to invoke when the bookmark is triggered.</summary>
public ExecuteActivityDelegate? Callback { get; set; }
/// <summary>An optional name to associate with the bookmark.</summary>
public string? BookmarkName { get; set; }
/// <summary>Whether or not the bookmark should be automatically burned when triggered.</summary>
public bool AutoBurn { get; set; } = true;
/// <summary>Whether or not the activity instance ID should be included in the bookmark payload.</summary>
public bool IncludeActivityInstanceId { get; set; }
/// <summary>
/// Whether or not the activity being resumed should be automatically completed if CallBack is not specified.
/// </summary>
public bool AutoComplete { get; set; } = true;
/// <summary>
/// An optional dictionary of metadata to associate with the bookmark.
/// </summary>
public IDictionary<string, string>? Metadata { get; set; }
}

View file

@ -133,7 +133,7 @@ public class BulkDispatchWorkflows : Activity
ScheduledInstanceIdsCount = dispatchedInstancesCount
},
IncludeActivityInstanceId = false,
AutoBurn = false
AutoBurn = false,
};
context.CreateBookmark(bookmarkOptions);
}

View file

@ -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.
/// </summary>
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));
}

View file

@ -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)