Remove obsolete properties and refactor activity evaluation (#6603)
* Remove obsolete properties and refactor activity evaluation Refactored activity input and log persistence property evaluation using improved notification handlers. Removed redundant `ActivityState` property and associated serialization logic, ensuring payloads are only serialized when necessary. All changes streamline workflow processing and enhance maintainability. * Refactor mediator call to inline cancellation token. Replaced the separate variable for the cancellation token with an inline reference for clarity and reduced redundancy. This simplifies the code without altering functionality.
This commit is contained in:
parent
476656ccce
commit
c8ed08cc85
|
|
@ -104,12 +104,12 @@ internal class DapperWorkflowExecutionLogStore(Store<WorkflowExecutionLogRecordR
|
|||
|
||||
private Page<WorkflowExecutionLogRecord> Map(Page<WorkflowExecutionLogRecordRecord> source)
|
||||
{
|
||||
return new Page<WorkflowExecutionLogRecord>(source.Items.Select(Map).ToList(), source.TotalCount);
|
||||
return new(source.Items.Select(Map).ToList(), source.TotalCount);
|
||||
}
|
||||
|
||||
private WorkflowExecutionLogRecordRecord Map(WorkflowExecutionLogRecord source)
|
||||
{
|
||||
return new WorkflowExecutionLogRecordRecord
|
||||
return new()
|
||||
{
|
||||
Id = source.Id,
|
||||
WorkflowDefinitionId = source.WorkflowDefinitionId,
|
||||
|
|
@ -128,15 +128,24 @@ internal class DapperWorkflowExecutionLogStore(Store<WorkflowExecutionLogRecordR
|
|||
EventName = source.EventName,
|
||||
Message = source.Message,
|
||||
Source = source.Source,
|
||||
SerializedActivityState = source.ActivityState != null ? payloadSerializer.Serialize(source.ActivityState) : null,
|
||||
SerializedPayload = source.Payload != null ? payloadSerializer.Serialize(source.Payload) : null,
|
||||
SerializedPayload = ShouldSerializePayload(source) ? payloadSerializer.Serialize(source.Payload!) : null,
|
||||
TenantId = source.TenantId
|
||||
};
|
||||
}
|
||||
|
||||
private bool ShouldSerializePayload(WorkflowExecutionLogRecord source)
|
||||
{
|
||||
return source.Payload switch
|
||||
{
|
||||
null => false,
|
||||
IDictionary<string, object> dictionary => dictionary.Count > 0,
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
|
||||
private WorkflowExecutionLogRecord Map(WorkflowExecutionLogRecordRecord source)
|
||||
{
|
||||
return new WorkflowExecutionLogRecord
|
||||
return new()
|
||||
{
|
||||
Id = source.Id,
|
||||
WorkflowDefinitionId = source.WorkflowDefinitionId,
|
||||
|
|
@ -155,7 +164,6 @@ internal class DapperWorkflowExecutionLogStore(Store<WorkflowExecutionLogRecordR
|
|||
EventName = source.EventName,
|
||||
Message = source.Message,
|
||||
Source = source.Source,
|
||||
ActivityState = source.SerializedActivityState != null ? payloadSerializer.Deserialize<IDictionary<string, object>>(source.SerializedActivityState) : null,
|
||||
Payload = source.SerializedPayload != null ? payloadSerializer.Deserialize(source.SerializedPayload) : null,
|
||||
TenantId = source.TenantId
|
||||
};
|
||||
|
|
|
|||
|
|
@ -74,8 +74,7 @@ public class EFCoreWorkflowExecutionLogStore(EntityStore<RuntimeElsaDbContext, W
|
|||
private async ValueTask OnSaveAsync(RuntimeElsaDbContext dbContext, WorkflowExecutionLogRecord entity, CancellationToken cancellationToken)
|
||||
{
|
||||
entity = entity.SanitizeLogMessage();
|
||||
dbContext.Entry(entity).Property("SerializedActivityState").CurrentValue = entity.ActivityState?.Any() == true ? safeSerializer.Serialize(entity.ActivityState) : null;
|
||||
dbContext.Entry(entity).Property("SerializedPayload").CurrentValue = entity.Payload != null ? safeSerializer.Serialize(entity.Payload) : null;
|
||||
dbContext.Entry(entity).Property("SerializedPayload").CurrentValue = ShouldSerializePayload(entity) ? safeSerializer.Serialize(entity.Payload) : null;
|
||||
}
|
||||
|
||||
private async ValueTask OnLoadAsync(RuntimeElsaDbContext dbContext, WorkflowExecutionLogRecord? entity, CancellationToken cancellationToken)
|
||||
|
|
@ -84,7 +83,6 @@ public class EFCoreWorkflowExecutionLogStore(EntityStore<RuntimeElsaDbContext, W
|
|||
return;
|
||||
|
||||
entity.Payload = await LoadPayload(dbContext, entity);
|
||||
entity.ActivityState = await LoadActivityState(dbContext, entity);
|
||||
}
|
||||
|
||||
private ValueTask<object?> LoadPayload(RuntimeElsaDbContext dbContext, WorkflowExecutionLogRecord entity)
|
||||
|
|
@ -93,10 +91,14 @@ public class EFCoreWorkflowExecutionLogStore(EntityStore<RuntimeElsaDbContext, W
|
|||
return new(!string.IsNullOrEmpty(json) ? JsonSerializer.Deserialize<object>(json) : null);
|
||||
}
|
||||
|
||||
private ValueTask<IDictionary<string, object>?> LoadActivityState(RuntimeElsaDbContext dbContext, WorkflowExecutionLogRecord entity)
|
||||
private bool ShouldSerializePayload(WorkflowExecutionLogRecord source)
|
||||
{
|
||||
var json = dbContext.Entry(entity).Property<string>("SerializedActivityState").CurrentValue;
|
||||
return new(!string.IsNullOrEmpty(json) ? JsonSerializer.Deserialize<IDictionary<string, object>>(json) : null);
|
||||
return source.Payload switch
|
||||
{
|
||||
null => false,
|
||||
IDictionary<string, object> dictionary => dictionary.Count > 0,
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
|
||||
private static IQueryable<WorkflowExecutionLogRecord> Filter(IQueryable<WorkflowExecutionLogRecord> queryable, WorkflowExecutionLogRecordFilter filter) => filter.Apply(queryable);
|
||||
|
|
|
|||
|
|
@ -14,17 +14,17 @@ namespace Elsa.Scheduling.Activities;
|
|||
public class Cron : EventGenerator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Cron([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line)
|
||||
public Cron([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Cron(string cronExpression, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(new Input<string>(cronExpression), source, line)
|
||||
public Cron(string cronExpression, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(new Input<string>(cronExpression), source, line)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Cron(Input<string> cronExpression, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line)
|
||||
public Cron(Input<string> cronExpression, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(source, line)
|
||||
{
|
||||
CronExpression = cronExpression;
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ public class Cron : EventGenerator
|
|||
/// The interval at which the timer should execute.
|
||||
/// </summary>
|
||||
[Input(Description = "The CRON expression at which the timer should execute.")]
|
||||
public Input<string> CronExpression { get; set; } = default!;
|
||||
public Input<string> CronExpression { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
|
|
@ -62,5 +62,5 @@ public class Cron : EventGenerator
|
|||
/// <summary>
|
||||
/// Creates a new <see cref="Cron"/> activity set to trigger at the specified cron expression.
|
||||
/// </summary>
|
||||
public static Cron FromCronExpression(string value, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) => new(value, source, line);
|
||||
public static Cron FromCronExpression(string value, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) => new(value, source, line);
|
||||
}
|
||||
|
|
@ -14,5 +14,5 @@ internal record ExecutionLogRecord(
|
|||
string? EventName,
|
||||
string? Message,
|
||||
string? Source,
|
||||
IDictionary<string, object>? ActivityState,
|
||||
[property: Obsolete] IDictionary<string, object>? ActivityState,
|
||||
object? Payload);
|
||||
|
|
@ -21,7 +21,7 @@ public abstract class Activity : IActivity, ISignalHandler
|
|||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
protected Activity(string? source = default, int? line = default)
|
||||
protected Activity(string? source = null, int? line = null)
|
||||
{
|
||||
this.SetSource(source, line);
|
||||
Type = ActivityTypeNameHelper.GenerateTypeName(GetType());
|
||||
|
|
@ -30,17 +30,17 @@ public abstract class Activity : IActivity, ISignalHandler
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected Activity(string activityType, int version = 1, string? source = default, int? line = default) : this(source, line)
|
||||
protected Activity(string activityType, int version = 1, string? source = null, int? line = null) : this(source, line)
|
||||
{
|
||||
Type = activityType;
|
||||
Version = version;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Id { get; set; } = default!;
|
||||
public string Id { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string NodeId { get; set; } = default!;
|
||||
public string NodeId { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? Name { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Mediator.Contracts;
|
||||
using Elsa.Workflows.Notifications;
|
||||
using Elsa.Workflows.Signals;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Workflows.Behaviors;
|
||||
|
||||
/// <summary>
|
||||
/// Implements a behavior that invokes "child completed" callbacks on parent activities.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class ScheduledChildCallbackBehavior : Behavior
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
|
@ -21,18 +24,16 @@ public class ScheduledChildCallbackBehavior : Behavior
|
|||
var childActivityNode = childActivityExecutionContext.ActivityNode;
|
||||
var callbackEntry = activityExecutionContext.WorkflowExecutionContext.PopCompletionCallback(activityExecutionContext, childActivityNode);
|
||||
|
||||
if (callbackEntry == null)
|
||||
return;
|
||||
|
||||
// Before invoking the parent activity, make sure its properties are evaluated.
|
||||
if (!activityExecutionContext.GetHasEvaluatedProperties())
|
||||
await activityExecutionContext.EvaluateInputPropertiesAsync();
|
||||
|
||||
if (callbackEntry.CompletionCallback != null)
|
||||
if (callbackEntry?.CompletionCallback != null)
|
||||
{
|
||||
var completedContext = new ActivityCompletedContext(activityExecutionContext, childActivityExecutionContext, signal.Result);
|
||||
var tag = callbackEntry.Tag;
|
||||
completedContext.TargetContext.Tag = tag;
|
||||
|
||||
var mediator = activityExecutionContext.GetRequiredService<IMediator>();
|
||||
var invokingActivityCallbackNotification = new InvokingActivityCallback(activityExecutionContext, childActivityExecutionContext);
|
||||
await mediator.SendAsync(invokingActivityCallbackNotification, context.CancellationToken);
|
||||
|
||||
await callbackEntry.CompletionCallback(completedContext);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public partial class ActivityExecutionContext
|
|||
ClearBookmarks();
|
||||
ClearCompletionCallbacks();
|
||||
WorkflowExecutionContext.Bookmarks.RemoveWhere(x => x.ActivityNodeId == NodeId);
|
||||
AddExecutionLogEntry("Canceled", payload: JournalData);
|
||||
AddExecutionLogEntry("Canceled");
|
||||
await this.SendSignalAsync(new CancelSignal());
|
||||
await CancelChildActivitiesAsync();
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public partial class ActivityExecutionContext
|
|||
JournalData["Outcomes"] = outcomes.Names;
|
||||
|
||||
// Add an execution log entry.
|
||||
AddExecutionLogEntry("Completed", payload: JournalData);
|
||||
AddExecutionLogEntry("Completed");
|
||||
|
||||
// Send a signal.
|
||||
await this.SendSignalAsync(new ActivityCompleted(result));
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ public static partial class ActivityExecutionContextExtensions
|
|||
context.WorkflowExecutionContext.Bookmarks.RemoveWhere(x => x.ActivityNodeId == context.NodeId);
|
||||
|
||||
// Add an execution log entry.
|
||||
context.AddExecutionLogEntry("Canceled", payload: context.JournalData);
|
||||
context.AddExecutionLogEntry("Canceled");
|
||||
|
||||
await context.SendSignalAsync(new CancelSignal());
|
||||
await publisher.SendAsync(new ActivityCancelled(context));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Mediator.Contracts;
|
||||
using Elsa.Workflows.Notifications;
|
||||
|
||||
namespace Elsa.Workflows.Handlers;
|
||||
|
||||
public class EvaluateParentInputProperties : INotificationHandler<InvokingActivityCallback>
|
||||
{
|
||||
public async Task HandleAsync(InvokingActivityCallback notification, CancellationToken cancellationToken)
|
||||
{
|
||||
// Before invoking the parent activity, make sure its properties are evaluated.
|
||||
if (!notification.Parent.GetHasEvaluatedProperties())
|
||||
await notification.Parent.EvaluateInputPropertiesAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ public class ExecutionLogMiddleware(ActivityMiddlewareDelegate next) : IActivity
|
|||
if (context.Status == ActivityStatus.Running)
|
||||
{
|
||||
if (IsActivityBookmarked(context))
|
||||
context.AddExecutionLogEntry("Suspended", payload: context.JournalData);
|
||||
context.AddExecutionLogEntry("Suspended");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
using Elsa.Mediator.Contracts;
|
||||
|
||||
namespace Elsa.Workflows.Notifications;
|
||||
|
||||
public record InvokingActivityCallback(ActivityExecutionContext Parent, ActivityExecutionContext Child) : INotification;
|
||||
|
|
@ -91,6 +91,7 @@ public class WorkflowExecutionLogRecord : Entity, ILogRecord
|
|||
/// <summary>
|
||||
/// The state of the activity at the time of the log entry.
|
||||
/// </summary>
|
||||
[Obsolete("Look at the ActivityExecutionRecord.ActivityState property instead.")]
|
||||
public IDictionary<string, object>? ActivityState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -344,6 +344,7 @@ public class WorkflowRuntimeFeature(IModule module) : FeatureBase(module)
|
|||
.AddNotificationHandler<DeleteWorkflowExecutionLogRecords>()
|
||||
.AddNotificationHandler<RefreshActivityRegistry>()
|
||||
.AddNotificationHandler<SignalBookmarkQueueWorker>()
|
||||
.AddNotificationHandler<EvaluateParentLogPersistenceModes>()
|
||||
|
||||
// Workflow activation strategies.
|
||||
.AddScoped<IWorkflowActivationStrategy, SingletonStrategy>()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@ public static class ActivityExecutionContextExtensions
|
|||
{
|
||||
private static object LogPersistenceMapKey { get; } = new();
|
||||
|
||||
public static bool HasLogPersistenceModeMap(this ActivityExecutionContext context)
|
||||
{
|
||||
return context.TransientProperties.ContainsKey(LogPersistenceMapKey);
|
||||
}
|
||||
|
||||
public static ActivityLogPersistenceModeMap GetLogPersistenceModeMap(this ActivityExecutionContext context)
|
||||
{
|
||||
return context.TransientProperties.GetValueOrDefault(LogPersistenceMapKey, () => new ActivityLogPersistenceModeMap())!;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
using Elsa.Mediator.Contracts;
|
||||
using Elsa.Workflows.Notifications;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.Handlers;
|
||||
|
||||
public class EvaluateParentLogPersistenceModes(IActivityPropertyLogPersistenceEvaluator persistenceEvaluator) : INotificationHandler<InvokingActivityCallback>
|
||||
{
|
||||
public async Task HandleAsync(InvokingActivityCallback notification, CancellationToken cancellationToken)
|
||||
{
|
||||
// Before invoking the parent activity, make sure its persistence log properties are evaluated.
|
||||
if (!notification.Parent.HasLogPersistenceModeMap())
|
||||
{
|
||||
var persistenceLogMap = await persistenceEvaluator.EvaluateLogPersistenceModesAsync(notification.Parent);
|
||||
notification.Parent.SetLogPersistenceModeMap(persistenceLogMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,14 +9,13 @@ public class DefaultActivityExecutionMapper : IActivityExecutionMapper
|
|||
{
|
||||
public ActivityExecutionRecord Map(ActivityExecutionContext source)
|
||||
{
|
||||
var payload = GetPayload(source);
|
||||
var outputs = source.GetOutputs();
|
||||
var inputs = source.GetInputs();
|
||||
var persistenceMap = source.GetLogPersistenceModeMap();
|
||||
var persistableInputs = GetPersistableInputOutput(inputs, persistenceMap.Inputs);
|
||||
var persistableOutputs = GetPersistableInputOutput(outputs, persistenceMap.Outputs);
|
||||
var persistableProperties = GetPersistableDictionary(source.Properties!, persistenceMap.InternalState);
|
||||
var persistablePayload = GetPersistableDictionary(payload!, persistenceMap.InternalState);
|
||||
var persistableJournalData = GetPersistableDictionary(source.JournalData!, persistenceMap.InternalState);
|
||||
|
||||
return new()
|
||||
{
|
||||
|
|
@ -29,7 +28,7 @@ public class DefaultActivityExecutionMapper : IActivityExecutionMapper
|
|||
ActivityState = persistableInputs,
|
||||
Outputs = persistableOutputs,
|
||||
Properties = persistableProperties,
|
||||
Payload = persistablePayload!,
|
||||
Payload = persistableJournalData!,
|
||||
Exception = ExceptionState.FromException(source.Exception),
|
||||
ActivityTypeVersion = source.Activity.Version,
|
||||
StartedAt = source.StartedAt,
|
||||
|
|
@ -62,15 +61,4 @@ public class DefaultActivityExecutionMapper : IActivityExecutionMapper
|
|||
{
|
||||
return mode == LogPersistenceMode.Include ? dictionary : null;
|
||||
}
|
||||
|
||||
private static IDictionary<string, object> GetPayload(ActivityExecutionContext source)
|
||||
{
|
||||
var outcomes = source.JournalData.TryGetValue("Outcomes", out var resultValue) ? resultValue as string[] : null;
|
||||
var payload = new Dictionary<string, object>();
|
||||
|
||||
if (outcomes != null)
|
||||
payload.Add("Outcomes", outcomes);
|
||||
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,6 @@ public class WorkflowExecutionLogRecordExtractor(IIdentityGenerator identityGene
|
|||
WorkflowInstanceId = context.Id,
|
||||
WorkflowVersion = context.Workflow.Version,
|
||||
Source = x.Source,
|
||||
ActivityState = x.ActivityState,
|
||||
Payload = x.Payload,
|
||||
Timestamp = x.Timestamp,
|
||||
Sequence = x.Sequence
|
||||
|
|
|
|||
Loading…
Reference in a new issue