diff --git a/src/designer/elsa-workflows-designer/src/components/inputs/check-list.tsx b/src/designer/elsa-workflows-designer/src/components/inputs/check-list.tsx index 6e5a41eb6..c87a8b764 100644 --- a/src/designer/elsa-workflows-designer/src/components/inputs/check-list.tsx +++ b/src/designer/elsa-workflows-designer/src/components/inputs/check-list.tsx @@ -1,6 +1,6 @@ import {Component, h, Prop, State} from '@stencil/core'; import {uniq} from 'lodash' -import {JsonExpression, LiteralExpression, SelectList, SyntaxNames} from "../../models"; +import {ObjectExpression, SelectList, SyntaxNames} from "../../models"; import {ActivityInputContext} from "../../services/activity-input-driver"; import {getSelectListItems, getInputPropertyValue, parseJson} from "../../utils"; import {ExpressionChangedArs} from "../shared/input-control-switch/input-control-switch"; @@ -24,6 +24,8 @@ export class CheckList { this.selectedValues = selectedValues; else if (typeof (selectedValues) == 'number') this.selectedValue = selectedValues; + else if (typeof selectedValues == 'string') + this.selectedValues = JSON.parse(selectedValues); } public render() { @@ -34,7 +36,7 @@ export class CheckList { const displayName = inputDescriptor.displayName; const hint = inputDescriptor.description; const input = getInputPropertyValue(inputContext); - const value = (input?.expression as JsonExpression)?.value; // TODO: The "value" field is currently hardcoded, but we should be able to be more flexible and potentially have different fields for a given syntax. + const value = (input?.expression as ObjectExpression)?.value; // TODO: The "value" field is currently hardcoded, but we should be able to be more flexible and potentially have different fields for a given syntax. const syntax = input?.expression?.type ?? inputDescriptor.defaultSyntax; const selectList = this.selectList; @@ -70,7 +72,7 @@ export class CheckList { private getSelectedValues = (selectList: SelectList): number | Array => { const input = getInputPropertyValue(this.inputContext); - const json = (input?.expression as JsonExpression)?.value; + const json = (input?.expression as ObjectExpression)?.value; return selectList.isFlagsEnum ? parseInt(json) : parseJson(json) || []; }; @@ -103,7 +105,7 @@ export class CheckList { json = JSON.stringify(newValue); } - this.inputContext.inputChanged(json, SyntaxNames.Json); + this.inputContext.inputChanged(json, SyntaxNames.Object); } private onExpressionChanged = (e: CustomEvent) => { diff --git a/src/designer/elsa-workflows-designer/src/components/inputs/multi-text.tsx b/src/designer/elsa-workflows-designer/src/components/inputs/multi-text.tsx index b58118776..a2a6096a0 100644 --- a/src/designer/elsa-workflows-designer/src/components/inputs/multi-text.tsx +++ b/src/designer/elsa-workflows-designer/src/components/inputs/multi-text.tsx @@ -1,5 +1,5 @@ import {Component, Prop, h} from '@stencil/core'; -import {JsonExpression, LiteralExpression, SyntaxNames} from "../../models"; +import {ObjectExpression, LiteralExpression, SyntaxNames} from "../../models"; import {ActivityInputContext} from "../../services/activity-input-driver"; import {getInputPropertyValue, parseJson} from "../../utils"; import {ExpressionChangedArs} from "../shared/input-control-switch/input-control-switch"; @@ -19,7 +19,7 @@ export class MultiTextInput { const hint = inputDescriptor.description; const input = getInputPropertyValue(inputContext); const syntax = input?.expression?.type ?? inputDescriptor.defaultSyntax; - const json = (input?.expression as JsonExpression)?.value; + const json = (input?.expression as ObjectExpression)?.value; const values = parseJson(json); return ( @@ -31,7 +31,7 @@ export class MultiTextInput { private onPropertyEditorChanged = (e: CustomEvent>) => { const json = JSON.stringify(e.detail); - this.inputContext.inputChanged(json, SyntaxNames.Json); + this.inputContext.inputChanged(json, SyntaxNames.Object); }; private onExpressionChanged = (e: CustomEvent) => { diff --git a/src/designer/elsa-workflows-designer/src/components/inputs/outcome-picker.tsx b/src/designer/elsa-workflows-designer/src/components/inputs/outcome-picker.tsx index b4f44c8f3..d752de127 100644 --- a/src/designer/elsa-workflows-designer/src/components/inputs/outcome-picker.tsx +++ b/src/designer/elsa-workflows-designer/src/components/inputs/outcome-picker.tsx @@ -1,5 +1,5 @@ import {Component, Prop, h} from '@stencil/core'; -import {JsonExpression, LiteralExpression, SyntaxNames, Variable} from "../../models"; +import {ObjectExpression, LiteralExpression, SyntaxNames, Variable} from "../../models"; import {ActivityInputContext} from "../../services/activity-input-driver"; import {getInputPropertyValue, getPropertyValue} from "../../utils"; import {FormEntry} from "../shared/forms/form-entry"; @@ -22,7 +22,7 @@ export class OutcomePicker { const displayName = inputDescriptor.displayName; const description = inputDescriptor.description; const input = getInputPropertyValue(inputContext); - const value = (input?.expression as JsonExpression)?.value; + const value = (input?.expression as ObjectExpression)?.value; const syntax = input?.expression?.type ?? inputDescriptor.defaultSyntax; return ( @@ -51,6 +51,6 @@ export class OutcomePicker { private onChange = (e: Event) => { const inputElement = e.target as HTMLSelectElement; const outcome = inputElement.value; - this.inputContext.inputChanged(outcome, SyntaxNames.Json); + this.inputContext.inputChanged(outcome, SyntaxNames.Object); } } diff --git a/src/designer/elsa-workflows-designer/src/models/core.ts b/src/designer/elsa-workflows-designer/src/models/core.ts index f54aa6a12..7a04278e6 100644 --- a/src/designer/elsa-workflows-designer/src/models/core.ts +++ b/src/designer/elsa-workflows-designer/src/models/core.ts @@ -155,7 +155,7 @@ export enum SyntaxNames { Literal = 'Literal', JavaScript = 'JavaScript', Liquid = 'Liquid', - Json = 'Json' + Object = 'Object' } export interface ActivityMetadata { diff --git a/src/designer/elsa-workflows-designer/src/models/expressions.ts b/src/designer/elsa-workflows-designer/src/models/expressions.ts index ac0bea6b5..3f636dbbb 100644 --- a/src/designer/elsa-workflows-designer/src/models/expressions.ts +++ b/src/designer/elsa-workflows-designer/src/models/expressions.ts @@ -1,5 +1,5 @@ export type ExpressionType = string; -export type Expression = LiteralExpression | JavaScriptExpression | JsonExpression; +export type Expression = LiteralExpression | JavaScriptExpression | ObjectExpression; export interface LiteralExpression { type: ExpressionType; @@ -11,7 +11,7 @@ export interface JavaScriptExpression { value: string; } -export interface JsonExpression { +export interface ObjectExpression { type: ExpressionType; value: string; } diff --git a/src/designer/elsa-workflows-designer/src/modules/http-request/flow/flow-http-request-port-provider.ts b/src/designer/elsa-workflows-designer/src/modules/http-request/flow/flow-http-request-port-provider.ts index ef81eb6d8..c7ae21aed 100644 --- a/src/designer/elsa-workflows-designer/src/modules/http-request/flow/flow-http-request-port-provider.ts +++ b/src/designer/elsa-workflows-designer/src/modules/http-request/flow/flow-http-request-port-provider.ts @@ -1,6 +1,6 @@ import 'reflect-metadata'; import {Service} from "typedi"; -import {Activity, ActivityInput, InputDescriptor, JsonExpression, Port, PortMode} from "../../../models"; +import {Activity, ActivityInput, InputDescriptor, ObjectExpression, Port, PortMode} from "../../../models"; import {PortProvider, PortProviderContext} from "../../../services"; import {FlowSendHttpRequest} from "./models"; @@ -18,7 +18,7 @@ export class FlowHttpRequestPortProvider implements PortProvider { if(!expectedStatusCodes) return []; - const statusCodesJson = (expectedStatusCodes.expression as JsonExpression).value; + const statusCodesJson = (expectedStatusCodes.expression as ObjectExpression).value; const statusCodes = JSON.parse(statusCodesJson) as Array; const catchAllPort = {name: 'Unmatched status code', displayName: 'Unmatched status code', mode: PortMode.Port}; const outcomes = [...statusCodes.map(x => ({name: x.toString(), displayName: x.toString(), mode: PortMode.Port})), catchAllPort]; diff --git a/src/modules/Elsa.Expressions/Contracts/IExpressionSyntaxProvider.cs b/src/modules/Elsa.Expressions/Contracts/IExpressionSyntaxProvider.cs index 1e0bf1556..e1d87fdcd 100644 --- a/src/modules/Elsa.Expressions/Contracts/IExpressionSyntaxProvider.cs +++ b/src/modules/Elsa.Expressions/Contracts/IExpressionSyntaxProvider.cs @@ -2,7 +2,13 @@ using Elsa.Expressions.Models; namespace Elsa.Expressions.Contracts; +/// +/// Provides descriptors for expression syntaxes. +/// public interface IExpressionSyntaxProvider { + /// + /// Gets the descriptors for the expression syntaxes supported by this provider. + /// ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default); } \ 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 9f46d4973..e08f6f72a 100644 --- a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs +++ b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs @@ -48,7 +48,7 @@ public static class ObjectConverter if (sourceType == targetType) return value; - var options = converterOptions?.SerializerOptions ?? new JsonSerializerOptions(); + var options = converterOptions?.SerializerOptions != null ? new JsonSerializerOptions(converterOptions.SerializerOptions) : new JsonSerializerOptions(); options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; options.ReferenceHandler = ReferenceHandler.Preserve; options.PropertyNameCaseInsensitive = true; diff --git a/src/modules/Elsa.Expressions/Models/JsonLiteral.cs b/src/modules/Elsa.Expressions/Models/ObjectLiteral.cs similarity index 69% rename from src/modules/Elsa.Expressions/Models/JsonLiteral.cs rename to src/modules/Elsa.Expressions/Models/ObjectLiteral.cs index 60b5e0979..c37812640 100644 --- a/src/modules/Elsa.Expressions/Models/JsonLiteral.cs +++ b/src/modules/Elsa.Expressions/Models/ObjectLiteral.cs @@ -6,16 +6,16 @@ namespace Elsa.Expressions.Models; /// /// Represents a literal JSON expression. /// -public class JsonLiteral : MemoryBlockReference +public class ObjectLiteral : MemoryBlockReference { /// [JsonConstructor] - public JsonLiteral() + public ObjectLiteral() { } /// - public JsonLiteral(string? value) + public ObjectLiteral(string? value) { Value = value; } @@ -29,26 +29,26 @@ public class JsonLiteral : MemoryBlockReference public override MemoryBlock Declare() => new(); /// - /// Serializes the value into a JSON string in the form of a + /// Serializes the value into a JSON string in the form of a /// /// /// /// - public static JsonLiteral From(T value) => new JsonLiteral(value); + public static ObjectLiteral From(T value) => new ObjectLiteral(value); } /// /// Represents a JSON string for the specified type T /// -public class JsonLiteral : JsonLiteral +public class ObjectLiteral : ObjectLiteral { /// - public JsonLiteral() + public ObjectLiteral() { } /// - public JsonLiteral(T value) : base(JsonSerializer.Serialize(value!)) + public ObjectLiteral(T value) : base(JsonSerializer.Serialize(value!)) { } } \ No newline at end of file diff --git a/src/modules/Elsa.Http/Activities/HttpEndpoint.cs b/src/modules/Elsa.Http/Activities/HttpEndpoint.cs index ab0fa9fd2..058e3f61a 100644 --- a/src/modules/Elsa.Http/Activities/HttpEndpoint.cs +++ b/src/modules/Elsa.Http/Activities/HttpEndpoint.cs @@ -41,7 +41,7 @@ public class HttpEndpoint : Trigger Description = "The HTTP methods to accept.", Options = new[] { "GET", "POST", "PUT", "HEAD", "DELETE" }, UIHint = InputUIHints.CheckList)] - public Input> SupportedMethods { get; set; } = new(JsonLiteral.From(new[] { HttpMethods.Get })); + public Input> SupportedMethods { get; set; } = new(ObjectLiteral.From(new[] { HttpMethods.Get })); /// /// Allow authenticated requests only. diff --git a/src/modules/Elsa.Workflows.Core/Activities/Complete.cs b/src/modules/Elsa.Workflows.Core/Activities/Complete.cs index d72e6275e..2c69c3223 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/Complete.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/Complete.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using System.Text.Json; using System.Text.Json.Serialization; using Elsa.Expressions.Models; using Elsa.Extensions; @@ -51,7 +52,7 @@ public class Complete : Activity [Input( Description = "The outcome or set of outcomes to complete this activity with.", UIHint = InputUIHints.OutcomePicker, - DefaultSyntax = "Json" + DefaultSyntax = "Object" )] public Input Outcomes { get; set; } = default!; @@ -83,6 +84,19 @@ public class Complete : Activity yield return outcome.ToString()!; break; } + case JsonElement jsonElement: + { + if (jsonElement.ValueKind == JsonValueKind.Array) + { + var outcomeArray = jsonElement.EnumerateArray().ToList(); + foreach (var element in outcomeArray) + yield return element.ToString(); + } + else + yield return jsonElement.ToString(); + + break; + } default: yield return "Done"; break; diff --git a/src/modules/Elsa.Workflows.Core/Expressions/JsonExpression.cs b/src/modules/Elsa.Workflows.Core/Expressions/ObjectExpression.cs similarity index 57% rename from src/modules/Elsa.Workflows.Core/Expressions/JsonExpression.cs rename to src/modules/Elsa.Workflows.Core/Expressions/ObjectExpression.cs index 27c296f9c..078289c70 100644 --- a/src/modules/Elsa.Workflows.Core/Expressions/JsonExpression.cs +++ b/src/modules/Elsa.Workflows.Core/Expressions/ObjectExpression.cs @@ -1,29 +1,30 @@ using System.Text.Json; using Elsa.Expressions.Contracts; +using Elsa.Expressions.Helpers; using Elsa.Expressions.Models; using Elsa.Workflows.Core.Serialization.Converters; namespace Elsa.Workflows.Core.Expressions; -public class JsonExpression : IExpression +public class ObjectExpression : IExpression { - public JsonExpression(string? value) => Value = value; + public ObjectExpression(string? value) => Value = value; public string? Value { get; } } -public class JsonExpression : JsonExpression +public class ObjectExpression : ObjectExpression { - public JsonExpression(T? value) : base(JsonSerializer.Serialize(value)) + public ObjectExpression(T? value) : base(JsonSerializer.Serialize(value)) { } } -public class JsonExpressionHandler : IExpressionHandler +public class ObjectExpressionHandler : IExpressionHandler { /// public ValueTask EvaluateAsync(IExpression expression, Type returnType, ExpressionExecutionContext context) { - var jsonExpression = (JsonExpression)expression; + var jsonExpression = (ObjectExpression)expression; var value = jsonExpression.Value; if (string.IsNullOrWhiteSpace(value)) @@ -32,7 +33,8 @@ public class JsonExpressionHandler : IExpressionHandler var serializerOptions = new JsonSerializerOptions(); serializerOptions.Converters.Add(new IntegerConverter()); - var model = JsonSerializer.Deserialize(value, returnType, serializerOptions); + var converterOptions = new ObjectConverterOptions(serializerOptions); + var model = value.ConvertTo(returnType, converterOptions); return ValueTask.FromResult(model); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Features/WorkflowsFeature.cs b/src/modules/Elsa.Workflows.Core/Features/WorkflowsFeature.cs index d425316c5..83ea4db54 100644 --- a/src/modules/Elsa.Workflows.Core/Features/WorkflowsFeature.cs +++ b/src/modules/Elsa.Workflows.Core/Features/WorkflowsFeature.cs @@ -155,7 +155,7 @@ public class WorkflowsFeature : FeatureBase .AddExpressionHandler() .AddExpressionHandler() .AddExpressionHandler() - .AddExpressionHandler() + .AddExpressionHandler() .AddExpressionHandler() .AddExpressionHandler(); } diff --git a/src/modules/Elsa.Workflows.Core/Models/Input.cs b/src/modules/Elsa.Workflows.Core/Models/Input.cs index f5475cf42..3e4aaf38f 100644 --- a/src/modules/Elsa.Workflows.Core/Models/Input.cs +++ b/src/modules/Elsa.Workflows.Core/Models/Input.cs @@ -74,12 +74,12 @@ public class Input : Input } /// - public Input(JsonLiteral literal) : base(new JsonExpression(literal.Value), literal, typeof(T)) + public Input(ObjectLiteral literal) : base(new ObjectExpression(literal.Value), literal, typeof(T)) { } /// - public Input(JsonLiteral literal) : base(new JsonExpression(literal.Value), literal, typeof(T)) + public Input(ObjectLiteral literal) : base(new ObjectExpression(literal.Value), literal, typeof(T)) { } diff --git a/src/modules/Elsa.Workflows.Management/Providers/DefaultExpressionSyntaxProvider.cs b/src/modules/Elsa.Workflows.Management/Providers/DefaultExpressionSyntaxProvider.cs index 93e69ce1b..fc5674ea0 100644 --- a/src/modules/Elsa.Workflows.Management/Providers/DefaultExpressionSyntaxProvider.cs +++ b/src/modules/Elsa.Workflows.Management/Providers/DefaultExpressionSyntaxProvider.cs @@ -7,43 +7,57 @@ using Elsa.Workflows.Core.Services; namespace Elsa.Workflows.Management.Providers; +/// public class DefaultExpressionSyntaxProvider : IExpressionSyntaxProvider { private readonly IIdentityGenerator _identityGenerator; + /// + /// Initializes a new instance of the class. + /// public DefaultExpressionSyntaxProvider(IIdentityGenerator identityGenerator) { _identityGenerator = identityGenerator; } + /// public ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default) { var literal = CreateLiteralDescriptor(); + var @object = CreateObjectDescriptor(); var json = CreateJsonDescriptor(); var @delegate = CreateDelegateDescriptor(); - return ValueTask.FromResult>(new[] { literal, json, @delegate }); + return ValueTask.FromResult>(new[] { literal, @object, json, @delegate }); } - private ExpressionSyntaxDescriptor CreateLiteralDescriptor() => CreateDescriptor( + private ExpressionSyntaxDescriptor CreateLiteralDescriptor() => DefaultExpressionSyntaxProvider.CreateDescriptor( "Literal", CreateLiteralExpression, context => new Literal(context.GetExpression().Value), expression => expression.Value); - private ExpressionSyntaxDescriptor CreateJsonDescriptor() => CreateDescriptor( + private ExpressionSyntaxDescriptor CreateObjectDescriptor() => DefaultExpressionSyntaxProvider.CreateDescriptor( + "Object", + CreateObjectExpression, + context => new ObjectLiteral(context.GetExpression().Value), + expression => expression.Value); + + // TODO: this is replaced by the above and exists only for existing workflow definitions. To be removed in a future version. + [Obsolete] + private ExpressionSyntaxDescriptor CreateJsonDescriptor() => DefaultExpressionSyntaxProvider.CreateDescriptor( "Json", - CreateJsonExpression, - context => new JsonLiteral(context.GetExpression().Value), + CreateObjectExpression, + context => new ObjectLiteral(context.GetExpression().Value), expression => expression.Value); - private ExpressionSyntaxDescriptor CreateDelegateDescriptor() => CreateDescriptor( + private ExpressionSyntaxDescriptor CreateDelegateDescriptor() => DefaultExpressionSyntaxProvider.CreateDescriptor( "Delegate", - CreateJsonExpression, + CreateObjectExpression, context => new DelegateBlockReference(), expression => expression.DelegateBlockReference.Delegate?.ToString()); - private ExpressionSyntaxDescriptor CreateDescriptor( + private static ExpressionSyntaxDescriptor CreateDescriptor( string syntax, Func constructor, Func createBlockReference, @@ -61,17 +75,17 @@ public class DefaultExpressionSyntaxProvider : IExpressionSyntaxProvider } }; - private IExpression CreateLiteralExpression(ExpressionConstructorContext context) + private static IExpression CreateLiteralExpression(ExpressionConstructorContext context) { return !context.Element.TryGetProperty("value", out var expressionValue) ? new LiteralExpression() : new LiteralExpression(expressionValue.ToString()); } - private IExpression CreateJsonExpression(ExpressionConstructorContext context) + private IExpression CreateObjectExpression(ExpressionConstructorContext context) { var expressionValue = context.Element.GetProperty("value").ToString(); - return new JsonExpression(expressionValue); + return new ObjectExpression(expressionValue); } private string GenerateId() => _identityGenerator.GenerateId();