Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions src/ConductorSharp.Engine/Builders/DoWhileTaskBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ConductorSharp.Client.Generated;
using ConductorSharp.Engine.Interface;
using ConductorSharp.Engine.Model;
using ConductorSharp.Engine.Util;
using ConductorSharp.Engine.Util.Builders;

namespace ConductorSharp.Engine.Builders
{
/// <summary>
/// Extension methods to add a DO_WHILE loop with multiple inner tasks, based on BaseTaskBuilder.
/// </summary>
public static class DoWhileTaskExtensions
{
/// <summary>
/// Adds a DO_WHILE loop to the workflow definition.
/// </summary>
/// <typeparam name="TWorkflow">Workflow type</typeparam>
/// <param name="builder">Parent sequence builder</param>
/// <param name="reference">Expression selecting the workflow property holding the loop's reference name</param>
/// <param name="input">Expression creating the input</param>
/// <param name="configureBody">Delegate to add tasks inside the loop</param>
public static ITaskOptionsBuilder AddTask<TWorkflow>(
this ITaskSequenceBuilder<TWorkflow> builder,
Expression<Func<TWorkflow, DoWhileTaskModel>> reference,
Expression<Func<TWorkflow, DoWhileInput>> input,
Action<ITaskSequenceBuilder<TWorkflow>> configureBody
)
where TWorkflow : ITypedWorkflow
{
// Extract expressions
var taskBuilder = new DoWhileTaskBuilder<TWorkflow>(reference.Body, input.Body, builder.BuildConfiguration);
// Register it with the outer sequence
builder.AddTaskBuilderToSequence(taskBuilder);
// Allow the caller to configure inner loop body tasks
configureBody(taskBuilder);
return taskBuilder;
}
}

/// <summary>
/// Builder for the DO_WHILE task, extending BaseTaskBuilder for consistent patterns.
/// </summary>
internal sealed class DoWhileTaskBuilder<TWorkflow> : BaseTaskBuilder<DoWhileInput, NoOutput>, ITaskSequenceBuilder<TWorkflow>
where TWorkflow : ITypedWorkflow
{
private readonly DoWhileInput _doWhileInput;
private readonly List<WorkflowTask> _innerTasks = new();
public BuildContext BuildContext { get; } = new();
public BuildConfiguration BuildConfiguration { get; }
public WorkflowBuildItemRegistry WorkflowBuildRegistry { get; } = new();
public IEnumerable<ConfigurationProperty> ConfigurationProperties { get; } = new List<ConfigurationProperty>();

/// <inheritdoc />
public DoWhileTaskBuilder(Expression taskExpression, Expression loopConditionExpression, BuildConfiguration buildConfiguration)
: base(taskExpression, loopConditionExpression, buildConfiguration)
{
BuildConfiguration = buildConfiguration;
// Compile the JS condition string once
_doWhileInput = Expression.Lambda<Func<DoWhileInput>>(loopConditionExpression).Compile().Invoke();
}

/// <inheritdoc/>
public override WorkflowTask[] Build()
{
var refName = _taskRefferenceName;

if (_innerTasks.Any(t => t.WorkflowTaskType == WorkflowTaskType.DO_WHILE))
{
throw new InvalidOperationException("Nested DO_WHILE tasks are not allowed.");
}

var loopTask = new WorkflowTask
{
Name = refName,
TaskReferenceName = refName,
WorkflowTaskType = WorkflowTaskType.DO_WHILE,
Type = nameof(WorkflowTaskType.DO_WHILE),
InputParameters = new Dictionary<string, object>(),
LoopCondition = _doWhileInput.LoopCondition,
LoopOver = _innerTasks,
};
return [loopTask];
}

public void AddTaskBuilderToSequence(ITaskBuilder builder)
{
foreach (var task in builder.Build())
{
_innerTasks.Add(task);
}
}
}
}
23 changes: 23 additions & 0 deletions src/ConductorSharp.Engine/Model/DoWhileTaskModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using ConductorSharp.Engine.Builders;
using MediatR;

namespace ConductorSharp.Engine.Model
{
/// <summary>
/// Input for configuration of the DO_WHILE task.
/// </summary>
public class DoWhileInput : IRequest<NoOutput>, IWorkflowInput
{
/// <summary>
/// Condition of the loop in JavaScript format.
/// Example: "$.do_while_ref['' + $.do_while_ref.iteration].number_adder_sub_workflow.success == false"
/// To access the latest iteration, use the following syntax: TaskRef.iteration
/// </summary>
public string LoopCondition { get; set; }
}

/// <summary>
/// Task Model to reference the do while task in the workflow builders
/// </summary>
public class DoWhileTaskModel : TaskModel<DoWhileInput, NoOutput> { }
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

<ItemGroup>
<EmbeddedResource Include="Samples\Workflows\DictionaryInitializationWorkflow.json" />
<EmbeddedResource Include="Samples\Workflows\DoWhileTask.json" />
<EmbeddedResource Include="Samples\Workflows\FormatterWorkflow.json" />
<EmbeddedResource Include="Samples\Workflows\HumanTaskWorkflow.json" />
<EmbeddedResource Include="Samples\Workflows\ListInitializationWorkflow.json" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,15 @@ public void BuilderReturnsCorrectDefinitionDecisionTask()
Assert.Equal(expectedDefinition, definition);
}

[Fact]
public void BuilderReturnsCorrectDefinitionDoWhileTask()
{
var definition = GetDefinitionFromWorkflow<DoWhileTask>();
var expectedDefinition = EmbeddedFileHelper.GetLinesFromEmbeddedFile("~/Samples/Workflows/DoWhileTask.json");

Assert.Equal(expectedDefinition, definition);
}

[Fact]
public void BuilderReturnsCorrectDefinitionSwitchTask()
{
Expand Down
27 changes: 27 additions & 0 deletions test/ConductorSharp.Engine.Tests/Samples/Workflows/DoWhileTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace ConductorSharp.Engine.Tests.Samples.Workflows
{
public sealed class DoWhileTaskInput : WorkflowInput<DoWhileTaskOutput> { }

public sealed class DoWhileTaskOutput : WorkflowOutput { }

public sealed class DoWhileTask : Workflow<DoWhileTask, DoWhileTaskInput, DoWhileTaskOutput>
{
public DoWhileTaskModel DoWhile { get; set; }
public CustomerGetV1 GetCustomer { get; set; }

public DoWhileTask(WorkflowDefinitionBuilder<DoWhileTask, DoWhileTaskInput, DoWhileTaskOutput> builder)
: base(builder) { }

public override void BuildDefinition()
{
_builder.AddTask(
wf => wf.DoWhile,
wf => new() { LoopCondition = "$.do_while.iteration < 3" },
builder =>
{
builder.AddTask(wf => wf.GetCustomer, wf => new CustomerGetV1Input() { CustomerId = "CUSTOMER-1" });
}
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "do_while_task",
"version": 1,
"tasks": [
{
"name": "do_while",
"taskReferenceName": "do_while",
"inputParameters": {},
"type": "DO_WHILE",
"loopCondition": "$.do_while.iteration < 3",
"loopOver": [
{
"name": "CUSTOMER_get",
"taskReferenceName": "get_customer",
"inputParameters": {
"customer_id": "CUSTOMER-1"
},
"type": "SIMPLE",
"optional": false,
"workflowTaskType": "SIMPLE"
}
],
"workflowTaskType": "DO_WHILE"
}
],
"inputParameters": [],
"outputParameters": {},
"schemaVersion": 2,
"timeoutSeconds": 0
}
6 changes: 3 additions & 3 deletions test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Newtonsoft.Json;
using System;
using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace ConductorSharp.Engine.Tests.Util
{
Expand Down Expand Up @@ -43,7 +43,7 @@ public static string GetLinesFromEmbeddedFile(string fileName)

var contents = ReadAssemblyFile(typeof(EmbeddedFileHelper).Assembly, fileName);

return contents;
return contents.Replace("\r\n", "\n");
}

public static Task<T> GetObjectFromEmbeddedFileAsync<T>(string fileName, params (string Key, object Value)[] templateParams) =>
Expand Down