From b9697a9e28e3943f2a39cc0a34a410a3e0f435c3 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 May 2025 21:45:28 +0200 Subject: [PATCH] Fix Liquid expressions not working in sub-workflows (workflow-as-activity) (#6678) * Initial plan for issue * Fix Liquid expressions not working in sub-workflows Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> --- .../Handlers/ConfigureLiquidEngine.cs | 14 +++- .../LiquidSubWorkflowTests.cs | 40 ++++++++++ .../Scenarios/LiquidSubWorkflow/Workflows.cs | 76 +++++++++++++++++++ 3 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 test/integration/Elsa.Workflows.IntegrationTests/Scenarios/LiquidSubWorkflow/LiquidSubWorkflowTests.cs create mode 100644 test/integration/Elsa.Workflows.IntegrationTests/Scenarios/LiquidSubWorkflow/Workflows.cs diff --git a/src/modules/Elsa.Expressions.Liquid/Handlers/ConfigureLiquidEngine.cs b/src/modules/Elsa.Expressions.Liquid/Handlers/ConfigureLiquidEngine.cs index 5fb40d3f4..a796393bd 100644 --- a/src/modules/Elsa.Expressions.Liquid/Handlers/ConfigureLiquidEngine.cs +++ b/src/modules/Elsa.Expressions.Liquid/Handlers/ConfigureLiquidEngine.cs @@ -75,10 +75,18 @@ internal class ConfigureLiquidEngine : INotificationHandler GetInput(ExpressionExecutionContext context, string key, TemplateOptions options) { - var workflowExecutionContext = context.GetWorkflowExecutionContext(); - var input = workflowExecutionContext.Input.TryGetValue(key, out var value) ? value : default; + // First, check if the current activity has inputs + if (context.TryGetActivityExecutionContext(out var activityExecutionContext) && + activityExecutionContext.Input.TryGetValue(key, out var activityValue)) + { + return Task.FromResult(activityValue == null ? NilValue.Instance : FluidValue.Create(activityValue, options)); + } - return Task.FromResult(input == null ? NilValue.Instance : FluidValue.Create(value, options)); + // Fall back to workflow inputs if activity inputs don't contain the key + var workflowExecutionContext = context.GetWorkflowExecutionContext(); + var input = workflowExecutionContext.Input.TryGetValue(key, out var workflowValue) ? workflowValue : default; + + return Task.FromResult(input == null ? NilValue.Instance : FluidValue.Create(workflowValue, options)); } private static object? GetVariableInScope(ExpressionExecutionContext context, string variableName) diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/LiquidSubWorkflow/LiquidSubWorkflowTests.cs b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/LiquidSubWorkflow/LiquidSubWorkflowTests.cs new file mode 100644 index 000000000..5080dfd69 --- /dev/null +++ b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/LiquidSubWorkflow/LiquidSubWorkflowTests.cs @@ -0,0 +1,40 @@ +using Elsa.Testing.Shared; +using Elsa.Workflows.Runtime; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; + +namespace Elsa.Workflows.IntegrationTests.Scenarios.LiquidSubWorkflow; + +public class LiquidSubWorkflowTests +{ + private readonly ITestOutputHelper _testOutputHelper; + + public LiquidSubWorkflowTests(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + [Fact] + public async Task ShouldBeAbleToUseLiquidExpressionsToReadInputInSubWorkflows() + { + // Arrange. + var services = new TestApplicationBuilder() + .WithCapturingTextWriter() + .Build(); + + var workflowRunner = services.GetRequiredService(); + + // Act. + await workflowRunner.RunAsync(); + + // Assert. + var capturedOutput = services.GetRequiredService(); + var output = capturedOutput.ToString(); + _testOutputHelper.WriteLine(output); + + // Verify that the liquid expressions were able to read the input values. + Assert.Contains("Person: John Doe, Email: john@example.com", output); + Assert.Contains("Sub workflow result: John Doe - john@example.com", output); + } +} \ No newline at end of file diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/LiquidSubWorkflow/Workflows.cs b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/LiquidSubWorkflow/Workflows.cs new file mode 100644 index 000000000..7d82b70a9 --- /dev/null +++ b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/LiquidSubWorkflow/Workflows.cs @@ -0,0 +1,76 @@ +using Elsa.Extensions; +using Elsa.Workflows.Activities; +using Elsa.Workflows.Management.Activities.SetOutput; +using Elsa.Workflows.Memory; +using Elsa.Workflows.Models; +using Elsa.Expressions.Models; + +namespace Elsa.Workflows.IntegrationTests.Scenarios.LiquidSubWorkflow; + +/// +/// A sub-workflow that reads input using liquid expressions +/// +public class LiquidSubWorkflow : WorkflowBase +{ + public Input PersonName { get; set; } = default!; + public Input PersonEmail { get; set; } = default!; + public Output Result { get; set; } = default!; + + protected override void Build(IWorkflowBuilder builder) + { + var resultVariable = new Variable("Result"); + + builder.Root = new Sequence + { + Variables = { resultVariable }, + Activities = + { + // Use liquid to read inputs + new WriteLine(new Expression("Liquid", "Person: {{ Input.PersonName }}, Email: {{ Input.PersonEmail }}")), + + // Set the result with liquid expression + new SetVariable + { + Variable = resultVariable, + Value = new Expression("Liquid", "{{ Input.PersonName }} - {{ Input.PersonEmail }}") + }, + + // Set output + new SetOutput + { + OutputName = new("Result"), + OutputValue = new(resultVariable) + } + } + }; + } +} + +/// +/// A main workflow that uses the sub-workflow as an activity +/// +public class LiquidParentWorkflow : WorkflowBase +{ + protected override void Build(IWorkflowBuilder builder) + { + var resultVariable = new Variable("Result"); + + var subWorkflow = new LiquidSubWorkflow + { + PersonName = new("John Doe"), + PersonEmail = new("john@example.com"), + Result = new(resultVariable) + }; + + builder.Variables.Add(resultVariable); + + builder.Root = new Sequence + { + Activities = + { + subWorkflow, + new WriteLine(context => $"Sub workflow result: {resultVariable.Get(context)}") + } + }; + } +} \ No newline at end of file