Merge remote-tracking branch 'origin/main' into feature/service-bus-pub-sub

This commit is contained in:
Raymond den Haan 2024-02-14 09:31:19 +01:00
commit b38ff41c6d
15 changed files with 179 additions and 99 deletions

View file

@ -1,43 +0,0 @@
# ------------------------------------------------------------------------------
# <auto-generated>
#
# This code was generated.
#
# - To turn off auto-generation set:
#
# [CustomGitHubActions (AutoGenerate = false)]
#
# - To trigger manual generation invoke:
#
# nuke --generate-configuration GitHubActions_pr --host GitHubActions
#
# </auto-generated>
# ------------------------------------------------------------------------------
name: pr
on:
pull_request:
branches:
- main
paths:
- '**/*'
- '!docs/**/*'
- '!package.json'
- '!package-lock.json'
- '!readme.md'
jobs:
ubuntu-latest:
name: ubuntu-latest
runs-on: ubuntu-latest
steps:
- if: ${{ runner.os == 'Windows' }}
name: 'Use GNU tar'
shell: cmd
run: |
echo "Adding GNU tar to PATH"
echo C:\Program Files\Git\usr\bin>>"%GITHUB_PATH%"
- uses: actions/checkout@v3
- name: 'Run: Compile, Test, Pack'
run: ./build.cmd Compile Test Pack

View file

@ -292,7 +292,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pipelines", "pipelines", "{
.github\workflows\elsa-server.yml = .github\workflows\elsa-server.yml
.github\workflows\elsa-studio.yml = .github\workflows\elsa-studio.yml
.github\workflows\packages.yml = .github\workflows\packages.yml
.github\workflows\pr.yml = .github\workflows\pr.yml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{99F2B1DA-2F69-4D70-A2A3-AC985AD91EC4}"

View file

@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>3.0.0</Version>
<Description>
Provides API endpoints for client applications to enumerate available environments.
</Description>

View file

@ -1,6 +1,5 @@
using System.Collections;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
using System.Globalization;
using System.Text.Json;

View file

@ -1,10 +1,12 @@
using Elsa.Expressions.Contracts;
using Elsa.Expressions.Helpers;
using Elsa.Expressions.Models;
using JetBrains.Annotations;
namespace Elsa.Expressions;
/// <inheritdoc />
[UsedImplicitly]
public class LiteralExpressionHandler : IExpressionHandler
{
private readonly IWellKnownTypeRegistry _wellKnownTypeRegistry;

View file

@ -1,3 +1,4 @@
using System.Text.Json;
using Elsa.Expressions.Contracts;
namespace Elsa.Expressions.Models;
@ -7,6 +8,20 @@ namespace Elsa.Expressions.Models;
/// </summary>
public class ExpressionDescriptor
{
/// <summary>
/// Initializes a new instance of the <see cref="ExpressionDescriptor"/> class.
/// </summary>
public ExpressionDescriptor()
{
// Default deserialization function.
Deserialize = context =>
{
return context.JsonElement.ValueKind == JsonValueKind.Object
? context.JsonElement.Deserialize<Expression>((JsonSerializerOptions?)context.Options)!
: new Expression(context.ExpressionType, null!);
};
}
/// <summary>
/// Gets or sets the syntax name.
/// </summary>
@ -41,4 +56,9 @@ public class ExpressionDescriptor
/// Gets or sets the memory block reference factory.
/// </summary>
public Func<MemoryBlockReference> MemoryBlockReferenceFactory { get; set; } = () => new MemoryBlockReference();
/// <summary>
/// Gets or sets the expression deserialization function.
/// </summary>
public Func<ExpressionSerializationContext, Expression> Deserialize { get; set; } = default!;
}

View file

@ -0,0 +1,8 @@
using System.Text.Json;
namespace Elsa.Expressions.Models;
/// <summary>
/// Defines the context for expression serialization.
/// </summary>
public record ExpressionSerializationContext(string ExpressionType, JsonElement JsonElement, JsonSerializerOptions Options, Type MemoryBlockType);

View file

@ -1,3 +1,4 @@
using System.Text.Json;
using Elsa.Expressions.Contracts;
using Elsa.Expressions.Models;
using Elsa.Workflows.Memory;

View file

@ -58,21 +58,6 @@ public static class ActivityExtensions
return query.Select(x => x!).ToList();
}
/// <summary>
/// Gets the output with the specified name.
/// </summary>
/// <param name="activity">The activity to get the output from.</param>
/// <param name="context">The workflow execution context.</param>
/// <param name="outputName">Name of the output.</param>
/// <returns>The output value.</returns>
public static object? GetOutput(this IActivity activity, WorkflowExecutionContext context, string? outputName = default)
{
var workflowExecutionContext = context;
var outputRegister = workflowExecutionContext.GetActivityOutputRegister();
var output = outputRegister.FindOutputByActivityId(activity.Id, outputName);
return output;
}
/// <summary>
/// Gets the output with the specified name.
@ -83,7 +68,15 @@ public static class ActivityExtensions
/// <returns>The output value.</returns>
public static object? GetOutput(this IActivity activity, ActivityExecutionContext context, string? outputName = default)
{
return activity.GetOutput(context.WorkflowExecutionContext, outputName);
var workflowExecutionContext = context.WorkflowExecutionContext;
var outputRegister = workflowExecutionContext.GetActivityOutputRegister();
// If the provided activity execution context is the same as the current activity's execution context, we return the exact output value of the current activity execution context.
if(context.Activity.NodeId == activity.NodeId)
return outputRegister.FindOutputByActivityInstanceId(context.Id, outputName);
// If the provided activity execution context is different from the current activity's execution context, we look for the last output value of the activity.
return outputRegister.FindOutputByActivityId(activity.Id, outputName);
}
/// <summary>
@ -95,7 +88,12 @@ public static class ActivityExtensions
/// <returns>The output value.</returns>
public static object? GetOutput(this IActivity activity, ExpressionExecutionContext context, string? outputName = default)
{
return activity.GetOutput(context.GetWorkflowExecutionContext(), outputName);
var activityExecutionContext = context.GetActivityExecutionContext();
if (activityExecutionContext == null)
return null;
return activity.GetOutput(activityExecutionContext, outputName);
}
/// <summary>

View file

@ -61,7 +61,7 @@ public class ActivityOutputRegister
/// <returns>The output value.</returns>
public object? FindOutputByActivityId(string activityId, string? outputName = default)
{
var record = _records.FirstOrDefault(x => x.ActivityId == activityId && x.OutputName == (outputName ?? DefaultOutputName));
var record = _records.LastOrDefault(x => x.ActivityId == activityId && x.OutputName == (outputName ?? DefaultOutputName));
return record?.Value;
}
@ -73,7 +73,7 @@ public class ActivityOutputRegister
/// <returns>The output value.</returns>
public object? FindOutputByActivityInstanceId(string activityInstanceId, string? outputName = default)
{
var record = _records.FirstOrDefault(x => x.ActivityInstanceId == activityInstanceId && x.OutputName == (outputName ?? DefaultOutputName));
var record = _records.LastOrDefault(x => x.ActivityInstanceId == activityInstanceId && x.OutputName == (outputName ?? DefaultOutputName));
return record?.Value;
}
}

View file

@ -39,27 +39,17 @@ public class InputJsonConverter<T> : JsonConverter<Input<T>>
var expressionElement = doc.RootElement.TryGetProperty("expression", out var expressionElementValue) ? expressionElementValue : default;
var expressionTypeNameElement = expressionElement.ValueKind != JsonValueKind.Undefined ? expressionElement.TryGetProperty("type", out var expressionTypeNameElementValue) ? expressionTypeNameElementValue : default : default;
var expressionTypeName = expressionTypeNameElement.ValueKind != JsonValueKind.Undefined ? expressionTypeNameElement.GetString() ?? "Literal" : "Literal";
var expressionDescriptor = _expressionDescriptorRegistry.Find(expressionTypeName);
var memoryBlockReference = expressionDescriptor?.MemoryBlockReferenceFactory();
var memoryBlockReferenceType = memoryBlockReference?.GetType();
var expressionValueElement = expressionElement.TryGetProperty("value", out var expressionElementValueValue) ? expressionElementValueValue : default;
var expressionValue = expressionValueElement.ValueKind switch
{
JsonValueKind.String => expressionValueElement.GetString(),
JsonValueKind.False => false,
JsonValueKind.True => true,
JsonValueKind.Number => expressionValueElement.GetDouble(),
JsonValueKind.Undefined => default,
_ => memoryBlockReferenceType != null ? expressionValueElement.Deserialize(memoryBlockReferenceType, options)! : default
};
var expression = new Expression(expressionTypeName, expressionValue);
var expressionTypeName = expressionTypeNameElement.ValueKind != JsonValueKind.Undefined ? expressionTypeNameElement.GetString() ?? "Literal" : default;
var expressionDescriptor = expressionTypeName != null ? _expressionDescriptorRegistry.Find(expressionTypeName) : default;
var memoryBlockReference = expressionDescriptor?.MemoryBlockReferenceFactory?.Invoke();
if (memoryBlockReference == null)
return default!;
var memoryBlockType = memoryBlockReference.GetType();
var context = new ExpressionSerializationContext(expressionTypeName!, expressionElement, options, memoryBlockType);
var expression = expressionDescriptor!.Deserialize(context);
return (Input<T>)Activator.CreateInstance(typeof(Input<T>), expression, memoryBlockReference)!;
}
@ -73,10 +63,10 @@ public class InputJsonConverter<T> : JsonConverter<Input<T>>
var expression = value.Expression;
var expressionType = expression?.Type;
var expressionDescriptor = expressionType != null ? _expressionDescriptorRegistry.Find(expressionType) : default;
if (expressionDescriptor == null)
throw new JsonException($"Could not find an expression descriptor for expression type '{expressionType}'.");
var targetType = value.Type;
var expressionValue = expressionDescriptor.IsSerializable ? expression : null;

View file

@ -1,3 +1,4 @@
using System.Text.Json;
using Elsa.Expressions;
using Elsa.Expressions.Contracts;
using Elsa.Expressions.Models;
@ -21,26 +22,66 @@ public class DefaultExpressionDescriptorProvider : IExpressionDescriptorProvider
yield return CreateVariableDescriptor();
}
private ExpressionDescriptor CreateLiteralDescriptor() => CreateDescriptor<LiteralExpressionHandler>("Literal", "Literal", isBrowsable: false);
private ExpressionDescriptor CreateLiteralDescriptor()
{
return CreateDescriptor<LiteralExpressionHandler>(
"Literal",
"Literal",
isBrowsable: false,
memoryBlockReferenceFactory: () => new Literal(),
deserialize: (context) =>
{
var elementValue = context.JsonElement.TryGetProperty("value", out var v) ? v : default;
var value = (object?)(elementValue.ValueKind switch
{
JsonValueKind.String => elementValue.GetString(),
JsonValueKind.Number => elementValue.GetDecimal(),
JsonValueKind.True => true,
JsonValueKind.False => false,
_ => v.ToString()
});
return new Expression("Literal", value);
});
}
private ExpressionDescriptor CreateObjectDescriptor() => CreateDescriptor<ObjectExpressionHandler>("Object", "Object", monacoLanguage: "json", isBrowsable: false);
[Obsolete("Use Object instead.")]
private ExpressionDescriptor CreateJsonDescriptor() => CreateDescriptor<ObjectExpressionHandler>("Json", "Json", monacoLanguage: "json", isBrowsable: false);
private ExpressionDescriptor CreateDelegateDescriptor() => CreateDescriptor<DelegateExpressionHandler>("Delegate", "Delegate", false, false);
private ExpressionDescriptor CreateVariableDescriptor() => CreateDescriptor<VariableExpressionHandler>("Variable", "Variable", isBrowsable: false, memoryBlockReferenceFactory: () => new Variable());
private ExpressionDescriptor CreateVariableDescriptor()
{
return CreateDescriptor<VariableExpressionHandler>(
"Variable",
"Variable",
isBrowsable: false,
memoryBlockReferenceFactory: () => new Variable(),
deserialize: context =>
{
var valueElement = context.JsonElement.TryGetProperty("value", out var v) ? v : default;
var value = valueElement.Deserialize(context.MemoryBlockType, context.Options);
return new Expression("Variable", value);
}
);
}
private static ExpressionDescriptor CreateDescriptor<THandler>(
string type,
string expressionType,
string displayName,
bool isSerializable = true,
bool isBrowsable = true,
string? monacoLanguage = null,
Func<MemoryBlockReference>? memoryBlockReferenceFactory = default) where THandler : IExpressionHandler
Func<MemoryBlockReference>? memoryBlockReferenceFactory = default,
Func<ExpressionSerializationContext, Expression>? deserialize = default)
where THandler : IExpressionHandler
{
var descriptor = new ExpressionDescriptor
{
Type = type,
Type = expressionType,
DisplayName = displayName,
IsSerializable = isSerializable,
IsBrowsable = isBrowsable,
@ -48,8 +89,14 @@ public class DefaultExpressionDescriptorProvider : IExpressionDescriptorProvider
MemoryBlockReferenceFactory = memoryBlockReferenceFactory ?? (() => new MemoryBlockReference())
};
if (deserialize != null)
descriptor.Deserialize = deserialize;
if (monacoLanguage != null)
descriptor.Properties = new { MonacoLanguage = monacoLanguage }.ToDictionary();
descriptor.Properties = new
{
MonacoLanguage = monacoLanguage
}.ToDictionary();
return descriptor;
}

