Merge remote-tracking branch 'origin/main' into feature/service-bus-pub-sub
This commit is contained in:
commit
b38ff41c6d
43
.github/workflows/pr.yml
vendored
43
.github/workflows/pr.yml
vendored
|
|
@ -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
|
||||
1
Elsa.sln
1
Elsa.sln
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Dynamic;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Expressions.Contracts;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Workflows.Memory;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue