diff --git a/src/designer/elsa-workflows-designer/src/components/designer/variables-editor/variable-editor-dialog-content.tsx b/src/designer/elsa-workflows-designer/src/components/designer/variables-editor/variable-editor-dialog-content.tsx index a587b3d7b..a749575f9 100644 --- a/src/designer/elsa-workflows-designer/src/components/designer/variables-editor/variable-editor-dialog-content.tsx +++ b/src/designer/elsa-workflows-designer/src/components/designer/variables-editor/variable-editor-dialog-content.tsx @@ -55,11 +55,11 @@ export class VariableEditorDialogContent { - {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 ; })} @@ -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; }; diff --git a/src/designer/elsa-workflows-designer/src/components/designer/variables-editor/variables-editor.tsx b/src/designer/elsa-workflows-designer/src/components/designer/variables-editor/variables-editor.tsx index b50807078..99fc8c064 100644 --- a/src/designer/elsa-workflows-designer/src/components/designer/variables-editor/variables-editor.tsx +++ b/src/designer/elsa-workflows-designer/src/components/designer/variables-editor/variables-editor.tsx @@ -60,7 +60,7 @@ export class VariablesEditor { {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; diff --git a/src/designer/elsa-workflows-designer/src/models/api.ts b/src/designer/elsa-workflows-designer/src/models/api.ts index 0831184ab..b5bd4c667 100644 --- a/src/designer/elsa-workflows-designer/src/models/api.ts +++ b/src/designer/elsa-workflows-designer/src/models/api.ts @@ -72,7 +72,7 @@ export interface IntellisenseContext { } export interface StorageDriverDescriptor { - id: string; + typeName: string; displayName: string; } diff --git a/src/designer/elsa-workflows-designer/src/models/core.ts b/src/designer/elsa-workflows-designer/src/models/core.ts index f3f0010e4..dc6b27fa0 100644 --- a/src/designer/elsa-workflows-designer/src/models/core.ts +++ b/src/designer/elsa-workflows-designer/src/models/core.ts @@ -34,7 +34,7 @@ export interface Variable { name: string; typeName: string; value?: any; - storageDriverId?: string; + storageDriverTypeName?: string; } export interface ActivityInput { diff --git a/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/WorkflowDefinitionBuilderInterpreter.cs b/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/WorkflowDefinitionBuilderInterpreter.cs index 8e7b7eb33..17d353a12 100644 --- a/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/WorkflowDefinitionBuilderInterpreter.cs +++ b/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/WorkflowDefinitionBuilderInterpreter.cs @@ -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> _argValues = new(); private readonly ParseTreeProperty _expressionType = new(); private readonly IDictionary _definedVariables = new Dictionary(); - private readonly Stack _containerStack = new(); + private readonly Stack _containerStack = new(); + /// public WorkflowDefinitionBuilderInterpreter( ITypeSystem typeSystem, IFunctionActivityRegistry functionActivityRegistry, diff --git a/src/modules/Elsa.Expressions/Elsa.Expressions.csproj b/src/modules/Elsa.Expressions/Elsa.Expressions.csproj index 3bdebc493..2a4cb3f04 100644 --- a/src/modules/Elsa.Expressions/Elsa.Expressions.csproj +++ b/src/modules/Elsa.Expressions/Elsa.Expressions.csproj @@ -13,7 +13,6 @@ - diff --git a/src/modules/Elsa.Expressions/Extensions/TypeExtensions.cs b/src/modules/Elsa.Expressions/Extensions/TypeExtensions.cs new file mode 100644 index 000000000..f0553e7b5 --- /dev/null +++ b/src/modules/Elsa.Expressions/Extensions/TypeExtensions.cs @@ -0,0 +1,12 @@ +namespace Elsa.Expressions.Extensions; + +/// +/// Adds extension methods to . +/// +public static class TypeExtensions +{ + /// + /// Returns the default value for the specified type. + /// + public static object? GetDefaultValue(this Type type) => type.IsClass ? null : Activator.CreateInstance(type); +} \ No newline at end of file diff --git a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs index c1911e5c8..d8904566c 100644 --- a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs +++ b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs @@ -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; +/// +/// A helper that attempts many strategies to try and convert the source value into the destination type. +/// public static class ObjectConverter { + public static Result TryConvertTo(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(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(); - return JsonSerializer.Deserialize(node.ToString(), type, options); - } } \ No newline at end of file diff --git a/src/modules/Elsa.Expressions/Models/DelegateBlockReference.cs b/src/modules/Elsa.Expressions/Models/DelegateBlockReference.cs index e832a9ddf..333f222a4 100644 --- a/src/modules/Elsa.Expressions/Models/DelegateBlockReference.cs +++ b/src/modules/Elsa.Expressions/Models/DelegateBlockReference.cs @@ -32,7 +32,7 @@ public class DelegateBlockReference : DelegateBlockReference { } - public DelegateBlockReference(Func> @delegate) : base(x => @delegate(x)) + public DelegateBlockReference(Func> @delegate) : base(async x => await @delegate(x)) { } } \ No newline at end of file diff --git a/src/modules/Elsa.Expressions/Models/MemoryRegister.cs b/src/modules/Elsa.Expressions/Models/MemoryRegister.cs index f7da6a6c1..cbf2f178d 100644 --- a/src/modules/Elsa.Expressions/Models/MemoryRegister.cs +++ b/src/modules/Elsa.Expressions/Models/MemoryRegister.cs @@ -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; } diff --git a/src/modules/Elsa.Expressions/Models/Result.cs b/src/modules/Elsa.Expressions/Models/Result.cs new file mode 100644 index 000000000..6c3429caa --- /dev/null +++ b/src/modules/Elsa.Expressions/Models/Result.cs @@ -0,0 +1,51 @@ +namespace Elsa.Expressions.Models; + +/// +/// A simple monad that runs either the or lambda, depending on whether or not the operation succeeded. +/// +public class Result +{ + internal Result(bool success, object? value, Exception? exception) + { + Success = success; + Value = value; + Exception = exception; + } + + /// + /// True if the conversaion succeeded, false otherwise. + /// + public bool Success { get; } + + /// + /// The result value. + /// + public object? Value { get; } + + /// + /// Any exception that may have occurred during the operation. + /// + public Exception? Exception { get; } + + /// + /// Runs the provided delegate if the result is successful. + /// + public Result OnSuccess(Action successHandler) + { + if (Success) + successHandler(Value); + + return this; + } + + /// + /// Runs the provided delegate if the result is unsuccessful. + /// + public Result OnFailure(Action failureHandler) + { + if (Exception != null) + failureHandler(Exception); + + return this; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/PlayAudio.cs b/src/modules/Elsa.Telnyx/Activities/PlayAudio.cs index 43abff6b7..95099dbe4 100644 --- a/src/modules/Elsa.Telnyx/Activities/PlayAudio.cs +++ b/src/modules/Elsa.Telnyx/Activities/PlayAudio.cs @@ -42,12 +42,6 @@ public class PlayAudio : PlayAudioBase { } - /// - /// The to execute when audio playback has started. - /// - [Port] - public IActivity? PlaybackStarted { get; set; } - /// /// The to execute when the call was no longer active. /// @@ -55,7 +49,7 @@ public class PlayAudio : PlayAudioBase public IActivity? Disconnected { get; set; } /// - protected override async ValueTask HandlePlaybackStartedAsync(ActivityExecutionContext context) => await context.ScheduleActivityAsync(PlaybackStarted, OnCompletedAsync); + protected override async ValueTask HandlePlaybackStartedAsync(ActivityExecutionContext context) => await context.CompleteActivityAsync(); /// protected override async ValueTask HandleDisconnectedAsync(ActivityExecutionContext context) => await context.ScheduleActivityAsync(Disconnected, OnCompletedAsync); diff --git a/src/modules/Elsa.Telnyx/Client/Models/Responses.cs b/src/modules/Elsa.Telnyx/Client/Models/Responses.cs index 45f6dfb1e..392043a0f 100644 --- a/src/modules/Elsa.Telnyx/Client/Models/Responses.cs +++ b/src/modules/Elsa.Telnyx/Client/Models/Responses.cs @@ -5,6 +5,15 @@ namespace Elsa.Telnyx.Client.Models; public record TelnyxResponse(T Data); +public record CallStatusResponse( + string CallControlId, + string CallLegId, + string CallSessionId, + string ClientState, + bool IsAlive, + string RecordType +); + public record DialResponse( string CallControlId, string CallLegId, diff --git a/src/modules/Elsa.Telnyx/Client/Services/ICallsApi.cs b/src/modules/Elsa.Telnyx/Client/Services/ICallsApi.cs index a4c072f75..c5aff0d7b 100644 --- a/src/modules/Elsa.Telnyx/Client/Services/ICallsApi.cs +++ b/src/modules/Elsa.Telnyx/Client/Services/ICallsApi.cs @@ -5,6 +5,9 @@ namespace Elsa.Telnyx.Client.Services; public interface ICallsApi { + [Get("/v2/calls/{callControlId}")] + Task> GetStatusAsync(string callControlId, CancellationToken cancellationToken = default); + [Post("/v2/calls")] Task> DialAsync([Body] DialRequest request, CancellationToken cancellationToken = default); diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/StorageDrivers/List/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/StorageDrivers/List/Models.cs index cf8d119db..d2b92623f 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/StorageDrivers/List/Models.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/StorageDrivers/List/Models.cs @@ -10,4 +10,4 @@ public class Response public ICollection Items { get; set; } } -public record StorageDriverDescriptor(string Id, string DisplayName); \ No newline at end of file +public record StorageDriverDescriptor(string TypeName, string DisplayName); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Activities/Composite.cs b/src/modules/Elsa.Workflows.Core/Activities/Composite.cs index 651a9f606..bd21c8e7a 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/Composite.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/Composite.cs @@ -22,6 +22,7 @@ public abstract class Composite : ActivityBase, IVariableContainer } /// + [JsonIgnore] // Composite activities' Variables is intended to be constructed from code only. public ICollection Variables { get; init; } = new List(); /// @@ -29,7 +30,7 @@ public abstract class Composite : ActivityBase, IVariableContainer /// [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(); /// @@ -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(); + } /// diff --git a/src/modules/Elsa.Workflows.Core/Activities/Container.cs b/src/modules/Elsa.Workflows.Core/Activities/Container.cs index 5607477c0..23e72e472 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/Container.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/Container.cs @@ -28,9 +28,6 @@ public abstract class Container : ActivityBase, IVariableContainer /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { - // Register variables. - context.ExpressionExecutionContext.Memory.Declare(Variables); - // Schedule children. await ScheduleChildrenAsync(context); } diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs index dda55d7b9..8a2d89927 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs @@ -103,6 +103,9 @@ public static class ActivityExecutionContextExtensions context.SetHasEvaluatedProperties(); } + /// + /// Evaluates the specified input property of the activity. + /// public static async Task EvaluateInputPropertyAsync(this ActivityExecutionContext context, Expression>> propertyExpression) { var inputName = propertyExpression.GetProperty()!.Name; diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExtensions.cs index bfcb5a4ce..460e76908 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExtensions.cs @@ -43,15 +43,16 @@ public static class ActivityExtensions return query.Select(x => x!).ToList(); } - public static IEnumerable GetVariables(this IActivity activity) + public static IEnumerable 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).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?)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).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?)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(this IActivity activity, string methodName) where TDelegate : Delegate diff --git a/src/modules/Elsa.Workflows.Core/Extensions/VariableExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/VariableExtensions.cs index ded8bccbe..5c3e6cc97 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/VariableExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/VariableExtensions.cs @@ -15,10 +15,20 @@ public static class VariableExtensions /// public static Variable WithWorkflowStorage(this Variable variable) => variable.WithStorage(); + /// + /// Configures the variable to use the . + /// + public static Variable WithWorkflowStorage(this Variable variable) => (Variable)variable.WithStorage(); + /// /// Configures the variable to use the . /// public static Variable WithMemoryStorage(this Variable variable) => variable.WithStorage(); + + /// + /// Configures the variable to use the . + /// + public static Variable WithMemoryStorage(this Variable variable) => (Variable)variable.WithStorage(); /// /// Configures the variable to use the specified type. diff --git a/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs index 19fe6ba2a..f3a834f45 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs @@ -76,10 +76,10 @@ public static class WorkflowExecutionContextExtensions IActivity activity, ActivityExecutionContext owner, ActivityCompletionCallback? completionCallback = default, - IEnumerable? references = default, object? tag = default) + object? tag = default) { var activityInvoker = workflowExecutionContext.GetRequiredService(); - 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); } diff --git a/src/modules/Elsa.Workflows.Core/Implementations/ActivityInvoker.cs b/src/modules/Elsa.Workflows.Core/Implementations/ActivityInvoker.cs index 7d3123cff..0277f1d46 100644 --- a/src/modules/Elsa.Workflows.Core/Implementations/ActivityInvoker.cs +++ b/src/modules/Elsa.Workflows.Core/Implementations/ActivityInvoker.cs @@ -21,20 +21,11 @@ public class ActivityInvoker : IActivityInvoker public async Task InvokeAsync( WorkflowExecutionContext workflowExecutionContext, IActivity activity, - ActivityExecutionContext? owner, - IEnumerable? 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); diff --git a/src/modules/Elsa.Workflows.Core/Implementations/DefaultWorkflowExecutionContextFactory.cs b/src/modules/Elsa.Workflows.Core/Implementations/DefaultWorkflowExecutionContextFactory.cs index e3da5f25b..15f91693f 100644 --- a/src/modules/Elsa.Workflows.Core/Implementations/DefaultWorkflowExecutionContextFactory.cs +++ b/src/modules/Elsa.Workflows.Core/Implementations/DefaultWorkflowExecutionContextFactory.cs @@ -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(); diff --git a/src/modules/Elsa.Workflows.Core/Implementations/IdentityGraphService.cs b/src/modules/Elsa.Workflows.Core/Implementations/IdentityGraphService.cs index cabd5cf4d..fee93a3e4 100644 --- a/src/modules/Elsa.Workflows.Core/Implementations/IdentityGraphService.cs +++ b/src/modules/Elsa.Workflows.Core/Implementations/IdentityGraphService.cs @@ -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; diff --git a/src/modules/Elsa.Workflows.Core/Implementations/VariablePersistenceManager.cs b/src/modules/Elsa.Workflows.Core/Implementations/VariablePersistenceManager.cs index 903300158..f2329c1f1 100644 --- a/src/modules/Elsa.Workflows.Core/Implementations/VariablePersistenceManager.cs +++ b/src/modules/Elsa.Workflows.Core/Implementations/VariablePersistenceManager.cs @@ -33,18 +33,18 @@ public class VariablePersistenceManager : IVariablePersistenceManager /// public IEnumerable 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(); + // 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()); - - // Return a concatenated list of variables. - return immediateCompositeVariables.Concat(directVariables); + if (node.Activity is Composite) + break; + } } /// @@ -112,12 +112,7 @@ public class VariablePersistenceManager : IVariablePersistenceManager /// public void EnsureVariables(WorkflowExecutionContext context, IEnumerable 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); } /// diff --git a/src/modules/Elsa.Workflows.Core/Models/ActivityExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Models/ActivityExecutionContext.cs index 8e0f66291..d981886c0 100644 --- a/src/modules/Elsa.Workflows.Core/Models/ActivityExecutionContext.cs +++ b/src/modules/Elsa.Workflows.Core/Models/ActivityExecutionContext.cs @@ -84,12 +84,12 @@ public class ActivityExecutionContext public ResumedBookmarkContext? ResumedBookmarkContext => WorkflowExecutionContext.ResumedBookmarkContext; - public async ValueTask ScheduleActivityAsync(IActivity? activity, ActivityCompletionCallback? completionCallback = default, IEnumerable? 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? 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); diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Converters/VariableConverter.cs b/src/modules/Elsa.Workflows.Core/Serialization/Converters/VariableConverter.cs index 322724413..02b4cbffa 100644 --- a/src/modules/Elsa.Workflows.Core/Serialization/Converters/VariableConverter.cs +++ b/src/modules/Elsa.Workflows.Core/Serialization/Converters/VariableConverter.cs @@ -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 { private readonly IWellKnownTypeRegistry _wellKnownTypeRegistry; + private readonly ILogger _logger; /// - public VariableConverter(IWellKnownTypeRegistry wellKnownTypeRegistry) + public VariableConverter(IWellKnownTypeRegistry wellKnownTypeRegistry, ILogger logger) { _wellKnownTypeRegistry = wellKnownTypeRegistry; + _logger = logger; } /// @@ -39,7 +42,7 @@ public class VariableConverter : JsonConverter { 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 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; diff --git a/src/modules/Elsa.Workflows.Core/Services/IActivityInvoker.cs b/src/modules/Elsa.Workflows.Core/Services/IActivityInvoker.cs index a70ab9c40..7a941a06a 100644 --- a/src/modules/Elsa.Workflows.Core/Services/IActivityInvoker.cs +++ b/src/modules/Elsa.Workflows.Core/Services/IActivityInvoker.cs @@ -8,8 +8,7 @@ public interface IActivityInvoker Task InvokeAsync( WorkflowExecutionContext workflowExecutionContext, IActivity activity, - ActivityExecutionContext? owner = default, - IEnumerable? memoryReferences = default); + ActivityExecutionContext? owner = default); Task InvokeAsync(ActivityExecutionContext activityExecutionContext); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Services/IIdentityGraphService.cs b/src/modules/Elsa.Workflows.Core/Services/IIdentityGraphService.cs index 6539e0d21..200c2165b 100644 --- a/src/modules/Elsa.Workflows.Core/Services/IIdentityGraphService.cs +++ b/src/modules/Elsa.Workflows.Core/Services/IIdentityGraphService.cs @@ -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); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Services/IVariableContainer.cs b/src/modules/Elsa.Workflows.Core/Services/IVariableContainer.cs index 39a041053..21137134a 100644 --- a/src/modules/Elsa.Workflows.Core/Services/IVariableContainer.cs +++ b/src/modules/Elsa.Workflows.Core/Services/IVariableContainer.cs @@ -5,7 +5,10 @@ namespace Elsa.Workflows.Core.Services; /// /// Represents a container for s. /// -public interface IVariableContainer +public interface IVariableContainer : IActivity { + /// + /// A collection of variables within the scope of the variable container. + /// ICollection Variables { get; } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverter.cs b/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverter.cs index f2430895a..a3fd676a2 100644 --- a/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverter.cs +++ b/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverter.cs @@ -59,9 +59,6 @@ public class ActivityJsonConverter : JsonConverter var context = new ActivityConstructorContext(doc.RootElement, newOptions); var activity = activityDescriptor.Constructor(context); - _identityGraphService.AssignInputOutputs(activity); - _identityGraphService.AssignVariables(activity); - return activity; } diff --git a/src/modules/Elsa.Workflows.Runtime/Implementations/ClrWorkflowDefinitionProvider.cs b/src/modules/Elsa.Workflows.Runtime/Implementations/ClrWorkflowDefinitionProvider.cs index 4d248bbfa..732ca1c5d 100644 --- a/src/modules/Elsa.Workflows.Runtime/Implementations/ClrWorkflowDefinitionProvider.cs +++ b/src/modules/Elsa.Workflows.Runtime/Implementations/ClrWorkflowDefinitionProvider.cs @@ -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, diff --git a/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowDefinitionService.cs b/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowDefinitionService.cs index f258f8e8d..930406d2b 100644 --- a/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowDefinitionService.cs +++ b/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowDefinitionService.cs @@ -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; +/// public class WorkflowDefinitionService : IWorkflowDefinitionService { private readonly IWorkflowDefinitionStore _workflowDefinitionStore; + private readonly IIdentityGraphService _identityGraphService; private readonly IEnumerable _materializers; - public WorkflowDefinitionService(IWorkflowDefinitionStore workflowDefinitionStore, IEnumerable materializers) + /// + /// Constructor. + /// + public WorkflowDefinitionService(IWorkflowDefinitionStore workflowDefinitionStore, IIdentityGraphService identityGraphService, IEnumerable materializers) { _workflowDefinitionStore = workflowDefinitionStore; + _identityGraphService = identityGraphService; _materializers = materializers; } + /// public async Task 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; + } + + /// public async Task FindAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) => await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, versionOptions, cancellationToken); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowDefinitionService.cs b/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowDefinitionService.cs index af06b56e5..96e69e8bb 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowDefinitionService.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowDefinitionService.cs @@ -9,6 +9,13 @@ namespace Elsa.Workflows.Runtime.Services; /// public interface IWorkflowDefinitionService { + /// + /// Constructs an executable from the specified . + /// Task MaterializeWorkflowAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default); + + /// + /// Looks for a by the specified definition ID and . + /// Task FindAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/test/Elsa.IntegrationTests/Activities/Flowchart/Services/Workflows.cs b/test/Elsa.IntegrationTests/Activities/Flowchart/Services/Workflows.cs index 38f022e1a..320a9fb53 100644 --- a/test/Elsa.IntegrationTests/Activities/Flowchart/Services/Workflows.cs +++ b/test/Elsa.IntegrationTests/Activities/Flowchart/Services/Workflows.cs @@ -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("CurrentValue", "", StorageDriverNames.Memory); + var currentItem = workflow.WithVariable("CurrentValue", "").WithMemoryStorage(); var writeLine1 = new WriteLine { Id = "WriteLine1", Text = new Input("Start!") }; var forEach1 = new ForEach { Id = "ForEach1", Items = new Input>(_items), CurrentValue = new Output(currentItem) }; var writeLine2 = new WriteLine { Id = "WriteLine2", Text = new Input("Current Item") }; @@ -51,7 +52,7 @@ class Workflow2 : WorkflowBase protected override void Build(IWorkflowBuilder workflow) { - var currentItem = workflow.WithVariable("CurrentValue", "", StorageDriverNames.Memory); + var currentItem = workflow.WithVariable("CurrentValue", "").WithMemoryStorage(); var writeLine1 = new WriteLine { Id = "WriteLine1", Text = new Input("Start!") }; var forEach1 = new ForEach { Id = "ForEach1", Items = new Input>(_items), CurrentValue = new Output(currentItem) }; var writeLine2 = new WriteLine { Id = "WriteLine2", Text = new Input(currentItem) }; @@ -82,7 +83,7 @@ class Workflow3 : WorkflowBase protected override void Build(IWorkflowBuilder workflow) { - var currentItem = workflow.WithVariable("CurrentValue", "", StorageDriverNames.Memory); + var currentItem = workflow.WithVariable("CurrentValue", "").WithMemoryStorage(); var writeLine1 = new WriteLine { Id = "WriteLine1", Text = new Input("Start!") }; var forEach1 = new ForEach