Skip to content

Commit

Permalink
Add ExecuteWorkflow activity (#6134)
Browse files Browse the repository at this point in the history
* Add ExecuteWorkflow activity and enable multitenancy

Introduced the ExecuteWorkflow activity to create and execute workflow instances, and updated runtime settings to use ProtoActor for distributed caching transport. Enabled multitenancy for the Elsa Server Web application.

* Namespace renames (#6135)

* Create ExecuteWorkflows tests and workflows

Add unit tests for executing workflows, including MainWorkflow and SubroutineWorkflow. Also, rename ExecutedWorkflowResult to ExecuteWorkflowResult in the runtime module for consistency.

* Add output definitions to WorkflowBuilder

Refactored the `WithInput` method for clarity and added multiple `WithOutput` methods to support different ways of defining workflow outputs. These changes enhance the flexibility and readability of the workflow configuration process by providing a consistent API for input and output definitions.

* Refactor namespaces in component tests

Updated namespaces from Helpers to specific contexts like Fixtures, Abstractions, Consumers, Decorators, etc., to improve code organization and readability. This change affects multiple files across different test scenarios and modules.

* Disable RabbitMQ and multitenancy, rename test class

Commented out RabbitMQ usage in WorkflowServer.cs to focus on other transports. Changed multitenancy flag to false in Program.cs. Renamed DispatchWorkflowsTests to ExecuteWorkflowsTests for clarity.
  • Loading branch information
sfmskywalker authored Nov 21, 2024
1 parent 49a7f22 commit 1ed6695
Show file tree
Hide file tree
Showing 37 changed files with 324 additions and 42 deletions.
2 changes: 1 addition & 1 deletion src/apps/Elsa.Server.Web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
const bool useReadOnlyMode = false;
const bool useSignalR = false; // Disabled until Elsa Studio sends authenticated requests.
const WorkflowRuntime workflowRuntime = WorkflowRuntime.ProtoActor;
const DistributedCachingTransport distributedCachingTransport = DistributedCachingTransport.MassTransit;
const DistributedCachingTransport distributedCachingTransport = DistributedCachingTransport.ProtoActor;
const MassTransitBroker massTransitBroker = MassTransitBroker.Memory;
const bool useMultitenancy = false;
const bool useAgents = false;
Expand Down
49 changes: 41 additions & 8 deletions src/modules/Elsa.Workflows.Core/Builders/WorkflowBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,7 @@ public IWorkflowBuilder WithVariables(params Variable[] variables)
/// <inheritdoc />
public InputDefinition WithInput<T>(string name, string? description = default)
{
return WithInput(inputDefinition =>
{
inputDefinition.Name = name;
inputDefinition.Type = typeof(T);
if (description != null)
inputDefinition.Description = description;
});
return WithInput(name, typeof(T), description);
}

/// <inheritdoc />
Expand Down Expand Up @@ -173,6 +166,46 @@ public IWorkflowBuilder WithInput(InputDefinition inputDefinition)
return this;
}

public OutputDefinition WithOutput<T>(string name, string? description = default)
{
return WithOutput(name, typeof(T), description);
}

public OutputDefinition WithOutput(string name, Type type, string? description = default)
{
return WithOutput(outputDefinition =>
{
outputDefinition.Name = name;
outputDefinition.Type = type;
if (description != null)
outputDefinition.Description = description;
});
}

public OutputDefinition WithOutput(string name, Type type, Action<OutputDefinition>? setup = default)
{
return WithOutput(outputDefinition =>
{
outputDefinition.Name = name;
outputDefinition.Type = type;
setup?.Invoke(outputDefinition);
});
}

public OutputDefinition WithOutput(Action<OutputDefinition> setup)
{
var outputDefinition = new OutputDefinition();
setup(outputDefinition);
return WithOutput(outputDefinition);
}

public OutputDefinition WithOutput(OutputDefinition outputDefinition)
{
Outputs.Add(outputDefinition);
return outputDefinition;
}

/// <inheritdoc />
public IWorkflowBuilder WithCustomProperty(string name, object value)
{
Expand Down
25 changes: 25 additions & 0 deletions src/modules/Elsa.Workflows.Core/Contracts/IWorkflowBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,31 @@ public interface IWorkflowBuilder
/// A fluent method for adding an input to <see cref="Inputs"/>.
/// </summary>
IWorkflowBuilder WithInput(InputDefinition inputDefinition);

/// <summary>
/// A fluent method for adding an output to <see cref="Outputs"/>.
/// </summary>
OutputDefinition WithOutput<T>(string name, string? description = default);

/// <summary>
/// A fluent method for adding an output to <see cref="Outputs"/>.
/// </summary>
OutputDefinition WithOutput(string name, Type type, string? description = default);

/// <summary>
/// A fluent method for adding an output to <see cref="Outputs"/>.
/// </summary>
OutputDefinition WithOutput(string name, Type type, Action<OutputDefinition>? setup = default);

/// <summary>
/// A fluent method for adding an output to <see cref="Outputs"/>.
/// </summary>
OutputDefinition WithOutput(Action<OutputDefinition> setup);

/// <summary>
/// A fluent method for adding an output to <see cref="Outputs"/>.
/// </summary>
OutputDefinition WithOutput(OutputDefinition outputDefinition);

/// <summary>
/// A fluent method for adding a property to <see cref="CustomProperties"/>.
Expand Down
6 changes: 3 additions & 3 deletions src/modules/Elsa.Workflows.Core/Models/ArgumentDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ public abstract class ArgumentDefinition
/// <summary>
/// A user friendly name of the input.
/// </summary>
public string DisplayName { get; set; } = default!;
public string DisplayName { get; set; } = string.Empty;

/// <summary>
/// A description of the input.
/// </summary>
public string Description { get; set; } = default!;
public string Description { get; set; } = string.Empty;

/// <summary>
/// The category to which this input belongs.
/// </summary>
public string Category { get; set; } = default!;
public string Category { get; set; } = string.Empty;
}
90 changes: 90 additions & 0 deletions src/modules/Elsa.Workflows.Runtime/Activities/ExecuteWorkflow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.Runtime.CompilerServices;
using Elsa.Common.Models;
using Elsa.Extensions;
using Elsa.Workflows.Attributes;
using Elsa.Workflows.Management;
using Elsa.Workflows.Models;
using Elsa.Workflows.Options;
using Elsa.Workflows.UIHints;
using JetBrains.Annotations;

namespace Elsa.Workflows.Runtime.Activities;

/// <summary>
/// Creates a new workflow instance of the specified workflow and dispatches it for execution.
/// </summary>
[Activity("Elsa", "Composition", "Create a new workflow instance of the specified workflow and execute it.", Kind = ActivityKind.Task)]
[UsedImplicitly]
public class ExecuteWorkflow : Activity<ExecuteWorkflowResult>
{
/// <inheritdoc />
public ExecuteWorkflow([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line)
{
}

/// <summary>
/// The definition ID of the workflow to execute.
/// </summary>
[Input(
DisplayName = "Workflow Definition",
Description = "The definition ID of the workflow to execute.",
UIHint = InputUIHints.WorkflowDefinitionPicker
)]
public Input<string> WorkflowDefinitionId { get; set; } = default!;

/// <summary>
/// The correlation ID to associate the workflow with.
/// </summary>
[Input(
DisplayName = "Correlation ID",
Description = "The correlation ID to associate the workflow with."
)]
public Input<string?> CorrelationId { get; set; } = default!;

/// <summary>
/// The input to send to the workflow.
/// </summary>
[Input(Description = "The input to send to the workflow.")]
public Input<IDictionary<string, object>?> Input { get; set; } = default!;

/// <inheritdoc />
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
{
var result = await ExecuteWorkflowAsync(context);
context.SetResult(result);
await context.CompleteActivityAsync();
}

private async ValueTask<ExecuteWorkflowResult> ExecuteWorkflowAsync(ActivityExecutionContext context)
{
var workflowDefinitionId = WorkflowDefinitionId.Get(context);
var input = Input.GetOrDefault(context) ?? new Dictionary<string, object>();
var correlationId = CorrelationId.GetOrDefault(context);
var workflowInvoker = context.GetRequiredService<IWorkflowInvoker>();
var identityGenerator = context.GetRequiredService<IIdentityGenerator>();
var workflowDefinitionService = context.GetRequiredService<IWorkflowDefinitionService>();
var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(workflowDefinitionId, VersionOptions.Published, context.CancellationToken);

if (workflowGraph == null)
throw new Exception($"No published version of workflow definition with ID {workflowDefinitionId} found.");

var options = new RunWorkflowOptions
{
ParentWorkflowInstanceId = context.WorkflowExecutionContext.Id,
Input = input,
CorrelationId = correlationId,
WorkflowInstanceId = identityGenerator.GenerateId()
};

var workflowResult = await workflowInvoker.InvokeAsync(workflowGraph, options, context.CancellationToken);
var info = new ExecuteWorkflowResult
{
WorkflowInstanceId = options.WorkflowInstanceId,
Status = workflowResult.WorkflowState.Status,
SubStatus = workflowResult.WorkflowState.SubStatus,
Output = workflowResult.WorkflowState.Output
};

return info;
}
}
14 changes: 14 additions & 0 deletions src/modules/Elsa.Workflows.Runtime/Models/ExecuteWorkflowResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Elsa.Workflows.Runtime;

/// <summary>
/// Represents the result of executing a workflow.
/// </summary>
public class ExecuteWorkflowResult
{
public string WorkflowDefinitionVersionId { get; set; } = default!;
public string WorkflowInstanceId { get; set; } = default!;
public string? CorrelationId { get; set; }
public WorkflowStatus Status { get; set; }
public WorkflowSubStatus SubStatus { get; set; }
public IDictionary<string, object>? Output { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Elsa.Workflows.ComponentTests.Fixtures;
using Microsoft.Extensions.DependencyInjection;

namespace Elsa.Workflows.ComponentTests.Helpers;
namespace Elsa.Workflows.ComponentTests.Abstractions;

[Collection(nameof(AppCollection))]
public abstract class AppComponentTest(App app) : IDisposable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using Hangfire.Annotations;
using MassTransit;

namespace Elsa.Workflows.ComponentTests.Helpers;
namespace Elsa.Workflows.ComponentTests.Consumers;

[UsedImplicitly]
public class WorkflowDefinitionEventConsumer(WorkflowDefinitionEvents workflowDefinitionEvents) : IConsumer<WorkflowDefinitionDeleted>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using Hangfire.Annotations;
using Microsoft.Extensions.Primitives;

namespace Elsa.Workflows.ComponentTests.Helpers;
namespace Elsa.Workflows.ComponentTests.Decorators;

/// <summary>
/// Provides a decorator for the `IChangeTokenSignaler` interface that triggers change token events.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Hangfire.Annotations;

namespace Elsa.Workflows.ComponentTests.Helpers;
namespace Elsa.Workflows.ComponentTests.Fixtures;

[UsedImplicitly]
public class App : IAsyncLifetime
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Elsa.Workflows.ComponentTests.Helpers;
namespace Elsa.Workflows.ComponentTests.Fixtures;

[CollectionDefinition(nameof(AppCollection))]
public class AppCollection : ICollectionFixture<App>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Hangfire.Annotations;

namespace Elsa.Workflows.ComponentTests.Helpers;
namespace Elsa.Workflows.ComponentTests.Fixtures;

[UsedImplicitly]
public class Cluster(Infrastructure infrastructure) : IAsyncDisposable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Testcontainers.PostgreSql;
using Testcontainers.RabbitMq;

namespace Elsa.Workflows.ComponentTests.Helpers;
namespace Elsa.Workflows.ComponentTests.Fixtures;

public class Infrastructure : IAsyncLifetime
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using Elsa.Agents;
using Elsa.Alterations.Extensions;
using Elsa.Caching;
using Elsa.EntityFrameworkCore;
using Elsa.EntityFrameworkCore.Extensions;
using Elsa.EntityFrameworkCore.Modules.Alterations;
using Elsa.EntityFrameworkCore.Modules.Identity;
Expand All @@ -14,6 +13,10 @@
using Elsa.MassTransit.Extensions;
using Elsa.Testing.Shared.Handlers;
using Elsa.Testing.Shared.Services;
using Elsa.Workflows.ComponentTests.Consumers;
using Elsa.Workflows.ComponentTests.Decorators;
using Elsa.Workflows.ComponentTests.Materializers;
using Elsa.Workflows.ComponentTests.WorkflowProviders;
using Elsa.Workflows.Management;
using Elsa.Workflows.Runtime.Distributed.Extensions;
using FluentStorage;
Expand All @@ -25,7 +28,7 @@
using Refit;
using static Elsa.Api.Client.RefitSettingsHelper;

namespace Elsa.Workflows.ComponentTests.Helpers;
namespace Elsa.Workflows.ComponentTests.Fixtures;

[UsedImplicitly]
public class WorkflowServer(Infrastructure infrastructure, string url) : WebApplicationFactory<Program>
Expand Down Expand Up @@ -73,7 +76,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
});
elsa.UseMassTransit(massTransit =>
{
massTransit.UseRabbitMq(rabbitMqConnectionString);
//massTransit.UseRabbitMq(rabbitMqConnectionString);
massTransit.AddConsumer<WorkflowDefinitionEventConsumer>("elsa-test-workflow-definition-updates", true);
});
elsa.UseIdentity(identity => identity.UseEntityFrameworkCore(ef => ef.UsePostgreSql(dbConnectionString)));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.ComponentTests.WorkflowProviders;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Entities;
using Elsa.Workflows.Runtime;

namespace Elsa.Workflows.ComponentTests.Helpers;
namespace Elsa.Workflows.ComponentTests.Materializers;

/// <summary>
/// A workflow materializer that deserializes workflows created from <see cref="TestWorkflowProvider"/>.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Elsa.Workflows.Runtime;

namespace Elsa.Workflows.ComponentTests.Helpers;
namespace Elsa.Workflows.ComponentTests.WorkflowProviders;

public class TestWorkflowProvider : IWorkflowsProvider
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Elsa.Workflows.ComponentTests.Helpers;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Microsoft.Extensions.DependencyInjection;

namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.FlowJoins;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
using Elsa.Api.Client.Resources.WorkflowDefinitions.Contracts;
using Elsa.Testing.Shared.Extensions;
using Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Execute;
using Elsa.Workflows.ComponentTests.Helpers;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;

namespace Elsa.Workflows.ComponentTests.Scenarios.BasicWorkflows;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using Elsa.Common.Models;
using Elsa.Testing.Shared;
using Elsa.Testing.Shared.Services;
using Elsa.Workflows.ComponentTests.Helpers;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.ComponentTests.Scenarios.BulkDispatchWorkflows.Workflows;
using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Elsa.Workflows.ComponentTests.Helpers;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Filters;
using Microsoft.Extensions.DependencyInjection;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Elsa.Workflows.ComponentTests.Helpers;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Models;
using Elsa.Workflows.Models;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using Elsa.Common.Models;
using Elsa.Testing.Shared;
using Elsa.Testing.Shared.Services;
using Elsa.Workflows.ComponentTests.Helpers;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.ComponentTests.Scenarios.DispatchWorkflows.Workflows;
using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime;
Expand Down
Loading

0 comments on commit 1ed6695

Please sign in to comment.