Merge pull request #6841 from elsa-workflows/feat/task-activity-attribute
Improving behaviour of Run Asynchronously property for the TaskActivity Attribute
This commit is contained in:
commit
6dfa5251df
|
|
@ -68,7 +68,7 @@ public abstract class Activity : IActivity, ISignalHandler
|
|||
/// will execute synchronously, while activities of the <see cref="ActivityKind.Job"/> kind will execute asynchronously.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool RunAsynchronously
|
||||
public bool? RunAsynchronously
|
||||
{
|
||||
get => this.GetRunAsynchronously();
|
||||
set => this.SetRunAsynchronously(value);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Expressions.Contracts;
|
||||
using Elsa.Extensions;
|
||||
|
|
@ -28,6 +28,7 @@ public class FlowchartJsonConverter(IIdentityGenerator identityGenerator, IWellK
|
|||
var name = doc.RootElement.TryGetProperty("name", out var nameElement) ? nameElement.GetString() : null;
|
||||
var type = doc.RootElement.TryGetProperty("type", out var typeElement) ? typeElement.GetString() : null;
|
||||
var version = doc.RootElement.TryGetProperty("version", out var versionElement) ? versionElement.GetInt32() : 1;
|
||||
var runAsynchronously = doc.RootElement.TryGetProperty("runAsynchronously", out var runAsyncElement) && runAsyncElement.GetBoolean();
|
||||
|
||||
var connectionsElement = doc.RootElement.TryGetProperty("connections", out var connectionsEl) ? connectionsEl : default;
|
||||
var activitiesElement = doc.RootElement.TryGetProperty("activities", out var activitiesEl) ? activitiesEl : default;
|
||||
|
|
@ -60,6 +61,7 @@ public class FlowchartJsonConverter(IIdentityGenerator identityGenerator, IWellK
|
|||
NodeId = nodeId!,
|
||||
Name = name,
|
||||
Type = type!,
|
||||
RunAsynchronously = runAsynchronously,
|
||||
Version = version,
|
||||
CustomProperties = customProperties,
|
||||
Metadata = metadata,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
namespace Elsa.Workflows.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class TaskActivityAttribute : ActivityAttribute
|
||||
{
|
||||
public TaskActivityAttribute()
|
||||
{
|
||||
Kind = ActivityKind.Task;
|
||||
}
|
||||
|
||||
public TaskActivityAttribute(string @namespace, string? category, string? description = null, bool runAsynchronously = false)
|
||||
: base(@namespace, category, description)
|
||||
{
|
||||
Kind = ActivityKind.Task;
|
||||
RunAsynchronously = runAsynchronously;
|
||||
}
|
||||
|
||||
public bool RunAsynchronously { get; set; }
|
||||
}
|
||||
|
|
@ -29,14 +29,17 @@ public static class ActivityPropertyExtensions
|
|||
/// By default, activities with an <see cref="ActivityKind"/> of <see cref="Action"/>, <see cref="Task"/> or <see cref="Trigger"/>
|
||||
/// will execute synchronously, while activities of the <see cref="ActivityKind.Job"/> kind will execute asynchronously.
|
||||
/// </summary>
|
||||
public static bool GetRunAsynchronously(this IActivity activity) => activity.CustomProperties.GetValueOrDefault(RunAsynchronouslyPropertyName, () => false);
|
||||
public static bool? GetRunAsynchronously(this IActivity activity)
|
||||
{
|
||||
return activity.CustomProperties.GetValueOrDefault<bool?>(RunAsynchronouslyPropertyName, defaultValueFactory: () => null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a flag indicating if this activity should execute synchronously or asynchronously.
|
||||
/// By default, activities with an <see cref="ActivityKind"/> of <see cref="Action"/>, <see cref="Task"/> or <see cref="Trigger"/>
|
||||
/// will execute synchronously, while activities of the <see cref="ActivityKind.Job"/> kind will execute asynchronously.
|
||||
/// </summary>
|
||||
public static void SetRunAsynchronously(this IActivity activity, bool value) => activity.CustomProperties[RunAsynchronouslyPropertyName[0]] = value;
|
||||
public static void SetRunAsynchronously(this IActivity activity, bool? value) => activity.CustomProperties[RunAsynchronouslyPropertyName[0]] = value ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source file and line number where this activity was instantiated, if any.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Text.Json.Nodes;
|
||||
using Elsa.Expressions.Helpers;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Attributes;
|
||||
using Elsa.Workflows.Memory;
|
||||
using Elsa.Workflows.Models;
|
||||
using Humanizer;
|
||||
|
|
@ -34,7 +35,18 @@ public class ActivityFactory : IActivityFactory
|
|||
|
||||
// 7) Pull out your boolean flags from the cleaned element
|
||||
var canStartWorkflow = GetBoolean(cleanedElement, "canStartWorkflow");
|
||||
var runAsynchronously = GetBoolean(cleanedElement, "runAsynchronously");
|
||||
var runAsynchronously = GetNullableBoolean(cleanedElement, "runAsynchronously");
|
||||
if (runAsynchronously is null)
|
||||
{
|
||||
if (context.ActivityDescriptor.Attributes.OfType<TaskActivityAttribute>().FirstOrDefault() is { } taskActivityAttribute)
|
||||
{
|
||||
runAsynchronously = taskActivityAttribute.RunAsynchronously;
|
||||
}
|
||||
else
|
||||
{
|
||||
runAsynchronously = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 8) If composite, setup
|
||||
if (activity is IComposite composite)
|
||||
|
|
@ -180,6 +192,11 @@ public class ActivityFactory : IActivityFactory
|
|||
}
|
||||
|
||||
private static bool GetBoolean(JsonElement element, string propertyName)
|
||||
{
|
||||
return GetNullableBoolean(element, propertyName) ?? false;
|
||||
}
|
||||
|
||||
private static bool? GetNullableBoolean(JsonElement element, string propertyName)
|
||||
{
|
||||
var propertyNames = new[] { propertyName.Camelize(), propertyName.Pascalize() };
|
||||
|
||||
|
|
@ -188,13 +205,14 @@ public class ActivityFactory : IActivityFactory
|
|||
if (element.TryGetProperty("customProperties", out var customPropertyElement))
|
||||
{
|
||||
if (customPropertyElement.TryGetProperty(name, out var canStartWorkflowElement))
|
||||
return canStartWorkflowElement.GetBoolean();
|
||||
return (bool?)canStartWorkflowElement.GetValue();
|
||||
}
|
||||
|
||||
if (element.TryGetProperty(propertyName.Camelize(), out var property) && property.GetBoolean())
|
||||
return true;
|
||||
if (element.TryGetProperty(propertyName.Camelize(), out var property)
|
||||
&& (bool?)property.GetValue() is { } propValue)
|
||||
return propValue;
|
||||
}
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Mediator.Contracts;
|
||||
using Elsa.Workflows.Attributes;
|
||||
using Elsa.Workflows.CommitStates;
|
||||
using Elsa.Workflows.Middleware.Activities;
|
||||
using Elsa.Workflows.Models;
|
||||
|
|
@ -103,7 +104,28 @@ public class BackgroundActivityInvokerMiddleware(
|
|||
|
||||
return !GetIsBackgroundExecution(context)
|
||||
&& context.WorkflowExecutionContext.ExecuteDelegate == null
|
||||
&& (kind is ActivityKind.Job || (kind == ActivityKind.Task && activity.GetRunAsynchronously()));
|
||||
&& (kind is ActivityKind.Job || GetTaskRunAsynchronously(context));
|
||||
}
|
||||
|
||||
private static bool GetTaskRunAsynchronously(ActivityExecutionContext context)
|
||||
{
|
||||
var activity = context.Activity;
|
||||
var activityDescriptor = context.ActivityDescriptor;
|
||||
var kind = activityDescriptor.Kind;
|
||||
|
||||
if (kind is not ActivityKind.Task)
|
||||
return false;
|
||||
|
||||
var runAsynchronously = activity.GetRunAsynchronously();
|
||||
|
||||
if (runAsynchronously is null)
|
||||
{
|
||||
var taskActivityAttribute = activityDescriptor.Attributes.OfType<TaskActivityAttribute>().FirstOrDefault();
|
||||
|
||||
return taskActivityAttribute is { RunAsynchronously: true };
|
||||
}
|
||||
|
||||
return (bool)runAsynchronously;
|
||||
}
|
||||
|
||||
private static bool GetIsBackgroundExecution(ActivityExecutionContext context) => context.TransientProperties.ContainsKey(BackgroundActivityExecutionContextExtensions.IsBackgroundExecution);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public class Tests
|
|||
[Theory(DisplayName = "Activity outputs captured in activity execution record")]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task ActivityOutputCaptureTest(bool runAsynchronously)
|
||||
public async Task ActivityOutputCaptureTest(bool? runAsynchronously)
|
||||
{
|
||||
// Arrange
|
||||
var workflow = new TestWorkflow(workflowBuilder =>
|
||||
|
|
@ -76,7 +76,7 @@ public class Tests
|
|||
[Theory(DisplayName = "Activity outputs captured in activity execution record")]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task ActivityOutputCaptureParallelTest(bool runAsynchronously)
|
||||
public async Task ActivityOutputCaptureParallelTest(bool? runAsynchronously)
|
||||
{
|
||||
// Arrange
|
||||
var workflow = new TestWorkflow(workflowBuilder =>
|
||||
|
|
|
|||
|
|
@ -32,17 +32,23 @@ public class Tests
|
|||
{
|
||||
Id = "start",
|
||||
Name = "Start",
|
||||
RunAsynchronously = false // Manually set to false because the manual construction defaults to null,
|
||||
// But deserialization uses the factory creation method that overwrites null values.
|
||||
};
|
||||
var writeLine = new WriteLine(new Input<string>(new Expression("JavaScript", "getVariable('TextVar')")))
|
||||
{
|
||||
Id = "writeLine",
|
||||
Name = "WriteLine",
|
||||
Version = 3,
|
||||
RunAsynchronously = false // Manually set to false because the manual construction defaults to null,
|
||||
// But deserialization uses the factory creation method that overwrites null values.
|
||||
};
|
||||
var end = new End
|
||||
{
|
||||
Id = "end",
|
||||
Name = "end",
|
||||
RunAsynchronously = false // Manually set to false because the manual construction defaults to null,
|
||||
// But deserialization uses the factory creation method that overwrites null values.
|
||||
};
|
||||
var container = new Flowchart
|
||||
{
|
||||
|
|
@ -74,6 +80,7 @@ public class Tests
|
|||
new(writeLine, end),
|
||||
},
|
||||
};
|
||||
container.RunAsynchronously = false;
|
||||
|
||||
// Act
|
||||
|
||||
|
|
@ -107,6 +114,8 @@ public class Tests
|
|||
Id = "writeLine",
|
||||
Name = "WriteLine",
|
||||
CanStartWorkflow = true,
|
||||
RunAsynchronously = false // Manually set to false because the manual construction defaults to null,
|
||||
// But deserialization uses the factory creation method that overwrites null values.
|
||||
},
|
||||
},
|
||||
CustomProperties = new Dictionary<string, object>
|
||||
|
|
@ -120,6 +129,7 @@ public class Tests
|
|||
{ "string", "str"},
|
||||
}
|
||||
};
|
||||
container.RunAsynchronously = false;
|
||||
|
||||
// Act
|
||||
|
||||
|
|
@ -153,6 +163,9 @@ public class Tests
|
|||
Id = "writeLine",
|
||||
Name = "WriteLine",
|
||||
CanStartWorkflow = true,
|
||||
RunAsynchronously = false // Manually set to false because the manual construction defaults to null,
|
||||
// But deserialization uses the factory creation method that overwrites null values.
|
||||
|
||||
},
|
||||
},
|
||||
CustomProperties = new Dictionary<string, object>
|
||||
|
|
@ -166,6 +179,7 @@ public class Tests
|
|||
{ "string", "str"},
|
||||
}
|
||||
};
|
||||
container.RunAsynchronously = false;
|
||||
|
||||
// Act
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue