Add support for object literals to Outcome activity

This commit is contained in:
Sipke Schoorstra 2023-03-17 21:11:55 +01:00
parent c56f27c580
commit a9e67b9b1c
15 changed files with 85 additions and 47 deletions

View file

@ -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<string> => {
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<ExpressionChangedArs>) => {

View file

@ -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<Array<string>>) => {
const json = JSON.stringify(e.detail);
this.inputContext.inputChanged(json, SyntaxNames.Json);
this.inputContext.inputChanged(json, SyntaxNames.Object);
};
private onExpressionChanged = (e: CustomEvent<ExpressionChangedArs>) => {

View file

@ -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);
}
}

View file

@ -155,7 +155,7 @@ export enum SyntaxNames {
Literal = 'Literal',
JavaScript = 'JavaScript',
Liquid = 'Liquid',
Json = 'Json'
Object = 'Object'
}
export interface ActivityMetadata {

View file

@ -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;
}

View file

@ -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<string>;
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];

View file

@ -2,7 +2,13 @@ using Elsa.Expressions.Models;
namespace Elsa.Expressions.Contracts;
/// <summary>
/// Provides descriptors for expression syntaxes.
/// </summary>
public interface IExpressionSyntaxProvider
{
/// <summary>
/// Gets the descriptors for the expression syntaxes supported by this provider.
/// </summary>
ValueTask<IEnumerable<ExpressionSyntaxDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default);
}

View file

@ -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;

View file

@ -6,16 +6,16 @@ namespace Elsa.Expressions.Models;
/// <summary>
/// Represents a literal JSON expression.
/// </summary>
public class JsonLiteral : MemoryBlockReference
public class ObjectLiteral : MemoryBlockReference
{
/// <inheritdoc />
[JsonConstructor]
public JsonLiteral()
public ObjectLiteral()
{
}
/// <inheritdoc />
public JsonLiteral(string? value)
public ObjectLiteral(string? value)
{
Value = value;
}
@ -29,26 +29,26 @@ public class JsonLiteral : MemoryBlockReference
public override MemoryBlock Declare() => new();
/// <summary>
/// Serializes the value into a JSON string in the form of a <see cref="JsonLiteral{T}"/>
/// Serializes the value into a JSON string in the form of a <see cref="ObjectLiteral{T}"/>
/// </summary>
/// <param name="value"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static JsonLiteral From<T>(T value) => new JsonLiteral<T>(value);
public static ObjectLiteral From<T>(T value) => new ObjectLiteral<T>(value);
}
/// <summary>
/// Represents a JSON string for the specified type <code>T</code>
/// </summary>
public class JsonLiteral<T> : JsonLiteral
public class ObjectLiteral<T> : ObjectLiteral
{
/// <inheritdoc />
public JsonLiteral()
public ObjectLiteral()
{
}
/// <inheritdoc />
public JsonLiteral(T value) : base(JsonSerializer.Serialize(value!))
public ObjectLiteral(T value) : base(JsonSerializer.Serialize(value!))
{
}
}

View file

@ -41,7 +41,7 @@ public class HttpEndpoint : Trigger<HttpRequest>
Description = "The HTTP methods to accept.",
Options = new[] { "GET", "POST", "PUT", "HEAD", "DELETE" },
UIHint = InputUIHints.CheckList)]
public Input<ICollection<string>> SupportedMethods { get; set; } = new(JsonLiteral.From(new[] { HttpMethods.Get }));
public Input<ICollection<string>> SupportedMethods { get; set; } = new(ObjectLiteral.From(new[] { HttpMethods.Get }));
/// <summary>
/// Allow authenticated requests only.

View file

@ -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<object> 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;

View file

@ -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<T> : JsonExpression
public class ObjectExpression<T> : 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
{
/// <inheritdoc />
public ValueTask<object?> 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);
}
}

View file

@ -155,7 +155,7 @@ public class WorkflowsFeature : FeatureBase
.AddExpressionHandler<LiteralExpressionHandler, LiteralExpression>()
.AddExpressionHandler<DelegateExpressionHandler, DelegateExpression>()
.AddExpressionHandler<VariableExpressionHandler, VariableExpression>()
.AddExpressionHandler<JsonExpressionHandler, JsonExpression>()
.AddExpressionHandler<ObjectExpressionHandler, ObjectExpression>()
.AddExpressionHandler<OutputExpressionHandler, OutputExpression>()
.AddExpressionHandler<ElsaExpressionHandler, ElsaExpression>();
}

View file

@ -74,12 +74,12 @@ public class Input<T> : Input
}
/// <inheritdoc />
public Input(JsonLiteral<T> literal) : base(new JsonExpression(literal.Value), literal, typeof(T))
public Input(ObjectLiteral<T> literal) : base(new ObjectExpression(literal.Value), literal, typeof(T))
{
}
/// <inheritdoc />
public Input(JsonLiteral literal) : base(new JsonExpression(literal.Value), literal, typeof(T))
public Input(ObjectLiteral literal) : base(new ObjectExpression(literal.Value), literal, typeof(T))
{
}

View file

@ -7,43 +7,57 @@ using Elsa.Workflows.Core.Services;
namespace Elsa.Workflows.Management.Providers;
/// <inheritdoc />
public class DefaultExpressionSyntaxProvider : IExpressionSyntaxProvider
{
private readonly IIdentityGenerator _identityGenerator;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultExpressionSyntaxProvider"/> class.
/// </summary>
public DefaultExpressionSyntaxProvider(IIdentityGenerator identityGenerator)
{
_identityGenerator = identityGenerator;
}
/// <inheritdoc />
public ValueTask<IEnumerable<ExpressionSyntaxDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default)
{
var literal = CreateLiteralDescriptor();
var @object = CreateObjectDescriptor();
var json = CreateJsonDescriptor();
var @delegate = CreateDelegateDescriptor();
return ValueTask.FromResult<IEnumerable<ExpressionSyntaxDescriptor>>(new[] { literal, json, @delegate });
return ValueTask.FromResult<IEnumerable<ExpressionSyntaxDescriptor>>(new[] { literal, @object, json, @delegate });
}
private ExpressionSyntaxDescriptor CreateLiteralDescriptor() => CreateDescriptor<LiteralExpression>(
private ExpressionSyntaxDescriptor CreateLiteralDescriptor() => DefaultExpressionSyntaxProvider.CreateDescriptor<LiteralExpression>(
"Literal",
CreateLiteralExpression,
context => new Literal(context.GetExpression<LiteralExpression>().Value),
expression => expression.Value);
private ExpressionSyntaxDescriptor CreateJsonDescriptor() => CreateDescriptor<JsonExpression>(
private ExpressionSyntaxDescriptor CreateObjectDescriptor() => DefaultExpressionSyntaxProvider.CreateDescriptor<ObjectExpression>(
"Object",
CreateObjectExpression,
context => new ObjectLiteral(context.GetExpression<ObjectExpression>().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<ObjectExpression>(
"Json",
CreateJsonExpression,
context => new JsonLiteral(context.GetExpression<JsonExpression>().Value),
CreateObjectExpression,
context => new ObjectLiteral(context.GetExpression<ObjectExpression>().Value),
expression => expression.Value);
private ExpressionSyntaxDescriptor CreateDelegateDescriptor() => CreateDescriptor<DelegateExpression>(
private ExpressionSyntaxDescriptor CreateDelegateDescriptor() => DefaultExpressionSyntaxProvider.CreateDescriptor<DelegateExpression>(
"Delegate",
CreateJsonExpression,
CreateObjectExpression,
context => new DelegateBlockReference(),
expression => expression.DelegateBlockReference.Delegate?.ToString());
private ExpressionSyntaxDescriptor CreateDescriptor<TExpression>(
private static ExpressionSyntaxDescriptor CreateDescriptor<TExpression>(
string syntax,
Func<ExpressionConstructorContext, IExpression> constructor,
Func<BlockReferenceConstructorContext, MemoryBlockReference> 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();