View file

@ -0,0 +1,41 @@
using System.Text.Json.Serialization;
using Elsa.Expressions.Models;
using Elsa.Extensions;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Models;
namespace Elsa.Workflows.IntegrationTests.Serialization.VariableExpressions;
/// <inheritdoc />
public class NumberActivity : CodeActivity
{
/// <inheritdoc />
[JsonConstructor]
public NumberActivity()
{
}
/// <inheritdoc />
public NumberActivity(Variable<int> variable)
{
Number = new(variable);
}
/// <inheritdoc />
public NumberActivity(Literal<int> literal)
{
Number = new(literal);
}
/// <summary>
/// Gets or sets the number.
/// </summary>
public Input<int> Number { get; set; } = default!;
/// <inheritdoc />
protected override void Execute(ActivityExecutionContext context)
{
var number = Number.Get(context);
Console.WriteLine(number.ToString());
}
}

View file

@ -18,23 +18,35 @@ public class Tests
{
private readonly IWorkflowSerializer _workflowSerializer;
private readonly IWorkflowBuilder _workflowBuilder;
private readonly IWorkflowRunner _workflowRunner;
/// <summary>
/// Initializes a new instance of the <see cref="Tests"/> class.
/// </summary>
public Tests(ITestOutputHelper testOutputHelper)
{
var serviceProvider = new TestApplicationBuilder(testOutputHelper).Build();
_workflowSerializer = serviceProvider.GetRequiredService<IWorkflowSerializer>();
IWorkflowBuilderFactory workflowBuilderFactory = serviceProvider.GetRequiredService<IWorkflowBuilderFactory>();
_workflowBuilder = workflowBuilderFactory.CreateBuilder();
_workflowRunner = serviceProvider.GetRequiredService<IWorkflowRunner>();
}
/// <summary>
/// Variable types remain intact after serialization.
/// </summary>
[Fact(DisplayName = "Variable types remain intact after serialization")]
public async Task Test1()
{
var workflow = await _workflowBuilder.BuildWorkflowAsync<SampleWorkflow>();
var serialized = _workflowSerializer.Serialize(workflow);
var deserializedWorkflow = _workflowSerializer.Deserialize(serialized);
var rehydratedWriteLine = (WriteLine)((Flowchart)deserializedWorkflow.Root).Activities.ElementAt(0);
var rehydratedWriteLine1 = (WriteLine)((Sequence)deserializedWorkflow.Root).Activities.ElementAt(0);
var rehydratedNumberActivity1 = (NumberActivity)((Sequence)deserializedWorkflow.Root).Activities.ElementAt(2);
Assert.IsType<Variable<string>>(rehydratedWriteLine.Text.Expression!.Value);
Assert.IsType<Variable<string>>(rehydratedWriteLine1.Text.Expression!.Value);
Assert.IsType<Variable<int>>(rehydratedNumberActivity1.Number.Expression!.Value);
await _workflowRunner.RunAsync(workflow);
}
}

View file

@ -1,6 +1,5 @@
using Elsa.Workflows;
using Elsa.Expressions.Models;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Activities.Flowchart.Activities;
using Elsa.Workflows.Contracts;
namespace Elsa.Workflows.IntegrationTests.Serialization.VariableExpressions;
@ -9,16 +8,24 @@ class SampleWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
var variable1 = workflow.WithVariable<string>("Some Value");
var variable1 = workflow.WithVariable("Some variable");
var variable2 = workflow.WithVariable(42);
var literal1 = new Literal<string>("Some literal");
var literal2 = new Literal<int>(84);
var writeLine1 = new WriteLine(variable1);
var writeLine2 = new WriteLine(literal1);
var numberActivity1 = new NumberActivity(variable2);
var numberActivity2 = new NumberActivity(literal2);
workflow.Root = new Flowchart
workflow.Root = new Sequence
{
Activities =
{
writeLine1
},
Start = writeLine1
writeLine1,
writeLine2,
numberActivity1,
numberActivity2
}
};
}
}