Introduce ActivityBase
This commit is contained in:
parent
ae1748cee3
commit
20002e2454
|
|
@ -96,7 +96,8 @@ export class ToolboxActivities {
|
|||
}
|
||||
|
||||
handleActivityDescriptorsChanged(value: Array<ActivityDescriptor>) {
|
||||
const categorizedActivitiesLookup = groupBy(value, x => x.category);
|
||||
const browsableDescriptors = value.filter(x => x.isBrowsable);
|
||||
const categorizedActivitiesLookup = groupBy(browsableDescriptors, x => x.category);
|
||||
const categories = Object.keys(categorizedActivitiesLookup);
|
||||
const renderedActivities: Map<string, string> = new Map<string, string>();
|
||||
|
||||
|
|
@ -114,7 +115,7 @@ export class ToolboxActivities {
|
|||
// Render activities.
|
||||
const activityDriverRegistry = Container.get(ActivityDriverRegistry);
|
||||
|
||||
for (const activityDescriptor of value) {
|
||||
for (const activityDescriptor of browsableDescriptors) {
|
||||
const activityType = activityDescriptor.activityType;
|
||||
const driver = activityDriverRegistry.createDriver(activityType);
|
||||
const html = driver.display({displayType: 'picker', activityDescriptor: activityDescriptor});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import {FunctionalComponent, h} from '@stencil/core';
|
||||
import {ActivityIconSettings, getActivityIconCssClass} from "./models";
|
||||
|
||||
export const FlowDecisionIcon: FunctionalComponent<ActivityIconSettings> = (settings) => (
|
||||
<svg class={getActivityIconCssClass(settings)} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
);
|
||||
|
|
@ -3,6 +3,7 @@ export * from "./http-endpoint";
|
|||
export * from "./write-line";
|
||||
export * from "./read-line";
|
||||
export * from "./if";
|
||||
export * from "./flow-decision";
|
||||
export * from "./flowchart";
|
||||
export * from "./foreach";
|
||||
export * from "./delay";
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ export interface ActivityDescriptor {
|
|||
kind: ActivityKind;
|
||||
ports: Array<Port>;
|
||||
isContainer: boolean;
|
||||
isBrowsable: boolean;
|
||||
}
|
||||
|
||||
export interface PropertyDescriptor {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {Service} from "typedi";
|
|||
import {
|
||||
DelayIcon,
|
||||
EventIcon,
|
||||
FlowchartIcon,
|
||||
FlowchartIcon, FlowDecisionIcon,
|
||||
ForEachIcon,
|
||||
HttpEndpointIcon,
|
||||
IfIcon,
|
||||
|
|
@ -32,6 +32,7 @@ export class ActivityIconRegistry {
|
|||
this.add('Elsa.ForEach', settings => <ForEachIcon size={settings?.size}/>);
|
||||
this.add('Elsa.Delay', settings => <DelayIcon size={settings?.size}/>);
|
||||
this.add('Elsa.Timer', settings => <TimerIcon size={settings?.size}/>);
|
||||
this.add('Elsa.FlowDecision', settings => <FlowDecisionIcon size={settings?.size}/>);
|
||||
this.add('Elsa.Event', settings => <EventIcon size={settings?.size}/>);
|
||||
this.add('Elsa.RunJavaScript', settings => <RunJavaScriptIcon size={settings?.size}/>);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,19 +2,14 @@ using Elsa.Expressions.Models;
|
|||
using Elsa.Workflows.Core.Activities.Flowchart.Attributes;
|
||||
using Elsa.Workflows.Core.Activities.Flowchart.Models;
|
||||
using Elsa.Workflows.Core.Attributes;
|
||||
using Elsa.Workflows.Core.Behaviors;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
|
||||
namespace Elsa.Workflows.Core.Activities.Flowchart.Activities;
|
||||
|
||||
[FlowNode("True", "False")]
|
||||
public class FlowDecision : Activity
|
||||
[Activity("Elsa", "Flow", "Continues on either the True or False branch, depending on the result of a specified condition")]
|
||||
public class FlowDecision : ActivityBase
|
||||
{
|
||||
public FlowDecision()
|
||||
{
|
||||
Behaviors.Remove<AutoCompleteBehavior>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The condition to evaluate.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Elsa.Workflows.Core.Signals;
|
|||
|
||||
namespace Elsa.Workflows.Core.Activities.Flowchart.Activities;
|
||||
|
||||
[Activity("Elsa", "Workflows", "A flowchart is a collection of activities and connections between them.")]
|
||||
[Activity("Elsa", "Flow", "A flowchart is a collection of activities and connections between them.")]
|
||||
public class Flowchart : Container
|
||||
{
|
||||
public Flowchart()
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@ public class ConnectionJsonConverter : JsonConverter<Connection>
|
|||
{
|
||||
private readonly IDictionary<string, IActivity> _activities;
|
||||
|
||||
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Connection);
|
||||
|
||||
public ConnectionJsonConverter(IDictionary<string, IActivity> activities)
|
||||
{
|
||||
_activities = activities;
|
||||
}
|
||||
|
||||
|
||||
public override Connection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (!JsonDocument.TryParseValue(ref reader, out var doc))
|
||||
|
|
@ -24,8 +26,8 @@ public class ConnectionJsonConverter : JsonConverter<Connection>
|
|||
var sourcePort = doc.RootElement.GetProperty("sourcePort").GetString()!;
|
||||
var targetPort = doc.RootElement.GetProperty("targetPort").GetString()!;
|
||||
|
||||
var source = _activities[sourceId];
|
||||
var target = _activities[targetId];
|
||||
var source = _activities.TryGetValue(sourceId, out var s) ? s : default!;
|
||||
var target = _activities.TryGetValue(targetId, out var t) ? t : default!;
|
||||
|
||||
return new Connection(source, target, sourcePort, targetPort);
|
||||
}
|
||||
|
|
@ -33,7 +35,7 @@ public class ConnectionJsonConverter : JsonConverter<Connection>
|
|||
public override void Write(Utf8JsonWriter writer, Connection value, JsonSerializerOptions options)
|
||||
{
|
||||
var (activity, target, sourcePort, targetPort) = value;
|
||||
|
||||
|
||||
var model = new
|
||||
{
|
||||
Source = activity.Id,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ public class FlowchartJsonConverter : JsonConverter<Activities.Flowchart>
|
|||
var activityDictionary = activities.ToDictionary(x => x.Id);
|
||||
|
||||
connectionSerializerOptions.Converters.Add(new ConnectionJsonConverter(activityDictionary));
|
||||
var connections = connectionsElement.ValueKind != JsonValueKind.Undefined ? connectionsElement.Deserialize<ICollection<Connection>>(connectionSerializerOptions) ?? new List<Connection>() : new List<Connection>();
|
||||
var connections = connectionsElement.ValueKind != JsonValueKind.Undefined
|
||||
? connectionsElement.Deserialize<ICollection<Connection>>(connectionSerializerOptions)?.Where(x => x.Source != null! && x.Target != null!).ToList() ?? new List<Connection>()
|
||||
: new List<Connection>();
|
||||
|
||||
var flowChart = new Activities.Flowchart
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
using System.ComponentModel;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
|
||||
namespace Elsa.Workflows.Core.Activities;
|
||||
|
||||
[Browsable(false)]
|
||||
public class NotFoundActivity : Activity
|
||||
{
|
||||
public NotFoundActivity()
|
||||
{
|
||||
}
|
||||
|
||||
public NotFoundActivity(string missingTypeName)
|
||||
{
|
||||
MissingTypeName = missingTypeName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type name of the missing activity.
|
||||
/// </summary>
|
||||
public string MissingTypeName { get; set; } = default!;
|
||||
}
|
||||
|
|
@ -1,21 +1,12 @@
|
|||
using System.Linq.Expressions;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Workflows.Core.Behaviors;
|
||||
using Elsa.Workflows.Core.Helpers;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
|
||||
namespace Elsa.Workflows.Core.Models;
|
||||
|
||||
public abstract class Activity : IActivity, ISignalHandler
|
||||
public abstract class Activity : ActivityBase
|
||||
{
|
||||
private readonly ICollection<SignalHandlerRegistration> _signalHandlers = new List<SignalHandlerRegistration>();
|
||||
|
||||
protected Activity()
|
||||
{
|
||||
TypeName = ActivityTypeNameHelper.GenerateTypeName(GetType());
|
||||
Behaviors.Add<ExecutionLoggingBehavior>(this);
|
||||
Behaviors.Add<ScheduledChildCallbackBehavior>(this);
|
||||
Behaviors.Add<AutoCompleteBehavior>(this);
|
||||
}
|
||||
|
||||
|
|
@ -23,82 +14,6 @@ public abstract class Activity : IActivity, ISignalHandler
|
|||
{
|
||||
TypeName = activityType;
|
||||
}
|
||||
|
||||
public string Id { get; set; } = default!;
|
||||
public string TypeName { get; set; }
|
||||
public bool CanStartWorkflow { get; set; }
|
||||
public IDictionary<string, object> ApplicationProperties { get; set; } = new Dictionary<string, object>();
|
||||
public IDictionary<string, object> Metadata { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
/// A collection of reusable behaviors to add to this activity.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ICollection<IBehavior> Behaviors { get; } = new List<IBehavior>();
|
||||
|
||||
protected virtual ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
Execute(context);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual ValueTask OnSignalReceivedAsync(object signal, SignalContext context)
|
||||
{
|
||||
OnSignalReceived(signal, context);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual void OnSignalReceived(object signal, SignalContext context)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void Execute(ActivityExecutionContext context)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify the sytem that this activity completed.
|
||||
/// </summary>
|
||||
protected async ValueTask CompleteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
await context.CompleteActivityAsync();
|
||||
}
|
||||
|
||||
protected void OnSignalReceived(Type signalType, Func<object, SignalContext, ValueTask> handler) => _signalHandlers.Add(new SignalHandlerRegistration(signalType, handler));
|
||||
protected void OnSignalReceived<T>(Func<T, SignalContext, ValueTask> handler) => OnSignalReceived(typeof(T), (signal, context) => handler((T)signal, context));
|
||||
|
||||
protected void OnSignalReceived<T>(Action<T, SignalContext> handler)
|
||||
{
|
||||
OnSignalReceived<T>((signal, context) =>
|
||||
{
|
||||
handler(signal, context);
|
||||
return ValueTask.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
async ValueTask IActivity.ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
await ExecuteAsync(context);
|
||||
|
||||
// Invoke behaviors.
|
||||
foreach (var behavior in Behaviors) await behavior.ExecuteAsync(context);
|
||||
}
|
||||
|
||||
async ValueTask ISignalHandler.HandleSignalAsync(object signal, SignalContext context)
|
||||
{
|
||||
// Give derived activity a chance to do something with the signal.
|
||||
await OnSignalReceivedAsync(signal, context);
|
||||
|
||||
// Invoke registered signal delegates for this particular type of signal.
|
||||
var signalType = signal.GetType();
|
||||
var handlers = _signalHandlers.Where(x => x.SignalType == signalType);
|
||||
|
||||
foreach (var registration in handlers)
|
||||
await registration.Handler(signal, context);
|
||||
|
||||
// Invoke behaviors.
|
||||
foreach (var behavior in Behaviors) await behavior.HandleSignalAsync(signal, context);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ActivityWithResult : Activity
|
||||
|
|
@ -153,6 +68,4 @@ public abstract class Activity<T> : ActivityWithResult
|
|||
get => base.Result as Output<T>;
|
||||
set => base.Result = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal record SignalHandlerRegistration(Type SignalType, Func<object, SignalContext, ValueTask> Handler);
|
||||
}
|
||||
99
src/modules/Elsa.Workflows.Core/Models/ActivityBase.cs
Normal file
99
src/modules/Elsa.Workflows.Core/Models/ActivityBase.cs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
using System.Text.Json.Serialization;
|
||||
using Elsa.Workflows.Core.Behaviors;
|
||||
using Elsa.Workflows.Core.Helpers;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
|
||||
namespace Elsa.Workflows.Core.Models;
|
||||
|
||||
public abstract class ActivityBase : IActivity, ISignalHandler
|
||||
{
|
||||
private readonly ICollection<SignalHandlerRegistration> _signalHandlers = new List<SignalHandlerRegistration>();
|
||||
|
||||
protected ActivityBase()
|
||||
{
|
||||
TypeName = ActivityTypeNameHelper.GenerateTypeName(GetType());
|
||||
Behaviors.Add<ExecutionLoggingBehavior>(this);
|
||||
Behaviors.Add<ScheduledChildCallbackBehavior>(this);
|
||||
}
|
||||
|
||||
protected ActivityBase(string activityType) : this()
|
||||
{
|
||||
TypeName = activityType;
|
||||
}
|
||||
|
||||
public string Id { get; set; } = default!;
|
||||
public string TypeName { get; set; }
|
||||
public bool CanStartWorkflow { get; set; }
|
||||
public IDictionary<string, object> ApplicationProperties { get; set; } = new Dictionary<string, object>();
|
||||
public IDictionary<string, object> Metadata { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
/// A collection of reusable behaviors to add to this activity.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ICollection<IBehavior> Behaviors { get; } = new List<IBehavior>();
|
||||
|
||||
protected virtual ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
Execute(context);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual ValueTask OnSignalReceivedAsync(object signal, SignalContext context)
|
||||
{
|
||||
OnSignalReceived(signal, context);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual void OnSignalReceived(object signal, SignalContext context)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void Execute(ActivityExecutionContext context)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify the sytem that this activity completed.
|
||||
/// </summary>
|
||||
protected async ValueTask CompleteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
await context.CompleteActivityAsync();
|
||||
}
|
||||
|
||||
protected void OnSignalReceived(Type signalType, Func<object, SignalContext, ValueTask> handler) => _signalHandlers.Add(new SignalHandlerRegistration(signalType, handler));
|
||||
protected void OnSignalReceived<T>(Func<T, SignalContext, ValueTask> handler) => OnSignalReceived(typeof(T), (signal, context) => handler((T)signal, context));
|
||||
|
||||
protected void OnSignalReceived<T>(Action<T, SignalContext> handler)
|
||||
{
|
||||
OnSignalReceived<T>((signal, context) =>
|
||||
{
|
||||
handler(signal, context);
|
||||
return ValueTask.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
async ValueTask IActivity.ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
await ExecuteAsync(context);
|
||||
|
||||
// Invoke behaviors.
|
||||
foreach (var behavior in Behaviors) await behavior.ExecuteAsync(context);
|
||||
}
|
||||
|
||||
async ValueTask ISignalHandler.HandleSignalAsync(object signal, SignalContext context)
|
||||
{
|
||||
// Give derived activity a chance to do something with the signal.
|
||||
await OnSignalReceivedAsync(signal, context);
|
||||
|
||||
// Invoke registered signal delegates for this particular type of signal.
|
||||
var signalType = signal.GetType();
|
||||
var handlers = _signalHandlers.Where(x => x.SignalType == signalType);
|
||||
|
||||
foreach (var registration in handlers)
|
||||
await registration.Handler(signal, context);
|
||||
|
||||
// Invoke behaviors.
|
||||
foreach (var behavior in Behaviors) await behavior.HandleSignalAsync(signal, context);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,11 @@ public class ActivityDescriptor
|
|||
/// A value indicating whether this activity is a container of child activities.
|
||||
/// </summary>
|
||||
public bool IsContainer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this activity type is selectable from activity pickers.
|
||||
/// </summary>
|
||||
public bool IsBrowsable { get; set; }
|
||||
}
|
||||
|
||||
public record ActivityConstructorContext(JsonElement Element, JsonSerializerOptions SerializerOptions);
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
using Elsa.Workflows.Core.Services;
|
||||
|
||||
namespace Elsa.Workflows.Core.Models;
|
||||
|
||||
internal record SignalHandlerRegistration(Type SignalType, Func<object, SignalContext, ValueTask> Handler);
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using Elsa.Features.Services;
|
||||
using Elsa.Workflows.Core.Activities;
|
||||
using Elsa.Workflows.Management.Features;
|
||||
|
||||
namespace Elsa.Workflows.Management.Extensions;
|
||||
|
|
@ -7,7 +8,11 @@ public static class DependencyInjectionExtensions
|
|||
{
|
||||
public static IModule UseManagement(this IModule module, Action<WorkflowManagementFeature>? configure = default)
|
||||
{
|
||||
module.Configure(configure);
|
||||
module.Configure<WorkflowManagementFeature>(management =>
|
||||
{
|
||||
management.AddActivity<NotFoundActivity>();
|
||||
configure?.Invoke(management);
|
||||
});
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +65,7 @@ public class ActivityDescriber : IActivityDescriber
|
|||
var inputProperties = properties.Where(x => typeof(Input).IsAssignableFrom(x.PropertyType) || x.GetCustomAttribute<InputAttribute>() != null).ToList();
|
||||
var outputProperties = properties.Where(x => typeof(Output).IsAssignableFrom(x.PropertyType)).DistinctBy(x => x.Name).ToList();
|
||||
var isTrigger = activityType.IsAssignableTo(typeof(ITrigger));
|
||||
var browsableAttr = activityType.GetCustomAttribute<BrowsableAttribute>();
|
||||
|
||||
var descriptor = new ActivityDescriptor
|
||||
{
|
||||
|
|
@ -77,6 +78,7 @@ public class ActivityDescriber : IActivityDescriber
|
|||
Inputs = DescribeInputProperties(inputProperties).ToList(),
|
||||
Outputs = DescribeOutputProperties(outputProperties).ToList(),
|
||||
IsContainer = typeof(IContainer).IsAssignableFrom(activityType),
|
||||
IsBrowsable = browsableAttr == null || browsableAttr.Browsable,
|
||||
Constructor = context =>
|
||||
{
|
||||
var activity = _activityFactory.Create(activityType, context);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
using Elsa.Workflows.Management.Services;
|
||||
|
||||
namespace Elsa.Workflows.Management.Implementations;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Workflows.Core.Activities;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
|
|
@ -33,7 +34,14 @@ public class ActivityJsonConverter : JsonConverter<IActivity>
|
|||
var activityDescriptor = _activityRegistry.Find(activityTypeName);
|
||||
|
||||
if (activityDescriptor == null)
|
||||
throw new Exception($"Activity of type {activityTypeName} not found in registry");
|
||||
{
|
||||
var activityId = doc.RootElement.TryGetProperty("Id", out var activityIdElement) ? activityIdElement.GetString() : default;
|
||||
|
||||
return new NotFoundActivity(activityTypeName)
|
||||
{
|
||||
Id = activityId ?? Guid.NewGuid().ToString("N"),
|
||||
};
|
||||
}
|
||||
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
newOptions.Converters.Add(new InputJsonConverterFactory(_serviceProvider));
|
||||
|
|
|
|||
|
|
@ -42,6 +42,10 @@ public class DefaultWorkflowInvoker : IWorkflowInvoker
|
|||
public async Task<InvokeWorkflowResult> InvokeAsync(InvokeWorkflowInstanceRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var workflowInstance = await _workflowInstanceStore.FindByIdAsync(request.InstanceId, cancellationToken);
|
||||
|
||||
if (workflowInstance == null)
|
||||
throw new Exception($"No workflow instance found with ID {request.InstanceId}");
|
||||
|
||||
return await InvokeAsync(workflowInstance!, request.Bookmark, request.Input, cancellationToken);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue