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>
This commit is contained in:
Copilot 2025-05-24 21:45:28 +02:00 committed by GitHub
parent 144edc3a85
commit b9697a9e28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 127 additions and 3 deletions

View file

@ -75,10 +75,18 @@ internal class ConfigureLiquidEngine : INotificationHandler<RenderingLiquidTempl
private Task<FluidValue> 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)

View file

@ -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<IWorkflowRunner>();
// Act.
await workflowRunner.RunAsync<LiquidParentWorkflow>();
// Assert.
var capturedOutput = services.GetRequiredService<StringWriter>();
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);
}
}

View file

@ -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;
/// <summary>
/// A sub-workflow that reads input using liquid expressions
/// </summary>
public class LiquidSubWorkflow : WorkflowBase
{
public Input<string> PersonName { get; set; } = default!;
public Input<string> PersonEmail { get; set; } = default!;
public Output<string> Result { get; set; } = default!;
protected override void Build(IWorkflowBuilder builder)
{
var resultVariable = new Variable<string>("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)
}
}
};
}
}
/// <summary>
/// A main workflow that uses the sub-workflow as an activity
/// </summary>
public class LiquidParentWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
var resultVariable = new Variable<string>("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)}")
}
};
}
}