Improve variable vs non-variable persistence (#3568)
* Incremental fixes * Fix base assignment of async delegate block reference * Reuse existing memory blocks * Remove PlaybackStarted child activity The PlaybackStarted property is unnecessary, since the PlayAudio activity already completes right after playback has started. * Add GetCallStatus API method * Prevent variables on composite activities to be overwritten during deserialization * Remove unnecessary variable declarations Activity variables will be declared automatically during activity invocation. * Add XML comments * Remove unused references arg * Streamline variable identity assignment * Fix composite early exit completion * Move identity assignment to workflow definition service
This commit is contained in:
parent
24692586a5
commit
8ac225520b
|
|
@ -55,11 +55,11 @@ export class VariableEditorDialogContent {
|
|||
</FormEntry>
|
||||
|
||||
<FormEntry fieldId="variableStorageDriverId" label="Storage" hint="The storage to use when persisting the variable.">
|
||||
<select id="variableStorageDriverId" name="variableStorageDriverId">
|
||||
<select id="variableStorageDriverTypeName" name="variableStorageDriverTypeName">
|
||||
{storageDrivers.map(driver => {
|
||||
const value = driver.id;
|
||||
const value = driver.typeName;
|
||||
const text = driver.displayName;
|
||||
const selected = value == variable.storageDriverId;
|
||||
const selected = value == variable.storageDriverTypeName;
|
||||
return <option value={value} selected={selected}>{text}</option>;
|
||||
})}
|
||||
</select>
|
||||
|
|
@ -84,13 +84,13 @@ export class VariableEditorDialogContent {
|
|||
const name = formData.get('variableName') as string;
|
||||
const value = formData.get('variableValue') as string;
|
||||
const type = formData.get('variableTypeName') as string;
|
||||
const driverId = formData.get('variableStorageDriverId') as string;
|
||||
const driverTypeName = formData.get('variableStorageDriverTypeName') as string;
|
||||
const variable = this.variable;
|
||||
|
||||
variable.name = name;
|
||||
variable.typeName = type;
|
||||
variable.value = value;
|
||||
variable.storageDriverId = isNullOrWhitespace(driverId) ? null : driverId;
|
||||
variable.storageDriverTypeName = isNullOrWhitespace(driverTypeName) ? null : driverTypeName;
|
||||
|
||||
return variable;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export class VariablesEditor {
|
|||
</thead>
|
||||
<tbody>
|
||||
{variables.map(variable => {
|
||||
const storage = storageDrivers.find(x => x.id == variable.storageDriverId);
|
||||
const storage = storageDrivers.find(x => x.typeName == variable.storageDriverTypeName);
|
||||
const storageName = storage?.displayName ?? '-';
|
||||
const descriptor = descriptorsStore.variableDescriptors.find(x => x.typeName == variable.typeName);
|
||||
const typeDisplayName = descriptor?.displayName ?? variable.typeName;
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export interface IntellisenseContext {
|
|||
}
|
||||
|
||||
export interface StorageDriverDescriptor {
|
||||
id: string;
|
||||
typeName: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export interface Variable {
|
|||
name: string;
|
||||
typeName: string;
|
||||
value?: any;
|
||||
storageDriverId?: string;
|
||||
storageDriverTypeName?: string;
|
||||
}
|
||||
|
||||
export interface ActivityInput {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using Elsa.Dsl.Models;
|
||||
using Elsa.Dsl.Services;
|
||||
using Elsa.Expressions.Services;
|
||||
using Elsa.Workflows.Core.Activities;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
|
||||
namespace Elsa.Dsl.Interpreters;
|
||||
|
|
@ -17,8 +18,9 @@ public partial class WorkflowDefinitionBuilderInterpreter : ElsaParserBaseVisito
|
|||
private readonly ParseTreeProperty<IList<object?>> _argValues = new();
|
||||
private readonly ParseTreeProperty<Type> _expressionType = new();
|
||||
private readonly IDictionary<string, DefinedVariable> _definedVariables = new Dictionary<string, DefinedVariable>();
|
||||
private readonly Stack<IContainer> _containerStack = new();
|
||||
private readonly Stack<Container> _containerStack = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public WorkflowDefinitionBuilderInterpreter(
|
||||
ITypeSystem typeSystem,
|
||||
IFunctionActivityRegistry functionActivityRegistry,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dahomey.Json" Version="1.12.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
12
src/modules/Elsa.Expressions/Extensions/TypeExtensions.cs
Normal file
12
src/modules/Elsa.Expressions/Extensions/TypeExtensions.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
namespace Elsa.Expressions.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Adds extension methods to <see cref="Type"/>.
|
||||
/// </summary>
|
||||
public static class TypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the default value for the specified type.
|
||||
/// </summary>
|
||||
public static object? GetDefaultValue(this Type type) => type.IsClass ? null : Activator.CreateInstance(type);
|
||||
}
|
||||
|
|
@ -1,17 +1,32 @@
|
|||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Dahomey.Json;
|
||||
using Dahomey.Json.Serialization.Conventions;
|
||||
using Dahomey.Json.Util;
|
||||
using Elsa.Expressions.Exceptions;
|
||||
using DahomeyJsonNode = System.Text.Json.JsonNode;
|
||||
using Elsa.Expressions.Extensions;
|
||||
using Elsa.Expressions.Models;
|
||||
|
||||
namespace Elsa.Expressions.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// A helper that attempts many strategies to try and convert the source value into the destination type.
|
||||
/// </summary>
|
||||
public static class ObjectConverter
|
||||
{
|
||||
public static Result TryConvertTo<T>(this object? value, JsonSerializerOptions? serializerOptions = null) => value.TryConvertTo(typeof(T), serializerOptions);
|
||||
|
||||
public static Result TryConvertTo(this object? value, Type targetType, JsonSerializerOptions? serializerOptions = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var convertedValue = value.ConvertTo(targetType, serializerOptions);
|
||||
return new Result(true, convertedValue, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new Result(false, null, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static T? ConvertTo<T>(this object? value, JsonSerializerOptions? serializerOptions = null) => value != null ? (T?)value.ConvertTo(typeof(T), serializerOptions) : default;
|
||||
|
||||
public static object? ConvertTo(this object? value, Type targetType, JsonSerializerOptions? serializerOptions = null)
|
||||
|
|
@ -32,9 +47,6 @@ public static class ObjectConverter
|
|||
|
||||
var underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;
|
||||
|
||||
if (value is DahomeyJsonNode { ValueKind: JsonValueKind.Object } dahomyJsonObject)
|
||||
return ToObject(dahomyJsonObject, targetType, options);
|
||||
|
||||
if (value is JsonElement jsonNumber && jsonNumber.ValueKind == JsonValueKind.Number && underlyingTargetType == typeof(string))
|
||||
return jsonNumber.ToString().ConvertTo(underlyingTargetType);
|
||||
|
||||
|
|
@ -42,7 +54,7 @@ public static class ObjectConverter
|
|||
{
|
||||
if (jsonObject.ValueKind == JsonValueKind.String && underlyingTargetType != typeof(string))
|
||||
return jsonObject.GetString().ConvertTo(underlyingTargetType);
|
||||
|
||||
|
||||
return jsonObject.Deserialize(targetType, options);
|
||||
}
|
||||
|
||||
|
|
@ -108,10 +120,4 @@ public static class ObjectConverter
|
|||
throw new TypeConversionException($"Failed to convert an object of type {sourceType} to {underlyingTargetType}", value, underlyingTargetType, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static object? ToObject(this DahomeyJsonNode node, Type type, JsonSerializerOptions? options = null)
|
||||
{
|
||||
using var arrayBufferWriter = new ArrayBufferWriter<byte>();
|
||||
return JsonSerializer.Deserialize(node.ToString(), type, options);
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ public class DelegateBlockReference<T> : DelegateBlockReference
|
|||
{
|
||||
}
|
||||
|
||||
public DelegateBlockReference(Func<ExpressionExecutionContext, ValueTask<T?>> @delegate) : base(x => @delegate(x))
|
||||
public DelegateBlockReference(Func<ExpressionExecutionContext, ValueTask<T?>> @delegate) : base(async x => await @delegate(x))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,10 @@ public class MemoryRegister
|
|||
|
||||
public MemoryBlock Declare(MemoryBlockReference blockReference)
|
||||
{
|
||||
var block = blockReference.Declare();
|
||||
if (TryGetBlock(blockReference.Id, out var block))
|
||||
return block;
|
||||
|
||||
block = blockReference.Declare();
|
||||
Blocks[blockReference.Id] = block;
|
||||
return block;
|
||||
}
|
||||
|
|
|
|||
51
src/modules/Elsa.Expressions/Models/Result.cs
Normal file
51
src/modules/Elsa.Expressions/Models/Result.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
namespace Elsa.Expressions.Models;
|
||||
|
||||
/// <summary>
|
||||
/// A simple monad that runs either the <see cref="OnSuccess"/> or <see cref="OnFailure"/> lambda, depending on whether or not the operation succeeded.
|
||||
/// </summary>
|
||||
public class Result
|
||||
{
|
||||
internal Result(bool success, object? value, Exception? exception)
|
||||
{
|
||||
Success = success;
|
||||
Value = value;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the conversaion succeeded, false otherwise.
|
||||
/// </summary>
|
||||
public bool Success { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The result value.
|
||||
/// </summary>
|
||||
public object? Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Any exception that may have occurred during the operation.
|
||||
/// </summary>
|
||||
public Exception? Exception { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Runs the provided delegate if the result is successful.
|
||||
/// </summary>
|
||||
public Result OnSuccess(Action<object?> successHandler)
|
||||
{
|
||||
if (Success)
|
||||
successHandler(Value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the provided delegate if the result is unsuccessful.
|
||||
/// </summary>
|
||||
public Result OnFailure(Action<Exception> failureHandler)
|
||||
{
|
||||
if (Exception != null)
|
||||
failureHandler(Exception);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -42,12 +42,6 @@ public class PlayAudio : PlayAudioBase
|
|||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IActivity"/> to execute when audio playback has started.
|
||||
/// </summary>
|
||||
[Port]
|
||||
public IActivity? PlaybackStarted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IActivity"/> to execute when the call was no longer active.
|
||||
/// </summary>
|
||||
|
|
@ -55,7 +49,7 @@ public class PlayAudio : PlayAudioBase
|
|||
public IActivity? Disconnected { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async ValueTask HandlePlaybackStartedAsync(ActivityExecutionContext context) => await context.ScheduleActivityAsync(PlaybackStarted, OnCompletedAsync);
|
||||
protected override async ValueTask HandlePlaybackStartedAsync(ActivityExecutionContext context) => await context.CompleteActivityAsync();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async ValueTask HandleDisconnectedAsync(ActivityExecutionContext context) => await context.ScheduleActivityAsync(Disconnected, OnCompletedAsync);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,15 @@ namespace Elsa.Telnyx.Client.Models;
|
|||
|
||||
public record TelnyxResponse<T>(T Data);
|
||||
|
||||
public record CallStatusResponse(
|
||||
string CallControlId,
|
||||
string CallLegId,
|
||||
string CallSessionId,
|
||||
string ClientState,
|
||||
bool IsAlive,
|
||||
string RecordType
|
||||
);
|
||||
|
||||
public record DialResponse(
|
||||
string CallControlId,
|
||||
string CallLegId,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ namespace Elsa.Telnyx.Client.Services;
|
|||
|
||||
public interface ICallsApi
|
||||
{
|
||||
[Get("/v2/calls/{callControlId}")]
|
||||
Task<TelnyxResponse<CallStatusResponse>> GetStatusAsync(string callControlId, CancellationToken cancellationToken = default);
|
||||
|
||||
[Post("/v2/calls")]
|
||||
Task<TelnyxResponse<DialResponse>> DialAsync([Body] DialRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@ public class Response
|
|||
public ICollection<StorageDriverDescriptor> Items { get; set; }
|
||||
}
|
||||
|
||||
public record StorageDriverDescriptor(string Id, string DisplayName);
|
||||
public record StorageDriverDescriptor(string TypeName, string DisplayName);
|
||||
|
|
@ -22,6 +22,7 @@ public abstract class Composite : ActivityBase, IVariableContainer
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonIgnore] // Composite activities' Variables is intended to be constructed from code only.
|
||||
public ICollection<Variable> Variables { get; init; } = new List<Variable>();
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -29,7 +30,7 @@ public abstract class Composite : ActivityBase, IVariableContainer
|
|||
/// </summary>
|
||||
[Port]
|
||||
[Browsable(false)]
|
||||
[JsonIgnore] // Composite activities' Root is intended to be constructed from code only, so we don't want to get it serialized.
|
||||
[JsonIgnore] // Composite activities' Root is intended to be constructed from code only.
|
||||
public IActivity Root { get; set; } = new Sequence();
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -74,12 +75,15 @@ public abstract class Composite : ActivityBase, IVariableContainer
|
|||
|
||||
private async ValueTask OnCompleteCompositeSignal(CompleteCompositeSignal signal, SignalContext context)
|
||||
{
|
||||
await OnCompletedAsync(context.ReceiverActivityExecutionContext, context.SenderActivityExecutionContext);
|
||||
|
||||
// Complete the sender first so that it notifies its parents to complete.
|
||||
await context.SenderActivityExecutionContext.CompleteActivityAsync();
|
||||
|
||||
// Then complete this activity.
|
||||
await context.ReceiverActivityExecutionContext.CompleteActivityAsync(signal.Result);
|
||||
context.StopPropagation();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -28,9 +28,6 @@ public abstract class Container : ActivityBase, IVariableContainer
|
|||
/// <inheritdoc />
|
||||
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
// Register variables.
|
||||
context.ExpressionExecutionContext.Memory.Declare(Variables);
|
||||
|
||||
// Schedule children.
|
||||
await ScheduleChildrenAsync(context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,6 +103,9 @@ public static class ActivityExecutionContextExtensions
|
|||
context.SetHasEvaluatedProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the specified input property of the activity.
|
||||
/// </summary>
|
||||
public static async Task<T?> EvaluateInputPropertyAsync<TActivity, T>(this ActivityExecutionContext context, Expression<Func<TActivity, Input<T>>> propertyExpression)
|
||||
{
|
||||
var inputName = propertyExpression.GetProperty()!.Name;
|
||||
|
|
|
|||
|
|
@ -43,15 +43,16 @@ public static class ActivityExtensions
|
|||
return query.Select(x => x!).ToList();
|
||||
}
|
||||
|
||||
public static IEnumerable<Variable> GetVariables(this IActivity activity)
|
||||
public static IEnumerable<Variable> GetVariables(this IVariableContainer activity)
|
||||
{
|
||||
var properties = activity.GetType().GetProperties();
|
||||
var variableProps = properties.Where(x => typeof(Variable).IsAssignableFrom(x.PropertyType)).ToList();
|
||||
var variablesProps = properties.Where(x => typeof(IEnumerable<Variable>).IsAssignableFrom(x.PropertyType)).ToList();
|
||||
var variables = variableProps.Select(x => (Variable?)x.GetValue(activity)).Where(x => x != null).Select(x => x!).ToList();
|
||||
var manyVariables = variablesProps.Select(x => (IEnumerable<Variable>?)x.GetValue(activity)).Where(x => x != null).SelectMany(x => x!).ToList();
|
||||
//var properties = activity.GetType().GetProperties();
|
||||
//var variableProps = properties.Where(x => typeof(Variable).IsAssignableFrom(x.PropertyType)).ToList();
|
||||
//var variablesProps = properties.Where(x => typeof(IEnumerable<Variable>).IsAssignableFrom(x.PropertyType)).ToList();
|
||||
//var variables = variableProps.Select(x => (Variable?)x.GetValue(activity)).Where(x => x != null).Select(x => x!).ToList();
|
||||
//var manyVariables = variablesProps.Select(x => (IEnumerable<Variable>?)x.GetValue(activity)).Where(x => x != null).SelectMany(x => x!).ToList();
|
||||
|
||||
return variables.Concat(manyVariables).ToList();
|
||||
//return variables.Concat(manyVariables).ToList();
|
||||
return activity.Variables;
|
||||
}
|
||||
|
||||
public static TDelegate GetDelegate<TDelegate>(this IActivity activity, string methodName) where TDelegate : Delegate
|
||||
|
|
|
|||
|
|
@ -15,10 +15,20 @@ public static class VariableExtensions
|
|||
/// </summary>
|
||||
public static Variable WithWorkflowStorage(this Variable variable) => variable.WithStorage<WorkflowStorageDriver>();
|
||||
|
||||
/// <summary>
|
||||
/// Configures the variable to use the <see cref="WorkflowStorageDriver"/>.
|
||||
/// </summary>
|
||||
public static Variable<T> WithWorkflowStorage<T>(this Variable<T> variable) => (Variable<T>)variable.WithStorage<WorkflowStorageDriver>();
|
||||
|
||||
/// <summary>
|
||||
/// Configures the variable to use the <see cref="MemoryStorageDriver"/>.
|
||||
/// </summary>
|
||||
public static Variable WithMemoryStorage(this Variable variable) => variable.WithStorage<MemoryStorageDriver>();
|
||||
|
||||
/// <summary>
|
||||
/// Configures the variable to use the <see cref="MemoryStorageDriver"/>.
|
||||
/// </summary>
|
||||
public static Variable<T> WithMemoryStorage<T>(this Variable<T> variable) => (Variable<T>)variable.WithStorage<MemoryStorageDriver>();
|
||||
|
||||
/// <summary>
|
||||
/// Configures the variable to use the specified <see cref="IStorageDriver"/> type.
|
||||
|
|
|
|||
|
|
@ -76,10 +76,10 @@ public static class WorkflowExecutionContextExtensions
|
|||
IActivity activity,
|
||||
ActivityExecutionContext owner,
|
||||
ActivityCompletionCallback? completionCallback = default,
|
||||
IEnumerable<MemoryBlockReference>? references = default, object? tag = default)
|
||||
object? tag = default)
|
||||
{
|
||||
var activityInvoker = workflowExecutionContext.GetRequiredService<IActivityInvoker>();
|
||||
var workItem = new ActivityWorkItem(activity.Id, async () => await activityInvoker.InvokeAsync(workflowExecutionContext, activity, owner, references), tag);
|
||||
var workItem = new ActivityWorkItem(activity.Id, async () => await activityInvoker.InvokeAsync(workflowExecutionContext, activity, owner), tag);
|
||||
workflowExecutionContext.Scheduler.Schedule(workItem);
|
||||
workflowExecutionContext.AddCompletionCallback(owner, activity, completionCallback);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,20 +21,11 @@ public class ActivityInvoker : IActivityInvoker
|
|||
public async Task InvokeAsync(
|
||||
WorkflowExecutionContext workflowExecutionContext,
|
||||
IActivity activity,
|
||||
ActivityExecutionContext? owner,
|
||||
IEnumerable<MemoryBlockReference>? memoryReferences = default)
|
||||
ActivityExecutionContext? owner)
|
||||
{
|
||||
// Setup an activity execution context.
|
||||
var activityExecutionContext = workflowExecutionContext.CreateActivityExecutionContext(activity, owner);
|
||||
|
||||
// Declare memory.
|
||||
if (memoryReferences != null)
|
||||
{
|
||||
var workflowMemory = workflowExecutionContext.MemoryRegister;
|
||||
var activityMemory = new MemoryRegister(workflowMemory);
|
||||
activityMemory.Declare(memoryReferences);
|
||||
}
|
||||
|
||||
// Add the activity context to the workflow context.
|
||||
workflowExecutionContext.AddActivityExecutionContext(activityExecutionContext);
|
||||
|
||||
|
|
|
|||
|
|
@ -41,9 +41,6 @@ public class DefaultWorkflowExecutionContextFactory : IWorkflowExecutionContextF
|
|||
// Build graph.
|
||||
var graph = await _activityWalker.WalkAsync(root, cancellationToken);
|
||||
|
||||
// Assign identities.
|
||||
_identityGraphService.AssignIdentities(graph);
|
||||
|
||||
// Create scheduler.
|
||||
var scheduler = _schedulerFactory.CreateScheduler();
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ public class IdentityGraphService : IIdentityGraphService
|
|||
_activityWalker = activityWalker;
|
||||
}
|
||||
|
||||
public async Task AssignIdentitiesAsync(Workflow workflow, CancellationToken cancellationToken = default) => await AssignIdentitiesAsync(workflow.Root, cancellationToken);
|
||||
public async Task AssignIdentitiesAsync(Workflow workflow, CancellationToken cancellationToken = default) => await AssignIdentitiesAsync((IActivity)workflow, cancellationToken);
|
||||
|
||||
public async Task AssignIdentitiesAsync(IActivity root, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
|
@ -29,7 +29,9 @@ public class IdentityGraphService : IIdentityGraphService
|
|||
{
|
||||
node.Activity.Id = CreateId(node, identityCounters, list);
|
||||
AssignInputOutputs(node.Activity);
|
||||
AssignVariables(node.Activity);
|
||||
|
||||
if(node.Activity is IVariableContainer variableContainer)
|
||||
AssignVariables(variableContainer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +66,7 @@ public class IdentityGraphService : IIdentityGraphService
|
|||
}
|
||||
}
|
||||
|
||||
public void AssignVariables(IActivity activity)
|
||||
public void AssignVariables(IVariableContainer activity)
|
||||
{
|
||||
var variables = activity.GetVariables();
|
||||
var seed = 0;
|
||||
|
|
|
|||
|
|
@ -33,18 +33,18 @@ public class VariablePersistenceManager : IVariablePersistenceManager
|
|||
/// <inheritdoc />
|
||||
public IEnumerable<Variable> GetVariablesInScope(ActivityExecutionContext context)
|
||||
{
|
||||
// Get variables for the current activity's immediate composite container.
|
||||
var immediateCompositeVariables = ((Composite?)context.ActivityNode.Ancestors()
|
||||
.FirstOrDefault(x => x.Activity is Composite)?.Activity)?.Variables
|
||||
.Where(x => x.StorageDriverType != null) ?? Enumerable.Empty<Variable>();
|
||||
// Get variables between the current activity and immediate composite container.
|
||||
var ancestors = context.ActivityNode.Ancestors();
|
||||
|
||||
foreach (var node in ancestors)
|
||||
{
|
||||
if (node.Activity is IVariableContainer variableContainer)
|
||||
foreach (var variable in variableContainer.Variables)
|
||||
yield return variable;
|
||||
|
||||
// Get variables for the current activity itself, if it's a container.
|
||||
var directVariables = (context.Activity is Composite composite
|
||||
? composite.Variables.Where(x => x.StorageDriverType != null)
|
||||
: Enumerable.Empty<Variable>());
|
||||
|
||||
// Return a concatenated list of variables.
|
||||
return immediateCompositeVariables.Concat(directVariables);
|
||||
if (node.Activity is Composite)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -112,12 +112,7 @@ public class VariablePersistenceManager : IVariablePersistenceManager
|
|||
/// <inheritdoc />
|
||||
public void EnsureVariables(WorkflowExecutionContext context, IEnumerable<Variable> variables)
|
||||
{
|
||||
var register = context.MemoryRegister;
|
||||
foreach (var variable in variables)
|
||||
{
|
||||
if (!register.IsDeclared(variable))
|
||||
register.Declare(variable);
|
||||
}
|
||||
foreach (var variable in variables) context.MemoryRegister.Declare(variable);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
|||
|
|
@ -84,12 +84,12 @@ public class ActivityExecutionContext
|
|||
|
||||
public ResumedBookmarkContext? ResumedBookmarkContext => WorkflowExecutionContext.ResumedBookmarkContext;
|
||||
|
||||
public async ValueTask ScheduleActivityAsync(IActivity? activity, ActivityCompletionCallback? completionCallback = default, IEnumerable<MemoryBlockReference>? references = default, object? tag = default)
|
||||
public async ValueTask ScheduleActivityAsync(IActivity? activity, ActivityCompletionCallback? completionCallback = default, object? tag = default)
|
||||
{
|
||||
await ScheduleActivityAsync(activity, this, completionCallback, references, tag);
|
||||
await ScheduleActivityAsync(activity, this, completionCallback, tag);
|
||||
}
|
||||
|
||||
public async ValueTask ScheduleActivityAsync(IActivity? activity, ActivityExecutionContext owner, ActivityCompletionCallback? completionCallback = default, IEnumerable<MemoryBlockReference>? references = default, object? tag = default)
|
||||
public async ValueTask ScheduleActivityAsync(IActivity? activity, ActivityExecutionContext owner, ActivityCompletionCallback? completionCallback = default, object? tag = default)
|
||||
{
|
||||
if (activity == null)
|
||||
{
|
||||
|
|
@ -100,7 +100,7 @@ public class ActivityExecutionContext
|
|||
return;
|
||||
}
|
||||
|
||||
WorkflowExecutionContext.Schedule(activity, owner, completionCallback, references, tag);
|
||||
WorkflowExecutionContext.Schedule(activity, owner, completionCallback, tag);
|
||||
}
|
||||
|
||||
public async ValueTask ScheduleActivitiesAsync(params IActivity?[] activities) => await ScheduleActivities(activities);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Text.Json.Serialization;
|
|||
using Elsa.Expressions.Helpers;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Elsa.Workflows.Core.Serialization.Converters;
|
||||
|
||||
|
|
@ -12,11 +13,13 @@ namespace Elsa.Workflows.Core.Serialization.Converters;
|
|||
public class VariableConverter : JsonConverter<Variable>
|
||||
{
|
||||
private readonly IWellKnownTypeRegistry _wellKnownTypeRegistry;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <inheritdoc />
|
||||
public VariableConverter(IWellKnownTypeRegistry wellKnownTypeRegistry)
|
||||
public VariableConverter(IWellKnownTypeRegistry wellKnownTypeRegistry, ILogger<VariableConverter> logger)
|
||||
{
|
||||
_wellKnownTypeRegistry = wellKnownTypeRegistry;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -39,7 +42,7 @@ public class VariableConverter : JsonConverter<Variable>
|
|||
{
|
||||
if (string.IsNullOrWhiteSpace(source.TypeName))
|
||||
return null;
|
||||
|
||||
|
||||
if (!_wellKnownTypeRegistry.TryGetTypeOrDefault(source.TypeName, out var type))
|
||||
return null;
|
||||
|
||||
|
|
@ -47,7 +50,11 @@ public class VariableConverter : JsonConverter<Variable>
|
|||
var variable = (Variable)Activator.CreateInstance(variableGenericType)!;
|
||||
|
||||
variable.Name = source.Name;
|
||||
variable.Value = source.Value.ConvertTo(type);
|
||||
|
||||
source.Value.TryConvertTo(type)
|
||||
.OnSuccess(value => variable.Value = value)
|
||||
.OnFailure(e => _logger.LogWarning("Failed to convert {SourceValue} to {TargetType}", source.Value, type.Name));
|
||||
|
||||
variable.StorageDriverType = !string.IsNullOrEmpty(source.StorageDriverTypeName) ? Type.GetType(source.StorageDriverTypeName) : default;
|
||||
|
||||
return variable;
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ public interface IActivityInvoker
|
|||
Task InvokeAsync(
|
||||
WorkflowExecutionContext workflowExecutionContext,
|
||||
IActivity activity,
|
||||
ActivityExecutionContext? owner = default,
|
||||
IEnumerable<MemoryBlockReference>? memoryReferences = default);
|
||||
ActivityExecutionContext? owner = default);
|
||||
|
||||
Task InvokeAsync(ActivityExecutionContext activityExecutionContext);
|
||||
}
|
||||
|
|
@ -8,5 +8,5 @@ public interface IIdentityGraphService
|
|||
Task AssignIdentitiesAsync(IActivity root, CancellationToken cancellationToken = default);
|
||||
void AssignIdentities(ActivityNode root);
|
||||
void AssignInputOutputs(IActivity activity);
|
||||
void AssignVariables(IActivity activity);
|
||||
void AssignVariables(IVariableContainer activity);
|
||||
}
|
||||
|
|
@ -5,7 +5,10 @@ namespace Elsa.Workflows.Core.Services;
|
|||
/// <summary>
|
||||
/// Represents a container for <see cref="Variable"/>s.
|
||||
/// </summary>
|
||||
public interface IVariableContainer
|
||||
public interface IVariableContainer : IActivity
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of variables within the scope of the variable container.
|
||||
/// </summary>
|
||||
ICollection<Variable> Variables { get; }
|
||||
}
|
||||
|
|
@ -59,9 +59,6 @@ public class ActivityJsonConverter : JsonConverter<IActivity>
|
|||
var context = new ActivityConstructorContext(doc.RootElement, newOptions);
|
||||
var activity = activityDescriptor.Constructor(context);
|
||||
|
||||
_identityGraphService.AssignInputOutputs(activity);
|
||||
_identityGraphService.AssignVariables(activity);
|
||||
|
||||
return activity;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,8 +59,6 @@ public class ClrWorkflowDefinitionProvider : IWorkflowDefinitionProvider
|
|||
await workflowBuilder.BuildAsync(builder, cancellationToken);
|
||||
|
||||
var workflow = builder.BuildWorkflow();
|
||||
await _identityGraphService.AssignIdentitiesAsync(workflow, cancellationToken);
|
||||
|
||||
var workflowJson = JsonSerializer.Serialize(workflow.Root, _serializerOptionsProvider.CreatePersistenceOptions());
|
||||
var materializerContext = new ClrWorkflowMaterializerContext(workflowBuilder.GetType());
|
||||
var materializerContextJson = JsonSerializer.Serialize(materializerContext, _serializerOptionsProvider.CreatePersistenceOptions());
|
||||
|
|
@ -75,7 +73,6 @@ public class ClrWorkflowDefinitionProvider : IWorkflowDefinitionProvider
|
|||
Description = workflow.WorkflowMetadata.Description,
|
||||
CustomProperties = workflow.Metadata,
|
||||
Variables = workflow.Variables,
|
||||
//ApplicationProperties = workflow.ApplicationProperties,
|
||||
IsLatest = workflow.Publication.IsLatest,
|
||||
IsPublished = workflow.Publication.IsPublished,
|
||||
CreatedAt = workflow.WorkflowMetadata.CreatedAt == default ? _systemClock.UtcNow : workflow.WorkflowMetadata.CreatedAt,
|
||||
|
|
|
|||
|
|
@ -1,22 +1,30 @@
|
|||
using Elsa.Common.Models;
|
||||
using Elsa.Workflows.Management.Services;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
using Elsa.Workflows.Management.Entities;
|
||||
using Elsa.Workflows.Runtime.Services;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.Implementations;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class WorkflowDefinitionService : IWorkflowDefinitionService
|
||||
{
|
||||
private readonly IWorkflowDefinitionStore _workflowDefinitionStore;
|
||||
private readonly IIdentityGraphService _identityGraphService;
|
||||
private readonly IEnumerable<IWorkflowMaterializer> _materializers;
|
||||
|
||||
public WorkflowDefinitionService(IWorkflowDefinitionStore workflowDefinitionStore, IEnumerable<IWorkflowMaterializer> materializers)
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public WorkflowDefinitionService(IWorkflowDefinitionStore workflowDefinitionStore, IIdentityGraphService identityGraphService, IEnumerable<IWorkflowMaterializer> materializers)
|
||||
{
|
||||
_workflowDefinitionStore = workflowDefinitionStore;
|
||||
_identityGraphService = identityGraphService;
|
||||
_materializers = materializers;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Workflow> MaterializeWorkflowAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var provider = _materializers.FirstOrDefault(x => x.Name == definition.MaterializerName);
|
||||
|
|
@ -24,9 +32,15 @@ public class WorkflowDefinitionService : IWorkflowDefinitionService
|
|||
if (provider == null)
|
||||
throw new Exception("Provider not found");
|
||||
|
||||
return await provider.MaterializeAsync(definition, cancellationToken);
|
||||
}
|
||||
var workflow = await provider.MaterializeAsync(definition, cancellationToken);
|
||||
|
||||
// Assign identities.
|
||||
await _identityGraphService.AssignIdentitiesAsync(workflow, cancellationToken);
|
||||
|
||||
return workflow;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<WorkflowDefinition?> FindAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) =>
|
||||
await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, versionOptions, cancellationToken);
|
||||
}
|
||||
|
|
@ -9,6 +9,13 @@ namespace Elsa.Workflows.Runtime.Services;
|
|||
/// </summary>
|
||||
public interface IWorkflowDefinitionService
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs an executable <see cref="Workflow"/> from the specified <see cref="WorkflowDefinition"/>.
|
||||
/// </summary>
|
||||
Task<Workflow> MaterializeWorkflowAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Looks for a <see cref="WorkflowDefinition"/> by the specified definition ID and <see cref="VersionOptions"/>.
|
||||
/// </summary>
|
||||
Task<WorkflowDefinition?> FindAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
|||
using Elsa.Workflows.Core;
|
||||
using Elsa.Workflows.Core.Activities;
|
||||
using Elsa.Workflows.Core.Activities.Flowchart.Models;
|
||||
using Elsa.Workflows.Core.Implementations;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
|
||||
|
|
@ -18,7 +19,7 @@ class Workflow1 : WorkflowBase
|
|||
|
||||
protected override void Build(IWorkflowBuilder workflow)
|
||||
{
|
||||
var currentItem = workflow.WithVariable<string>("CurrentValue", "", StorageDriverNames.Memory);
|
||||
var currentItem = workflow.WithVariable<string>("CurrentValue", "").WithMemoryStorage();
|
||||
var writeLine1 = new WriteLine { Id = "WriteLine1", Text = new Input<string>("Start!") };
|
||||
var forEach1 = new ForEach<string> { Id = "ForEach1", Items = new Input<ICollection<string>>(_items), CurrentValue = new Output<string?>(currentItem) };
|
||||
var writeLine2 = new WriteLine { Id = "WriteLine2", Text = new Input<string>("Current Item") };
|
||||
|
|
@ -51,7 +52,7 @@ class Workflow2 : WorkflowBase
|
|||
|
||||
protected override void Build(IWorkflowBuilder workflow)
|
||||
{
|
||||
var currentItem = workflow.WithVariable<string>("CurrentValue", "", StorageDriverNames.Memory);
|
||||
var currentItem = workflow.WithVariable<string>("CurrentValue", "").WithMemoryStorage();
|
||||
var writeLine1 = new WriteLine { Id = "WriteLine1", Text = new Input<string>("Start!") };
|
||||
var forEach1 = new ForEach<string> { Id = "ForEach1", Items = new Input<ICollection<string>>(_items), CurrentValue = new Output<string?>(currentItem) };
|
||||
var writeLine2 = new WriteLine { Id = "WriteLine2", Text = new Input<string>(currentItem) };
|
||||
|
|
@ -82,7 +83,7 @@ class Workflow3 : WorkflowBase
|
|||
|
||||
protected override void Build(IWorkflowBuilder workflow)
|
||||
{
|
||||
var currentItem = workflow.WithVariable<string>("CurrentValue", "", StorageDriverNames.Memory);
|
||||
var currentItem = workflow.WithVariable<string>("CurrentValue", "").WithMemoryStorage();
|
||||
var writeLine1 = new WriteLine { Id = "WriteLine1", Text = new Input<string>("Start!") };
|
||||
|
||||
var forEach1 = new ForEach<string>
|
||||
|
|
|
|||
Loading…
Reference in a new issue