diff --git a/src/designer/elsa-workflows-designer/src/components.d.ts b/src/designer/elsa-workflows-designer/src/components.d.ts index 5a5b334b9..25face086 100644 --- a/src/designer/elsa-workflows-designer/src/components.d.ts +++ b/src/designer/elsa-workflows-designer/src/components.d.ts @@ -248,6 +248,7 @@ export namespace Components { } interface ElsaVariablePickerInput { "inputContext": ActivityInputContext; + "workflowDefinition": WorkflowDefinition; } interface ElsaVariablesEditor { "variables"?: Array; @@ -1134,6 +1135,7 @@ declare namespace LocalJSX { } interface ElsaVariablePickerInput { "inputContext"?: ActivityInputContext; + "workflowDefinition"?: WorkflowDefinition; } interface ElsaVariablesEditor { "onVariablesChanged"?: (event: ElsaVariablesEditorCustomEvent>) => void; diff --git a/src/designer/elsa-workflows-designer/src/components/inputs/variable-picker.tsx b/src/designer/elsa-workflows-designer/src/components/inputs/variable-picker.tsx index b40a3589c..bc5f43f69 100644 --- a/src/designer/elsa-workflows-designer/src/components/inputs/variable-picker.tsx +++ b/src/designer/elsa-workflows-designer/src/components/inputs/variable-picker.tsx @@ -4,13 +4,15 @@ import {ActivityInputContext} from "../../services/activity-input-driver"; import {getPropertyValue} from "../../utils"; import {FormEntry} from "../shared/forms/form-entry"; import WorkflowDefinitionTunnel from "../../modules/workflow-definitions/state"; +import {WorkflowDefinition} from "../../modules/workflow-definitions/models/entities"; @Component({ tag: 'elsa-variable-picker-input', shadow: false }) export class VariablePickerInput { - @Prop() public inputContext: ActivityInputContext; + @Prop() inputContext: ActivityInputContext; + @Prop() workflowDefinition: WorkflowDefinition; // Injected by WorkflowDefinitionTunnel public render() { const inputContext = this.inputContext; @@ -36,9 +38,9 @@ export class VariablePickerInput { @@ -49,8 +51,10 @@ export class VariablePickerInput { private onChange = (e: Event) => { const inputElement = e.target as HTMLSelectElement; - const json = inputElement.selectedOptions[0].dataset.variable; - const variable = inputElement.selectedIndex <= 0 ? null : JSON.parse(json); + const variableId = inputElement.value; + const variable = this.workflowDefinition.variables.find(x => x.id == variableId); + this.inputContext.inputChanged(variable, SyntaxNames.Literal); } } +WorkflowDefinitionTunnel.injectProps(VariablePickerInput, ['workflowDefinition']); diff --git a/src/designer/elsa-workflows-designer/src/models/core.ts b/src/designer/elsa-workflows-designer/src/models/core.ts index 7a04278e6..616a883b6 100644 --- a/src/designer/elsa-workflows-designer/src/models/core.ts +++ b/src/designer/elsa-workflows-designer/src/models/core.ts @@ -36,6 +36,7 @@ export interface Workflow extends Activity { } export interface Variable { + id: string; name: string; typeName: string; isArray: boolean; diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/activity-properties/activity-properties-editor.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/activity-properties/activity-properties-editor.tsx index b9115a0d6..af48ef047 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/activity-properties/activity-properties-editor.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/activity-properties/activity-properties-editor.tsx @@ -278,8 +278,7 @@ export class ActivityPropertiesEditor { const activity = this.activity; const propertyName = outputDescriptor.name; const camelCasePropertyName = camelCase(propertyName); - const outputTargetValuePair = outputTargetValue.split(':'); - const kind = outputTargetValuePair[0]; + const outputTargetValuePair = outputTargetValue.split('::'); const outputTargetId = outputTargetValuePair[1]; const property: ActivityOutput = { @@ -303,9 +302,7 @@ export class ActivityPropertiesEditor { newId: activityId, originalId: activityId, activity, - activityDescriptor, - // propertyName: propertyName, - // propertyDescriptor: propertyDescriptor + activityDescriptor }); } @@ -356,8 +353,9 @@ export class ActivityPropertiesEditor { const key = `${activityId}`; const outputTargetOptions: Array = [null]; - if (variables.length > 0) - outputTargetOptions.push({label: 'Variables', items: [...variables.map(x => ({value: x.name, name: x.name}))], kind: 'variable'}); + if (variables.length > 0) { + outputTargetOptions.push({label: 'Variables', items: [...variables.map(x => ({value: x.id, name: x.name}))], kind: 'variable'}); + } if (outputDefinitions.length > 0) outputTargetOptions.push({label: 'Outputs', items: [...outputDefinitions.map(x => ({value: x.name, name: x.name}))], kind: 'output'}); @@ -388,7 +386,7 @@ export class ActivityPropertiesEditor { {items.map(item => { const isSelected = propertyValue?.memoryReference?.id == item.value; - return ; + return ; })} ); })} @@ -413,7 +411,6 @@ export class ActivityPropertiesEditor { this.onRunAsynchronouslyChanged(e)}/> - }; } diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/workflow-properties/variable-editor-dialog-content.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/workflow-properties/variable-editor-dialog-content.tsx index 820c37a54..70d03bf54 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/workflow-properties/variable-editor-dialog-content.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/workflow-properties/variable-editor-dialog-content.tsx @@ -22,7 +22,7 @@ export class VariableEditorDialogContent { } render() { - const variable: Variable = this.variable ?? {name: '', typeName: 'Object', isArray: false}; + const variable: Variable = this.variable ?? {id: '', name: '', typeName: 'Object', isArray: false}; const variableTypeName = variable.typeName; const availableTypes: Array = descriptorsStore.variableDescriptors; const groupedVariableTypes = groupBy(availableTypes, x => x.category); diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/workflow-properties/variables-editor.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/workflow-properties/variables-editor.tsx index 765ca2d73..edbdbe35c 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/workflow-properties/variables-editor.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/workflow-properties/variables-editor.tsx @@ -109,7 +109,7 @@ export class VariablesEditor { private onAddVariableClick = async () => { const newVariableName = this.generateNewVariableName(); - const variable: Variable = {name: newVariableName, typeName: 'Object', value: null, isArray: false}; + const variable: Variable = {id: '', name: newVariableName, typeName: 'Object', value: null, isArray: false}; this.modalDialogInstance = this.modalDialogService.show(() => , {actions: [this.saveAction]}) }; diff --git a/src/modules/Elsa.Expressions/Models/ExpressionExecutionContext.cs b/src/modules/Elsa.Expressions/Models/ExpressionExecutionContext.cs index 68c60fbe7..24acea929 100644 --- a/src/modules/Elsa.Expressions/Models/ExpressionExecutionContext.cs +++ b/src/modules/Elsa.Expressions/Models/ExpressionExecutionContext.cs @@ -59,6 +59,16 @@ public class ExpressionExecutionContext /// public MemoryBlock GetBlock(MemoryBlockReference blockReference) => GetBlockInternal(blockReference) ?? throw new Exception($"Failed to retrieve memory block with reference {blockReference.Id}"); + /// + /// Returns the pointed to by the specified memory block reference. + /// + public bool TryGetBlock(MemoryBlockReference blockReference, out MemoryBlock block) + { + var b = GetBlockInternal(blockReference); + block = b ?? default!; + return b != null; + } + /// /// Returns the value of the memory block pointed to by the specified memory block reference. /// @@ -69,6 +79,21 @@ public class ExpressionExecutionContext /// public object? Get(MemoryBlockReference blockReference) => GetBlock(blockReference).Value; + /// + /// Returns the value of the memory block pointed to by the specified memory block reference. + /// + public bool TryGet(MemoryBlockReference blockReference, out object? value) + { + if (TryGetBlock(blockReference, out var block)) + { + value = block.Value; + return true; + } + + value = default; + return false; + } + /// /// Returns the value of the memory block pointed to by the specified memory block reference. /// diff --git a/src/modules/Elsa.Expressions/Models/MemoryBlockReference.cs b/src/modules/Elsa.Expressions/Models/MemoryBlockReference.cs index b16ca9ea2..f2adb5c71 100644 --- a/src/modules/Elsa.Expressions/Models/MemoryBlockReference.cs +++ b/src/modules/Elsa.Expressions/Models/MemoryBlockReference.cs @@ -54,6 +54,11 @@ public abstract class MemoryBlockReference /// public T? Get(ExpressionExecutionContext context) => Get(context).ConvertTo(); + /// + /// Returns the value of the memory block. + /// + public bool TryGet(ExpressionExecutionContext context, out object? value) => context.TryGet(this, out value); + /// /// Sets the value of the memory block. /// diff --git a/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs b/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs index ee9d00301..daf49acbe 100644 --- a/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs +++ b/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs @@ -7,6 +7,7 @@ using Elsa.JavaScript.Contracts; using Elsa.JavaScript.Notifications; using Elsa.JavaScript.Options; using Elsa.Mediator.Contracts; +using Elsa.Workflows.Core.Models; using Humanizer; using Jint; using Microsoft.Extensions.Options; @@ -60,8 +61,8 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator engine.SetValue("setCorrelationId", (Action)(value => context.GetActivityExecutionContext().WorkflowExecutionContext.CorrelationId = value)); engine.SetValue("getCorrelationId", (Func)(() => context.GetActivityExecutionContext().WorkflowExecutionContext.CorrelationId)); engine.SetValue("setCorrelationId", (Action)(value => context.GetActivityExecutionContext().WorkflowExecutionContext.CorrelationId = value)); - engine.SetValue("setVariable", (Action)((name, value) => context.SetVariable(name, value))); - engine.SetValue("getVariable", (Func)(name => context.GetVariable(name))); + engine.SetValue("setVariable", (Action)((id, value) => context.SetVariable(id, value))); + engine.SetValue("getVariable", (Func)(id => context.GetVariable(id))); engine.SetValue("getInput", (Func)(name => context.GetWorkflowExecutionContext().Input.GetValue(name))); // Create variable & input setters and getters for each variable. @@ -89,16 +90,58 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator private static void CreateMemoryBlockAccessors(Engine engine, ExpressionExecutionContext context) { - var variablesDictionary = context.ReadAndFlattenMemoryBlocks(); + var variableNames = GetVariableNamesInScope(context).ToList(); - foreach (var variable in variablesDictionary) + foreach (var variableName in variableNames) { - var pascalName = variable.Key.Pascalize(); - engine.SetValue($"get{pascalName}", (Func)(() => context.GetVariable(variable.Key))); - engine.SetValue($"set{pascalName}", (Action)(value => context.SetVariable(variable.Key, value))); + var pascalName = variableName.Pascalize(); + engine.SetValue($"get{pascalName}", (Func)(() => GetVariableInScope(context, variableName))); + engine.SetValue($"set{pascalName}", (Action)(value => SetVariableInScope(context, variableName, value))); } } - + + private static IEnumerable GetVariableNamesInScope(ExpressionExecutionContext context) => EnumerateVariablesInScope(context).Select(x => x.Name).Distinct(); + + private static object GetVariableInScope(ExpressionExecutionContext context, string variableName) + { + var q = from variable in EnumerateVariablesInScope(context) + where variable.Name == variableName + where variable.TryGet(context, out _) + select variable.Get(context); + + var value = q.FirstOrDefault(); + return value!; + } + + private static void SetVariableInScope(ExpressionExecutionContext context, string variableName, object? value) + { + var q = from v in EnumerateVariablesInScope(context) + where v.Name == variableName + where v.TryGet(context, out _) + select v; + + var variable = q.FirstOrDefault(); + variable?.Set(context, value); + } + + private static IEnumerable EnumerateVariablesInScope(ExpressionExecutionContext context) + { + var currentScope = context; + + while (currentScope != null) + { + if (!currentScope.TryGetActivityExecutionContext(out var activityExecutionContext)) + break; + + var variables = activityExecutionContext.Variables; + + foreach (var variable in variables) + yield return variable; + + currentScope = currentScope.ParentContext; + } + } + private static object ExecuteExpressionAndGetResult(Engine engine, string expression) { var result = engine.Evaluate(expression); diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs index ac011419b..0ebf8afff 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs @@ -2,7 +2,9 @@ using System.Text.Json; using Elsa.Abstractions; using Elsa.Common.Models; using Elsa.Workflows.Api.Models; +using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Serialization; +using Elsa.Workflows.Core.Serialization.Converters; using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Mappers; using Elsa.Workflows.Runtime.Contracts; @@ -43,7 +45,6 @@ public class Export : ElsaEndpoint /// public override async Task HandleAsync(Request request, CancellationToken cancellationToken) { - var serializerOptions = _serializerOptionsProvider.CreateApiOptions(); var versionOptions = request.VersionOptions != null ? VersionOptions.FromString(request.VersionOptions) : VersionOptions.Latest; var definition = (await _store.FindManyAsync(new WorkflowDefinitionFilter { DefinitionId = request.DefinitionId, VersionOptions = versionOptions }, cancellationToken: cancellationToken)).FirstOrDefault(); @@ -73,6 +74,11 @@ public class Export : ElsaEndpoint definition.IsPublished, workflow.Root); + var serializerOptions = _serializerOptionsProvider.CreateApiOptions(); + + // Exclude composite activities from being serialized. + serializerOptions.Converters.Add(new JsonIgnoreCompositeRootConverterFactory()); + var binaryJson = JsonSerializer.SerializeToUtf8Bytes(model, serializerOptions); var hasWorkflowName = !string.IsNullOrWhiteSpace(definition.Name); var workflowName = hasWorkflowName ? definition.Name!.Trim() : definition.DefinitionId; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs index 673a2dcf2..75dfbf15e 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs @@ -30,7 +30,7 @@ internal class Get : ElsaEndpoint public override async Task HandleAsync(Request request, CancellationToken cancellationToken) { var versionOptions = request.VersionOptions != null ? VersionOptions.FromString(request.VersionOptions) : VersionOptions.Latest; - + var filter = new WorkflowDefinitionFilter { DefinitionId = request.DefinitionId, @@ -49,10 +49,10 @@ internal class Get : ElsaEndpoint var mapper = new WorkflowDefinitionMapper(); var response = await mapper.FromEntityAsync(definition, cancellationToken); var serializerOptions = _serializerOptionsProvider.CreateApiOptions(); - + // If the root of composite activities is not requested, exclude them from being serialized. - if(!request.IncludeCompositeRoot) - serializerOptions.Converters.Add(new JsonIgnoreCompositeRootConverterFactory()); + if (!request.IncludeCompositeRoot) + serializerOptions.Converters.Add(new JsonIgnoreCompositeRootConverterFactory()); await HttpContext.Response.WriteAsJsonAsync(response, serializerOptions, cancellationToken); } diff --git a/src/modules/Elsa.Workflows.Core/Builders/WorkflowBuilder.cs b/src/modules/Elsa.Workflows.Core/Builders/WorkflowBuilder.cs index 6979af2e6..e5bcab980 100644 --- a/src/modules/Elsa.Workflows.Core/Builders/WorkflowBuilder.cs +++ b/src/modules/Elsa.Workflows.Core/Builders/WorkflowBuilder.cs @@ -64,7 +64,12 @@ public class WorkflowBuilder : IWorkflowBuilder /// public Variable WithVariable(string name, T value) { - var variable = value != null ? new Variable(name, value) : new Variable(name); + var variable = new Variable + { + Name = name, + Value = value + }; + Variables.Add(variable); return variable; } diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs index 93b56617c..08a827f41 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs @@ -99,7 +99,7 @@ public static class ActivityExecutionContextExtensions public static Variable SetVariable(this ActivityExecutionContext context, string name, object? value, Type? storageDriverType = default, Action? configure = default) => context.ExpressionExecutionContext.SetVariable(name, value, storageDriverType, configure); - public static T? GetVariable(this ActivityExecutionContext context, string name) => context.ExpressionExecutionContext.GetVariable(name); + public static T? GetVariable(this ActivityExecutionContext context, string id) => context.ExpressionExecutionContext.GetVariable(id); /// /// Returns a dictionary of variable keys and their values across scopes. diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs index 3e8e72fd1..11a5bbb3e 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs @@ -29,6 +29,8 @@ public static class ExpressionExecutionContextExtensions public static WorkflowExecutionContext GetWorkflowExecutionContext(this ExpressionExecutionContext context) => (WorkflowExecutionContext)context.TransientProperties[WorkflowExecutionContextKey]; public static ActivityExecutionContext GetActivityExecutionContext(this ExpressionExecutionContext context) => (ActivityExecutionContext)context.TransientProperties[ActivityExecutionContextKey]; + public static bool TryGetActivityExecutionContext(this ExpressionExecutionContext context, out ActivityExecutionContext activityExecutionContext) => context.TransientProperties.TryGetValue(ActivityExecutionContextKey, out activityExecutionContext!); + public static IDictionary GetInput(this ExpressionExecutionContext context) => (IDictionary)context.TransientProperties[InputKey]; public static T? GetInput(this ExpressionExecutionContext context, string key) => context.GetInput(key).ConvertTo(); public static object? GetInput(this ExpressionExecutionContext context, string key) => context.GetInput().TryGetValue(key, out var value) ? value : default; @@ -36,15 +38,13 @@ public static class ExpressionExecutionContextExtensions public static T? Get(this ExpressionExecutionContext context, Input? input) => input != null ? context.GetBlock(input.MemoryBlockReference).Value.ConvertTo() : default; public static T? Get(this ExpressionExecutionContext context, Output output) => context.GetBlock(output.MemoryBlockReference).Value.ConvertTo(); public static object? Get(this ExpressionExecutionContext context, Output output) => context.GetBlock(output.MemoryBlockReference).Value; - public static T? GetVariable(this ExpressionExecutionContext context, string name) => (T?)context.GetVariable(name); - public static T? GetVariable(this ExpressionExecutionContext context) => context.GetVariable(typeof(T).Name).ConvertTo(); - public static object? GetVariable(this ExpressionExecutionContext context, string name) => new Variable(name).Get(context); - public static Variable SetVariable(this ExpressionExecutionContext context, T? value, Type? storageDriverType = default) => context.SetVariable(typeof(T).Name, value, storageDriverType); - public static Variable SetVariable(this ExpressionExecutionContext context, string name, T? value, Type? storageDriverType = default) => context.SetVariable(name, (object?)value, storageDriverType); + public static T? GetVariable(this ExpressionExecutionContext context, string id) => (T?)context.GetVariable(id); + public static object? GetVariable(this ExpressionExecutionContext context, string id) => new Variable(id).Get(context); + public static Variable SetVariable(this ExpressionExecutionContext context, string id, T? value, Type? storageDriverType = default) => context.SetVariable(id, (object?)value, storageDriverType, default); - public static Variable SetVariable(this ExpressionExecutionContext context, string name, object? value, Type? storageDriverType, Action? configure = default) + public static Variable SetVariable(this ExpressionExecutionContext context, string id, object? value, Type? storageDriverType, Action? configure = default) { - var variable = new Variable(name, value) + var variable = new Variable(id, value) { StorageDriverType = storageDriverType }; diff --git a/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs index c4c84b736..de3bac037 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs @@ -95,50 +95,4 @@ public static class WorkflowExecutionContextExtensions workflowExecutionContext.Scheduler.Schedule(workItem); workflowExecutionContext.AddCompletionCallback(owner, activityNode, completionCallback); } - - /// - /// Gets the specified workflow variable by name. - /// - public static T? GetVariable(this WorkflowExecutionContext workflowExecutionContext, string name) => (T?)workflowExecutionContext.GetVariable(name); - - /// - /// Gets the specified workflow variable by name, where the name is implied by the type name. - /// - public static T? GetVariable(this WorkflowExecutionContext workflowExecutionContext) => (T?)workflowExecutionContext.GetVariable(typeof(T).Name); - - /// - /// Gets the specified workflow variable by name. - /// - public static object? GetVariable(this WorkflowExecutionContext workflowExecutionContext, string name) - { - var variable = workflowExecutionContext.Workflow.Variables.FirstOrDefault(x => x.Name == name); - return variable?.Get(workflowExecutionContext.MemoryRegister); - } - - /// - /// Sets the specified workflow variable by name, where the name is implied by the type name. - /// - public static Variable SetVariable(this WorkflowExecutionContext workflowExecutionContext, T? value) => workflowExecutionContext.SetVariable(typeof(T).Name, value); - - /// - /// Sets the specified workflow variable by name. - /// - public static Variable SetVariable(this WorkflowExecutionContext workflowExecutionContext, string name, T? value) => workflowExecutionContext.SetVariable(name, (object?)value); - - /// - /// Sets the specified workflow variable by name. - /// - public static Variable SetVariable(this WorkflowExecutionContext workflowExecutionContext, string name, object? value) - { - var variable = workflowExecutionContext.Workflow.Variables.FirstOrDefault(x => x.Name == name); - - if (variable == null) - { - variable = new Variable(name, value); - workflowExecutionContext.Workflow.Variables.Add(variable); - } - - variable.Set(workflowExecutionContext.MemoryRegister, value); - return variable; - } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Models/Variable.cs b/src/modules/Elsa.Workflows.Core/Models/Variable.cs index 564e75092..8d2b31cd4 100644 --- a/src/modules/Elsa.Workflows.Core/Models/Variable.cs +++ b/src/modules/Elsa.Workflows.Core/Models/Variable.cs @@ -8,23 +8,20 @@ public class Variable : MemoryBlockReference { public Variable() { + Id = Guid.NewGuid().ToString("N"); } - public Variable(string name) + public Variable(string id) { - Id = name; + Id = id; } - public Variable(string name, object? value = default) : this(name) + public Variable(string id, object? value = default) : this(id) { Value = value; } - public string Name - { - get => Id; - set => Id = value; - } + public string Name { get; set; } public object? Value { get; set; } @@ -44,14 +41,6 @@ public class Variable : Variable { } - public Variable(string name) : base(name) - { - } - - public Variable(string name, T value) : base(name, value ?? default) - { - } - public Variable(T value) { Value = value; diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Converters/JsonIgnoreCompositeRootConverter.cs b/src/modules/Elsa.Workflows.Core/Serialization/Converters/JsonIgnoreCompositeRootConverter.cs index 4e5af9b64..db107393b 100644 --- a/src/modules/Elsa.Workflows.Core/Serialization/Converters/JsonIgnoreCompositeRootConverter.cs +++ b/src/modules/Elsa.Workflows.Core/Serialization/Converters/JsonIgnoreCompositeRootConverter.cs @@ -2,22 +2,23 @@ using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using Elsa.Workflows.Core.Attributes; +using Elsa.Workflows.Core.Contracts; namespace Elsa.Workflows.Core.Serialization.Converters; /// /// Ignores properties with the attribute. /// -public class JsonIgnoreCompositeRootConverter : JsonConverter +public class JsonIgnoreCompositeRootConverter : JsonConverter { /// - public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override IActivity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { throw new NotImplementedException(); } /// - public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, IActivity value, JsonSerializerOptions options) { writer.WriteStartObject(); @@ -39,19 +40,4 @@ public class JsonIgnoreCompositeRootConverter : JsonConverter writer.WriteEndObject(); } -} - -/// -/// A that creates instances. -/// -public class JsonIgnoreCompositeRootConverterFactory : JsonConverterFactory -{ - /// - public override bool CanConvert(Type typeToConvert) => typeof(T).IsAssignableFrom(typeToConvert); - - /// - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) - { - return new JsonIgnoreCompositeRootConverter(); - } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Converters/JsonIgnoreCompositeRootConverterFactory.cs b/src/modules/Elsa.Workflows.Core/Serialization/Converters/JsonIgnoreCompositeRootConverterFactory.cs new file mode 100644 index 000000000..19185ed80 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/Serialization/Converters/JsonIgnoreCompositeRootConverterFactory.cs @@ -0,0 +1,20 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Workflows.Core.Contracts; + +namespace Elsa.Workflows.Core.Serialization.Converters; + +/// +/// A that creates instances. +/// +public class JsonIgnoreCompositeRootConverterFactory : JsonConverterFactory +{ + /// + public override bool CanConvert(Type typeToConvert) => typeof(IActivity).IsAssignableFrom(typeToConvert); + + /// + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + return new JsonIgnoreCompositeRootConverter(); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Converters/PolymorphicObjectConverter.cs b/src/modules/Elsa.Workflows.Core/Serialization/Converters/PolymorphicObjectConverter.cs index 626e7856c..627eb0f08 100644 --- a/src/modules/Elsa.Workflows.Core/Serialization/Converters/PolymorphicObjectConverter.cs +++ b/src/modules/Elsa.Workflows.Core/Serialization/Converters/PolymorphicObjectConverter.cs @@ -47,12 +47,23 @@ public class PolymorphicObjectConverter : JsonConverter if(jsonObject.TryGetProperty(ItemsPropertyName, out var items)) { - var array = JsonSerializer.Deserialize(items.GetRawText(), targetType, newOptions)!; + var elementType = targetType.GetElementType()!; + var array = Array.CreateInstance(elementType, items.GetArrayLength()); + var index = 0; + + newOptions.Converters.Add(this); + + foreach (var element in items.EnumerateArray()) + { + var deserializedElement = JsonSerializer.Deserialize(element.GetRawText(), elementType, newOptions)!; + array.SetValue(deserializedElement, index++); + } return array; } var json = jsonObject.GetRawText(); - return JsonSerializer.Deserialize(json, targetType, newOptions)!; + var result = JsonSerializer.Deserialize(json, targetType, newOptions)!; + return result; } private static object ReadPrimitive(ref Utf8JsonReader reader, JsonSerializerOptions options) diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Converters/VariableConverter.cs b/src/modules/Elsa.Workflows.Core/Serialization/Converters/VariableConverter.cs index 59bb353ee..c577eed53 100644 --- a/src/modules/Elsa.Workflows.Core/Serialization/Converters/VariableConverter.cs +++ b/src/modules/Elsa.Workflows.Core/Serialization/Converters/VariableConverter.cs @@ -53,6 +53,7 @@ public class VariableConverter : JsonConverter var variableGenericType = typeof(Variable<>).MakeGenericType(type); var variable = (Variable)Activator.CreateInstance(variableGenericType)!; + variable.Id = source.Id ?? Guid.NewGuid().ToString("N"); // Temporarily assign a new ID if the source doesn't have one. variable.Name = source.Name; source.Value.TryConvertTo(type) @@ -73,7 +74,7 @@ public class VariableConverter : JsonConverter var storageDriverTypeName = source.StorageDriverType?.GetSimpleAssemblyQualifiedName(); var serializedValue = value.Format(); - return new VariableModel(source.Name, valueTypeAlias, serializedValue, storageDriverTypeName); + return new VariableModel(source.Id, source.Name, valueTypeAlias, serializedValue, storageDriverTypeName); } private class VariableModel @@ -83,14 +84,16 @@ public class VariableConverter : JsonConverter { } - public VariableModel(string name, string typeName, string? value, string? storageDriverTypeName) + public VariableModel(string id, string name, string typeName, string? value, string? storageDriverTypeName) { + Id = id; Name = name; TypeName = typeName; Value = value; StorageDriverTypeName = storageDriverTypeName; } + public string Id { get; set; } = default!; public string Name { get; set; } = default!; public string TypeName { get; set; } = default!; public string? Value { get; set; } diff --git a/src/modules/Elsa.Workflows.Core/Serialization/SerializerOptionsProvider.cs b/src/modules/Elsa.Workflows.Core/Serialization/SerializerOptionsProvider.cs index b90ebfaef..fc775fc9d 100644 --- a/src/modules/Elsa.Workflows.Core/Serialization/SerializerOptionsProvider.cs +++ b/src/modules/Elsa.Workflows.Core/Serialization/SerializerOptionsProvider.cs @@ -23,7 +23,7 @@ public class SerializerOptionsProvider public JsonSerializerOptions CreatePersistenceOptions(ReferenceHandler? referenceHandler = default) { var options = CreateDefaultOptions(referenceHandler ?? ReferenceHandler.IgnoreCycles); - options.Converters.Add(Create>()); + options.Converters.Add(Create()); return options; } diff --git a/src/modules/Elsa.Workflows.Core/Services/IdentityGraphService.cs b/src/modules/Elsa.Workflows.Core/Services/IdentityGraphService.cs index 2a33e7a80..26e3d1f53 100644 --- a/src/modules/Elsa.Workflows.Core/Services/IdentityGraphService.cs +++ b/src/modules/Elsa.Workflows.Core/Services/IdentityGraphService.cs @@ -87,7 +87,7 @@ public class IdentityGraphService : IIdentityGraphService var seed = 0; foreach (var variable in variables) - variable.Id = variable.Name != null! ? variable.Name : $"{activity.Id}:variable-{++seed}"; + variable.Id = variable.Id != null! ? variable.Id : $"{activity.Id}:variable-{++seed}"; } private string CreateId(ActivityNode activityNode, IDictionary identityCounters, ICollection allNodes) diff --git a/src/modules/Elsa.Workflows.Management/Activities/WorkflowDefinitionActivity/WorkflowDefinitionActivity.cs b/src/modules/Elsa.Workflows.Management/Activities/WorkflowDefinitionActivity/WorkflowDefinitionActivity.cs index a79fc47cc..407da6f25 100644 --- a/src/modules/Elsa.Workflows.Management/Activities/WorkflowDefinitionActivity/WorkflowDefinitionActivity.cs +++ b/src/modules/Elsa.Workflows.Management/Activities/WorkflowDefinitionActivity/WorkflowDefinitionActivity.cs @@ -42,8 +42,10 @@ public class WorkflowDefinitionActivity : Composite, IInitializable var evaluatedExpression = input != null ? context.Get(input.MemoryBlockReference()) : default; // Create a local scope variable for each input property. - var variable = new Variable(inputDescriptor.Name) + var variable = new Variable { + Id = inputDescriptor.Name, + Name = inputDescriptor.Name, StorageDriverType = inputDescriptor.StorageDriverType }; @@ -62,6 +64,7 @@ public class WorkflowDefinitionActivity : Composite, IInitializable // Create a local scope variable for each input property. var variable = new Variable(inputDescriptor.Name) { + Name = inputDescriptor.Name, StorageDriverType = inputDescriptor.StorageDriverType }; diff --git a/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs b/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs index ef8c6bb5d..e0ed251b6 100644 --- a/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs +++ b/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs @@ -34,6 +34,9 @@ public class VariableDefinitionMapper var variableGenericType = typeof(Variable<>).MakeGenericType(valueType); var variable = (Variable)Activator.CreateInstance(variableGenericType)!; + if(!string.IsNullOrEmpty(source.Id)) + variable.Id = source.Id; + variable.Name = source.Name; variable.Value = source.Value.ConvertTo(valueType); variable.StorageDriverType = !string.IsNullOrEmpty(source.StorageDriverTypeName) ? Type.GetType(source.StorageDriverTypeName) : default; @@ -66,7 +69,7 @@ public class VariableDefinitionMapper var storageDriverTypeName = source.StorageDriverType?.GetSimpleAssemblyQualifiedName(); var serializedValue = value.Format(); - return new VariableDefinition(source.Name, valueTypeAlias, isArray, serializedValue, storageDriverTypeName); + return new VariableDefinition(source.Id, source.Name, valueTypeAlias, isArray, serializedValue, storageDriverTypeName); } /// diff --git a/src/modules/Elsa.Workflows.Management/Models/VariableDefinition.cs b/src/modules/Elsa.Workflows.Management/Models/VariableDefinition.cs index 3173fdc3e..289f6de76 100644 --- a/src/modules/Elsa.Workflows.Management/Models/VariableDefinition.cs +++ b/src/modules/Elsa.Workflows.Management/Models/VariableDefinition.cs @@ -3,4 +3,4 @@ namespace Elsa.Workflows.Management.Models; /// /// Stores information about a workflow variable. /// -public record VariableDefinition(string Name, string TypeName, bool IsArray, string? Value, string? StorageDriverTypeName); \ No newline at end of file +public record VariableDefinition(string Id, string Name, string TypeName, bool IsArray, string? Value, string? StorageDriverTypeName); \ 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 116290492..f8ef7d49e 100644 --- a/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverter.cs +++ b/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverter.cs @@ -108,7 +108,12 @@ public class ActivityJsonConverter : JsonConverter if (!memoryReferenceElement.TryGetProperty("id", out var memoryReferenceIdElement)) continue; - var variable = new Variable(memoryReferenceIdElement.GetString()!); + var variable = new Variable + { + Id = memoryReferenceIdElement.GetString()! + }; + variable.Name = variable.Id; + var output = Activator.CreateInstance(wrappedType, variable)!; activity.SyntheticProperties[outputName] = output!; diff --git a/src/modules/Elsa.Workflows.Management/Serialization/Converters/OutputJsonConverter.cs b/src/modules/Elsa.Workflows.Management/Serialization/Converters/OutputJsonConverter.cs index ac5fc3c58..560cd98d1 100644 --- a/src/modules/Elsa.Workflows.Management/Serialization/Converters/OutputJsonConverter.cs +++ b/src/modules/Elsa.Workflows.Management/Serialization/Converters/OutputJsonConverter.cs @@ -34,7 +34,12 @@ public class OutputJsonConverter : JsonConverter?> if (!memoryReferenceElement.TryGetProperty("id", out var memoryReferenceIdElement)) return default; - var variable = new Variable(memoryReferenceIdElement.GetString()!); + var variable = new Variable + { + Id = memoryReferenceIdElement.GetString()! + }; + variable.Name = variable.Id; + return (Output)Activator.CreateInstance(typeof(Output), variable)!; } diff --git a/src/samples/console/Elsa.Samples.Composition/Activities/AskName.cs b/src/samples/console/Elsa.Samples.Composition/Activities/AskName.cs index 1daec4c43..b628d95ac 100644 --- a/src/samples/console/Elsa.Samples.Composition/Activities/AskName.cs +++ b/src/samples/console/Elsa.Samples.Composition/Activities/AskName.cs @@ -14,7 +14,7 @@ public class AskName : Composite Root = new Sequence { Variables = new List { _name }, - Activities = new List() + Activities = new List { new WriteLine(context => Prompt.Get(context)), new ReadLine(_name) diff --git a/test/integration/Elsa.IntegrationTests/Activities/Break/BreakWhileForkWorkflow.cs b/test/integration/Elsa.IntegrationTests/Activities/Break/BreakWhileForkWorkflow.cs index 9f31fb1f1..ecc0ad554 100644 --- a/test/integration/Elsa.IntegrationTests/Activities/Break/BreakWhileForkWorkflow.cs +++ b/test/integration/Elsa.IntegrationTests/Activities/Break/BreakWhileForkWorkflow.cs @@ -10,7 +10,11 @@ public class BreakWhileForkWorkflow : WorkflowBase { protected override void Build(IWorkflowBuilder workflow) { - var currentValue = new Variable("CurrentValue", 0); + var currentValue = new Variable + { + Name = "CurrentValue", + Value = 0 + }; workflow.Root = new Sequence { diff --git a/test/integration/Elsa.IntegrationTests/Activities/ForEach/ForEachWorkflow.cs b/test/integration/Elsa.IntegrationTests/Activities/ForEach/ForEachWorkflow.cs index 428766324..7796035ea 100644 --- a/test/integration/Elsa.IntegrationTests/Activities/ForEach/ForEachWorkflow.cs +++ b/test/integration/Elsa.IntegrationTests/Activities/ForEach/ForEachWorkflow.cs @@ -17,7 +17,10 @@ class ForEachWorkflow : WorkflowBase protected override void Build(IWorkflowBuilder workflow) { - var currentItem = new Variable("CurrentItem"); + var currentItem = new Variable + { + Name = "CurrentItem" + }; workflow.Root = new Sequence {