From 0ea463fdd09cfbbf71d183bb3a47dc7e66f256a6 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 25 Apr 2023 22:15:11 +0200 Subject: [PATCH] Add JSON workflow provider (#3955) * Refactor import models * Update API endpoints * Add console sample to run workflow from JSON * Add FluentStorage provider for JSON workflows + sample --- Elsa.sln | 21 +++ .../ServiceProviderExtensions.cs | 9 +- .../workflow-definitions/services/api.ts | 15 +-- .../workflow-definitions/services/manager.ts | 15 +-- .../Services/HttpBookmarkProcessor.cs | 1 + .../Elsa.ProtoActor/Grains/WorkflowGrain.cs | 1 + .../Services/ProtoActorWorkflowRuntime.cs | 1 + .../Contracts/IBlobStorageProvider.cs | 16 +++ ...lsa.WorkflowProviders.FluentStorage.csproj | 22 ++++ .../Extensions/ModuleExtensions.cs | 37 ++++++ .../Features/FluentStorageFeature.cs | 46 +++++++ .../FodyWeavers.xml | 3 + .../Services/BlobStorageProvider.cs | 24 ++++ ...FluentStorageWorkflowDefinitionProvider.cs | 96 ++++++++++++++ .../JavaScript/TypeDefinitions/Endpoint.cs | 1 + .../WorkflowDefinitions/Export/Endpoint.cs | 3 +- .../WorkflowDefinitions/Get/Endpoint.cs | 13 +- .../WorkflowDefinitions/Import/Endpoint.cs | 49 +++---- .../WorkflowDefinitions/Post/Endpoint.cs | 32 ++--- .../WorkflowDefinitions/Publish/Endpoint.cs | 12 +- .../WorkflowDefinitions/Retract/Endpoint.cs | 12 +- .../Features/WorkflowsApiFeature.cs | 1 - .../Mappers/WorkflowDefinitionMapper.cs | 36 ------ .../Models/WorkflowDefinitionModel.cs | 22 ---- .../Models/WorkflowDefinitionResponse.cs | 122 +++++++++--------- .../Contracts/IWorkflowDefinitionImporter.cs | 2 +- .../Contracts/IWorkflowDefinitionService.cs | 2 +- .../Features/WorkflowManagementFeature.cs | 1 + .../Mappers/WorkflowDefinitionMapper.cs | 76 +++++++++++ .../Materializers/JsonWorkflowMaterializer.cs | 22 +--- .../Models/SaveWorkflowDefinitionRequest.cs | 27 ++-- .../Models/WorkflowDefinitionModel.cs | 66 ++++++++++ .../Services/WorkflowDefinitionImporter.cs | 24 ++-- .../Services/WorkflowDefinitionService.cs | 10 +- .../Contracts/IWorkflowDefinitionProvider.cs | 17 ++- .../DependencyInjectionExtensions.cs | 10 ++ .../Features/WorkflowRuntimeFeature.cs | 6 + .../PopulateWorkflowDefinitionStore.cs | 7 +- .../Models/WorkflowDefinitionResult.cs | 11 ++ .../Services/ClrWorkflowDefinitionProvider.cs | 1 + .../DefaultBackgroundActivityInvoker.cs | 1 + .../Services/DefaultWorkflowRuntime.cs | 1 + .../Services/TriggerIndexer.cs | 1 + .../Services/DefaultWorkflowSinkInvoker.cs | 1 + .../Elsa.Samples.JsonWorkflowProvider.csproj | 22 ++++ .../Program.cs | 39 ++++++ .../Properties/launchSettings.json | 37 ++++++ .../Workflows/HelloWorld.json | 28 ++++ .../appsettings.json | 9 ++ .../Elsa.Samples.JsonWorkflows.csproj | 22 ++++ .../HelloWorld.json | 24 ++++ .../Elsa.Samples.JsonWorkflows/Program.cs | 33 +++++ .../Elsa.Samples.JsonWorkflows/Readme.md | 5 + 53 files changed, 847 insertions(+), 268 deletions(-) create mode 100644 src/modules/Elsa.WorkflowProviders.FluentStorage/Contracts/IBlobStorageProvider.cs create mode 100644 src/modules/Elsa.WorkflowProviders.FluentStorage/Elsa.WorkflowProviders.FluentStorage.csproj create mode 100644 src/modules/Elsa.WorkflowProviders.FluentStorage/Extensions/ModuleExtensions.cs create mode 100644 src/modules/Elsa.WorkflowProviders.FluentStorage/Features/FluentStorageFeature.cs create mode 100644 src/modules/Elsa.WorkflowProviders.FluentStorage/FodyWeavers.xml create mode 100644 src/modules/Elsa.WorkflowProviders.FluentStorage/Services/BlobStorageProvider.cs create mode 100644 src/modules/Elsa.WorkflowProviders.FluentStorage/Services/FluentStorageWorkflowDefinitionProvider.cs delete mode 100644 src/modules/Elsa.Workflows.Api/Mappers/WorkflowDefinitionMapper.cs delete mode 100644 src/modules/Elsa.Workflows.Api/Models/WorkflowDefinitionModel.cs rename src/modules/{Elsa.Workflows.Runtime => Elsa.Workflows.Management}/Contracts/IWorkflowDefinitionService.cs (94%) create mode 100644 src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs create mode 100644 src/modules/Elsa.Workflows.Management/Models/WorkflowDefinitionModel.cs rename src/modules/{Elsa.Workflows.Runtime => Elsa.Workflows.Management}/Services/WorkflowDefinitionService.cs (85%) create mode 100644 src/modules/Elsa.Workflows.Runtime/Models/WorkflowDefinitionResult.cs create mode 100644 src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Elsa.Samples.JsonWorkflowProvider.csproj create mode 100644 src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Program.cs create mode 100644 src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Properties/launchSettings.json create mode 100644 src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Workflows/HelloWorld.json create mode 100644 src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/appsettings.json create mode 100644 src/samples/console/Elsa.Samples.JsonWorkflows/Elsa.Samples.JsonWorkflows.csproj create mode 100644 src/samples/console/Elsa.Samples.JsonWorkflows/HelloWorld.json create mode 100644 src/samples/console/Elsa.Samples.JsonWorkflows/Program.cs create mode 100644 src/samples/console/Elsa.Samples.JsonWorkflows/Readme.md diff --git a/Elsa.sln b/Elsa.sln index 79bd9c993..183315e6f 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -188,6 +188,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Onboarding.Web EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Onboarding.WorkflowServer", "src\samples\aspnet\Elsa.Samples.Onboarding.WorkflowServer\Elsa.Samples.Onboarding.WorkflowServer.csproj", "{199FC97A-682B-4C7B-A560-1032B16AEAC4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.JsonWorkflows", "src\samples\console\Elsa.Samples.JsonWorkflows\Elsa.Samples.JsonWorkflows.csproj", "{4FA6955C-6860-493F-ABD4-CE327A33EEA3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.JsonWorkflowProvider", "src\samples\aspnet\Elsa.Samples.JsonWorkflowProvider\Elsa.Samples.JsonWorkflowProvider.csproj", "{C5043453-5FB8-4796-9A80-C4C766F2CB62}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.WorkflowProviders.FluentStorage", "src\modules\Elsa.WorkflowProviders.FluentStorage\Elsa.WorkflowProviders.FluentStorage.csproj", "{044C3108-FE79-460A-9C31-A03C30228836}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -474,6 +480,18 @@ Global {199FC97A-682B-4C7B-A560-1032B16AEAC4}.Debug|Any CPU.Build.0 = Debug|Any CPU {199FC97A-682B-4C7B-A560-1032B16AEAC4}.Release|Any CPU.ActiveCfg = Release|Any CPU {199FC97A-682B-4C7B-A560-1032B16AEAC4}.Release|Any CPU.Build.0 = Release|Any CPU + {4FA6955C-6860-493F-ABD4-CE327A33EEA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FA6955C-6860-493F-ABD4-CE327A33EEA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FA6955C-6860-493F-ABD4-CE327A33EEA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FA6955C-6860-493F-ABD4-CE327A33EEA3}.Release|Any CPU.Build.0 = Release|Any CPU + {C5043453-5FB8-4796-9A80-C4C766F2CB62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5043453-5FB8-4796-9A80-C4C766F2CB62}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5043453-5FB8-4796-9A80-C4C766F2CB62}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5043453-5FB8-4796-9A80-C4C766F2CB62}.Release|Any CPU.Build.0 = Release|Any CPU + {044C3108-FE79-460A-9C31-A03C30228836}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {044C3108-FE79-460A-9C31-A03C30228836}.Debug|Any CPU.Build.0 = Debug|Any CPU + {044C3108-FE79-460A-9C31-A03C30228836}.Release|Any CPU.ActiveCfg = Release|Any CPU + {044C3108-FE79-460A-9C31-A03C30228836}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} @@ -557,5 +575,8 @@ Global {FAF438E1-B771-418B-83DD-A0F959C82D97} = {873BFC3E-63C2-4495-A503-5EC05DCD84E4} {D64F1240-0110-42E1-AE59-E95AEC8A2900} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} {199FC97A-682B-4C7B-A560-1032B16AEAC4} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} + {4FA6955C-6860-493F-ABD4-CE327A33EEA3} = {873BFC3E-63C2-4495-A503-5EC05DCD84E4} + {C5043453-5FB8-4796-9A80-C4C766F2CB62} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} + {044C3108-FE79-460A-9C31-A03C30228836} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} EndGlobalSection EndGlobal diff --git a/src/common/Elsa.Testing.Shared/ServiceProviderExtensions.cs b/src/common/Elsa.Testing.Shared/ServiceProviderExtensions.cs index 5217b8ea3..21c5150f9 100644 --- a/src/common/Elsa.Testing.Shared/ServiceProviderExtensions.cs +++ b/src/common/Elsa.Testing.Shared/ServiceProviderExtensions.cs @@ -38,9 +38,14 @@ public static class ServiceProviderExtensions { var json = await File.ReadAllTextAsync(fileName); var serializer = services.GetRequiredService(); - var workflowDefinitionRequest = serializer.Deserialize(json); + var model = serializer.Deserialize(json); - workflowDefinitionRequest.Publish = true; + var workflowDefinitionRequest = new SaveWorkflowDefinitionRequest + { + Model = model, + Publish = true + }; + var workflowDefinitionImporter = services.GetRequiredService(); return await workflowDefinitionImporter.ImportAsync(workflowDefinitionRequest); } diff --git a/src/designer/designer_packages/elsa-workflows-designer/src/modules/workflow-definitions/services/api.ts b/src/designer/designer_packages/elsa-workflows-designer/src/modules/workflow-definitions/services/api.ts index 087d21bf5..fc1ed20bb 100644 --- a/src/designer/designer_packages/elsa-workflows-designer/src/modules/workflow-definitions/services/api.ts +++ b/src/designer/designer_packages/elsa-workflows-designer/src/modules/workflow-definitions/services/api.ts @@ -49,7 +49,7 @@ export class WorkflowDefinitionsApi { //TODO: Written as a workaround for different server and client models. //To be deleted after the port model on backend is updated. const requestClone = cloneDeep(request); - removeGuidsFromPortNames(requestClone.root); + removeGuidsFromPortNames(requestClone.model.root); const httpClient = await this.getHttpClient(); const response = await httpClient.post('workflow-definitions', requestClone); @@ -179,19 +179,8 @@ export class WorkflowDefinitionsApi { export interface SaveWorkflowDefinitionRequest { - definitionId: string; - version?: number; - name?: string; - description?: string; + model: WorkflowDefinition; publish: boolean; - root?: Activity; - variables?: Array; - inputs?: Array; - outputs?: Array; - outcomes?: Array; - options?: WorkflowOptions; - usableAsActivity?: boolean; - customProperties?: Map; } export interface BaseManyWorkflowDefinitionRequest { diff --git a/src/designer/designer_packages/elsa-workflows-designer/src/modules/workflow-definitions/services/manager.ts b/src/designer/designer_packages/elsa-workflows-designer/src/modules/workflow-definitions/services/manager.ts index 7f562b3bf..023dd49c6 100644 --- a/src/designer/designer_packages/elsa-workflows-designer/src/modules/workflow-definitions/services/manager.ts +++ b/src/designer/designer_packages/elsa-workflows-designer/src/modules/workflow-definitions/services/manager.ts @@ -16,19 +16,8 @@ export class WorkflowDefinitionManager { public saveWorkflow = async (definition: WorkflowDefinition, publish: boolean): Promise => { const request: SaveWorkflowDefinitionRequest = { - definitionId: definition.definitionId, - version: definition.version, - name: definition.name, - description: definition.description, - publish: publish, - root: definition.root, - variables: definition.variables, - customProperties: definition.customProperties, - inputs: definition.inputs, - outputs: definition.outputs, - outcomes: definition.outcomes, - options: definition.options, - usableAsActivity: definition.usableAsActivity + model: definition, + publish: publish }; return await this.api.post(request); diff --git a/src/modules/Elsa.Http/Services/HttpBookmarkProcessor.cs b/src/modules/Elsa.Http/Services/HttpBookmarkProcessor.cs index c87761e65..0263974da 100644 --- a/src/modules/Elsa.Http/Services/HttpBookmarkProcessor.cs +++ b/src/modules/Elsa.Http/Services/HttpBookmarkProcessor.cs @@ -2,6 +2,7 @@ using Elsa.Common.Models; using Elsa.Http.Contracts; using Elsa.Workflows.Core.Helpers; using Elsa.Workflows.Core.State; +using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Runtime.Contracts; using Microsoft.AspNetCore.Http; diff --git a/src/modules/Elsa.ProtoActor/Grains/WorkflowGrain.cs b/src/modules/Elsa.ProtoActor/Grains/WorkflowGrain.cs index f6d0714b5..1df2d983a 100644 --- a/src/modules/Elsa.ProtoActor/Grains/WorkflowGrain.cs +++ b/src/modules/Elsa.ProtoActor/Grains/WorkflowGrain.cs @@ -4,6 +4,7 @@ using Elsa.ProtoActor.Mappers; using Elsa.ProtoActor.Protos; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.State; +using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Runtime.Contracts; using Proto; using Proto.Cluster; diff --git a/src/modules/Elsa.ProtoActor/Services/ProtoActorWorkflowRuntime.cs b/src/modules/Elsa.ProtoActor/Services/ProtoActorWorkflowRuntime.cs index 363a534df..6024e8e9a 100644 --- a/src/modules/Elsa.ProtoActor/Services/ProtoActorWorkflowRuntime.cs +++ b/src/modules/Elsa.ProtoActor/Services/ProtoActorWorkflowRuntime.cs @@ -5,6 +5,7 @@ using Elsa.ProtoActor.Mappers; using Elsa.ProtoActor.Protos; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.State; +using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Runtime.Entities; using Proto.Cluster; diff --git a/src/modules/Elsa.WorkflowProviders.FluentStorage/Contracts/IBlobStorageProvider.cs b/src/modules/Elsa.WorkflowProviders.FluentStorage/Contracts/IBlobStorageProvider.cs new file mode 100644 index 000000000..f94ec546b --- /dev/null +++ b/src/modules/Elsa.WorkflowProviders.FluentStorage/Contracts/IBlobStorageProvider.cs @@ -0,0 +1,16 @@ +using FluentStorage.Blobs; + +namespace Elsa.WorkflowProviders.FluentStorage.Contracts; + +/// +/// A provider of . The point of this interface is to provide a wrapper for actual implementations. +/// This prevents collisions when the application uses multiple implementations. +/// +public interface IBlobStorageProvider +{ + /// + /// Gets the . + /// + /// The . + IBlobStorage GetBlobStorage(); +} \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowProviders.FluentStorage/Elsa.WorkflowProviders.FluentStorage.csproj b/src/modules/Elsa.WorkflowProviders.FluentStorage/Elsa.WorkflowProviders.FluentStorage.csproj new file mode 100644 index 000000000..237dbd422 --- /dev/null +++ b/src/modules/Elsa.WorkflowProviders.FluentStorage/Elsa.WorkflowProviders.FluentStorage.csproj @@ -0,0 +1,22 @@ + + + + + + + net6.0;net7.0 + + Provides a workflow definition provider that loads workflow definitions from files. + + elsa module fluentstorage json blob + + + + + + + + + + + diff --git a/src/modules/Elsa.WorkflowProviders.FluentStorage/Extensions/ModuleExtensions.cs b/src/modules/Elsa.WorkflowProviders.FluentStorage/Extensions/ModuleExtensions.cs new file mode 100644 index 000000000..ebe4d2864 --- /dev/null +++ b/src/modules/Elsa.WorkflowProviders.FluentStorage/Extensions/ModuleExtensions.cs @@ -0,0 +1,37 @@ +using Elsa.Features.Services; +using Elsa.WorkflowProviders.FluentStorage.Features; +using FluentStorage.Blobs; +using JetBrains.Annotations; + +// ReSharper disable once CheckNamespace +namespace Elsa.Extensions; + +/// +/// Extension methods for to add the fluent storage workflow definition provider. +/// +[PublicAPI] +public static class ModuleExtensions +{ + /// + /// Adds the fluent storage workflow definition provider. + /// + /// The module. + /// A callback that creates an . + /// The module. + public static IModule UseFluentStorageProvider(this IModule module, Func blobStorage) + { + return module.UseFluentStorageProvider(feature => feature.BlobStorage = blobStorage); + } + + /// + /// Adds the fluent storage workflow definition provider. + /// + /// The module. + /// The configuration delegate. + /// The module. + public static IModule UseFluentStorageProvider(this IModule module, Action? configure = default) + { + module.Use(configure); + return module; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowProviders.FluentStorage/Features/FluentStorageFeature.cs b/src/modules/Elsa.WorkflowProviders.FluentStorage/Features/FluentStorageFeature.cs new file mode 100644 index 000000000..67ccc4a90 --- /dev/null +++ b/src/modules/Elsa.WorkflowProviders.FluentStorage/Features/FluentStorageFeature.cs @@ -0,0 +1,46 @@ +using System.Reflection; +using Elsa.Features.Abstractions; +using Elsa.Features.Attributes; +using Elsa.Features.Services; +using Elsa.WorkflowProviders.FluentStorage.Contracts; +using Elsa.WorkflowProviders.FluentStorage.Services; +using Elsa.Workflows.Management.Features; +using FluentStorage; +using FluentStorage.Blobs; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.WorkflowProviders.FluentStorage.Features; + +/// +/// A feature that enables the FluentStorage workflow definition provider. +/// +[DependsOn(typeof(WorkflowManagementFeature))] +[PublicAPI] +public class FluentStorageFeature : FeatureBase +{ + /// + public FluentStorageFeature(IModule module) : base(module) + { + } + + /// + /// The blob storage to use. + /// + public Func BlobStorage { get; set; } = _ => StorageFactory.Blobs.DirectoryFiles(GetDefaultWorkflowsDirectory()); + + /// + public override void Apply() + { + Services.AddSingleton(sp => new BlobStorageProvider(BlobStorage(sp))); + Services.AddWorkflowDefinitionProvider(); + } + + private static string GetDefaultWorkflowsDirectory() + { + var entryAssemblyDir = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)!; + var directory = Path.Combine(entryAssemblyDir, "Workflows"); + return directory; + } + +} \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowProviders.FluentStorage/FodyWeavers.xml b/src/modules/Elsa.WorkflowProviders.FluentStorage/FodyWeavers.xml new file mode 100644 index 000000000..00e1d9a1c --- /dev/null +++ b/src/modules/Elsa.WorkflowProviders.FluentStorage/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowProviders.FluentStorage/Services/BlobStorageProvider.cs b/src/modules/Elsa.WorkflowProviders.FluentStorage/Services/BlobStorageProvider.cs new file mode 100644 index 000000000..397e5f3c4 --- /dev/null +++ b/src/modules/Elsa.WorkflowProviders.FluentStorage/Services/BlobStorageProvider.cs @@ -0,0 +1,24 @@ +using Elsa.WorkflowProviders.FluentStorage.Contracts; +using FluentStorage.Blobs; + +namespace Elsa.WorkflowProviders.FluentStorage.Services; + +/// +/// A provider of . +/// +public class BlobStorageProvider : IBlobStorageProvider +{ + private readonly IBlobStorage _blobStorage; + + /// + /// Initializes a new instance of the class. + /// + /// The . + public BlobStorageProvider(IBlobStorage blobStorage) + { + _blobStorage = blobStorage; + } + + /// + public IBlobStorage GetBlobStorage() => _blobStorage; +} \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowProviders.FluentStorage/Services/FluentStorageWorkflowDefinitionProvider.cs b/src/modules/Elsa.WorkflowProviders.FluentStorage/Services/FluentStorageWorkflowDefinitionProvider.cs new file mode 100644 index 000000000..29720c389 --- /dev/null +++ b/src/modules/Elsa.WorkflowProviders.FluentStorage/Services/FluentStorageWorkflowDefinitionProvider.cs @@ -0,0 +1,96 @@ +using Elsa.Common.Contracts; +using Elsa.WorkflowProviders.FluentStorage.Contracts; +using Elsa.Workflows.Core.Contracts; +using Elsa.Workflows.Management.Entities; +using Elsa.Workflows.Management.Mappers; +using Elsa.Workflows.Management.Materializers; +using Elsa.Workflows.Management.Models; +using Elsa.Workflows.Runtime.Contracts; +using Elsa.Workflows.Runtime.Models; +using FluentStorage.Blobs; +using JetBrains.Annotations; + +namespace Elsa.WorkflowProviders.FluentStorage.Services; + +/// +/// A workflow definition provider that loads workflow definitions from a storage using FluentStorage (See https://github.com/robinrodricks/FluentStorage). +/// +[PublicAPI] +public class FluentStorageWorkflowDefinitionProvider : IWorkflowDefinitionProvider +{ + private readonly IBlobStorageProvider _blobStorageProvider; + private readonly IActivitySerializer _activitySerializer; + private readonly ISystemClock _systemClock; + private readonly WorkflowDefinitionMapper _workflowDefinitionMapper; + private readonly VariableDefinitionMapper _variableDefinitionMapper; + + /// + /// Initializes a new instance of the class. + /// + public FluentStorageWorkflowDefinitionProvider( + IBlobStorageProvider blobStorageProvider, + IActivitySerializer activitySerializer, + ISystemClock systemClock, + WorkflowDefinitionMapper workflowDefinitionMapper, + VariableDefinitionMapper variableDefinitionMapper) + { + _blobStorageProvider = blobStorageProvider; + _activitySerializer = activitySerializer; + _systemClock = systemClock; + _workflowDefinitionMapper = workflowDefinitionMapper; + _variableDefinitionMapper = variableDefinitionMapper; + } + + /// + public string Name => "FluentStorage"; + + /// + public async ValueTask> GetWorkflowDefinitionsAsync(CancellationToken cancellationToken = default) + { + var options = new ListOptions + { + Recurse = true, + }; + + var blobStorage = _blobStorageProvider.GetBlobStorage(); + var blobs = await blobStorage.ListFilesAsync(options, cancellationToken); + var results = new List(); + + foreach (var blob in blobs) + { + var result = await ReadWorkflowDefinitionAsync(blob, cancellationToken); + results.Add(result); + } + + return results; + } + + private async Task ReadWorkflowDefinitionAsync(Blob blob, CancellationToken cancellationToken) + { + var blobStorage = _blobStorageProvider.GetBlobStorage(); + var workflowJson = await blobStorage.ReadTextAsync(blob.FullPath, cancellationToken: cancellationToken); + var workflowDefinitionModel = _activitySerializer.Deserialize(workflowJson); + var variables = _variableDefinitionMapper.Map(workflowDefinitionModel.Variables).ToList(); + var rootJson = _activitySerializer.Serialize(workflowDefinitionModel.Root!); + + var definition = new WorkflowDefinition + { + Id = workflowDefinitionModel.Id, + DefinitionId = workflowDefinitionModel.DefinitionId, + Version = workflowDefinitionModel.Version, + Name = workflowDefinitionModel.Name, + Description = workflowDefinitionModel.Description, + CustomProperties = workflowDefinitionModel.CustomProperties ?? new Dictionary(), + Variables = variables, + IsLatest = workflowDefinitionModel.IsLatest, + IsPublished = workflowDefinitionModel.IsPublished, + CreatedAt = workflowDefinitionModel.CreatedAt == default ? _systemClock.UtcNow : workflowDefinitionModel.CreatedAt, + MaterializerName = JsonWorkflowMaterializer.MaterializerName, + StringData = rootJson + }; + + var workflow = _workflowDefinitionMapper.Map(definition); + + return new WorkflowDefinitionResult(definition, workflow); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/Scripting/JavaScript/TypeDefinitions/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/Scripting/JavaScript/TypeDefinitions/Endpoint.cs index 9baa8c44a..241329247 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/Scripting/JavaScript/TypeDefinitions/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/Scripting/JavaScript/TypeDefinitions/Endpoint.cs @@ -2,6 +2,7 @@ using System.Text; using Elsa.Common.Models; using Elsa.JavaScript.TypeDefinitions.Contracts; using Elsa.JavaScript.TypeDefinitions.Models; +using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Entities; using Elsa.Workflows.Runtime.Contracts; using FastEndpoints; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs index ee43bfe6d..ef6658f00 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs @@ -1,11 +1,11 @@ using System.Text.Json; using Elsa.Abstractions; using Elsa.Common.Models; -using Elsa.Workflows.Api.Models; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Serialization.Converters; using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Mappers; +using Elsa.Workflows.Management.Models; using Elsa.Workflows.Runtime.Contracts; using Humanizer; @@ -71,6 +71,7 @@ public class Export : ElsaEndpoint definition.UsableAsActivity, definition.IsLatest, definition.IsPublished, + definition.Options, workflow.Root); var serializerOptions = _serializer.CreateOptions(); diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs index 060984f93..7e2f2dbb1 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs @@ -1,10 +1,10 @@ using Elsa.Abstractions; using Elsa.Common.Entities; using Elsa.Common.Models; -using Elsa.Workflows.Api.Mappers; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Serialization.Converters; using Elsa.Workflows.Management.Contracts; +using Elsa.Workflows.Management.Mappers; using Microsoft.AspNetCore.Http; namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Get; @@ -13,11 +13,13 @@ internal class Get : ElsaEndpoint { private readonly IWorkflowDefinitionStore _store; private readonly IApiSerializer _apiSerializer; + private readonly WorkflowDefinitionMapper _mapper; - public Get(IWorkflowDefinitionStore store, IApiSerializer apiSerializer) + public Get(IWorkflowDefinitionStore store, IApiSerializer apiSerializer, WorkflowDefinitionMapper mapper) { _store = store; _apiSerializer = apiSerializer; + _mapper = mapper; } public override void Configure() @@ -44,15 +46,14 @@ internal class Get : ElsaEndpoint await SendNotFoundAsync(cancellationToken); return; } - - var mapper = new WorkflowDefinitionMapper(); - var response = await mapper.FromEntityAsync(definition, cancellationToken); + + var model = await _mapper.MapAsync(definition, cancellationToken); var serializerOptions = _apiSerializer.CreateOptions(); // If the root of composite activities is not requested, exclude them from being serialized. if (!request.IncludeCompositeRoot) serializerOptions.Converters.Add(new JsonIgnoreCompositeRootConverterFactory()); - await HttpContext.Response.WriteAsJsonAsync(response, serializerOptions, cancellationToken); + await HttpContext.Response.WriteAsJsonAsync(model, serializerOptions, cancellationToken); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Import/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Import/Endpoint.cs index 645e365c7..2b5e6eb47 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Import/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Import/Endpoint.cs @@ -1,26 +1,30 @@ using Elsa.Abstractions; -using Elsa.Workflows.Api.Models; using Elsa.Workflows.Management.Contracts; +using Elsa.Workflows.Management.Mappers; using Elsa.Workflows.Management.Models; -using Elsa.Workflows.Runtime.Contracts; +using JetBrains.Annotations; namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Import; /// /// Imports a JSON file containing a workflow definition. /// -internal class Import : ElsaEndpoint +[PublicAPI] +internal class Import : ElsaEndpoint { private readonly IWorkflowDefinitionService _workflowDefinitionService; private readonly IWorkflowDefinitionImporter _workflowDefinitionImporter; + private readonly WorkflowDefinitionMapper _workflowDefinitionMapper; /// public Import( IWorkflowDefinitionService workflowDefinitionService, - IWorkflowDefinitionImporter workflowDefinitionImporter) + IWorkflowDefinitionImporter workflowDefinitionImporter, + WorkflowDefinitionMapper workflowDefinitionMapper) { _workflowDefinitionService = workflowDefinitionService; _workflowDefinitionImporter = workflowDefinitionImporter; + _workflowDefinitionMapper = workflowDefinitionMapper; } /// @@ -32,38 +36,25 @@ internal class Import : ElsaEndpoint - public override async Task HandleAsync(SaveWorkflowDefinitionRequest request, CancellationToken cancellationToken) + public override async Task HandleAsync(WorkflowDefinitionModel model, CancellationToken cancellationToken) { - var definitionId = request.DefinitionId; + var definitionId = model.DefinitionId; var isNew = string.IsNullOrWhiteSpace(definitionId); // Import workflow - var draft = await _workflowDefinitionImporter.ImportAsync(request, cancellationToken); + var saveWorkflowRequest = new SaveWorkflowDefinitionRequest + { + Model = model, + Publish = false, + }; + var draft = await _workflowDefinitionImporter.ImportAsync(saveWorkflowRequest, cancellationToken); - // Materialize the workflow definition for serialization. - var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(draft, cancellationToken); - - var response = new WorkflowDefinitionResponse( - draft.Id, - draft.DefinitionId, - draft.Name, - draft.Description, - draft.CreatedAt, - draft.Version, - request.Variables ?? new List(), - draft.Inputs, - draft.Outputs, - draft.Outcomes, - draft.CustomProperties, - draft.IsLatest, - draft.IsPublished, - draft.UsableAsActivity, - workflow.Root, - draft.Options); + // Map the workflow definition for serialization. + var updatedModel = await _workflowDefinitionMapper.MapAsync(draft, cancellationToken); if (isNew) - await SendCreatedAtAsync(new { DefinitionId = definitionId }, response, cancellation: cancellationToken); + await SendCreatedAtAsync(new { DefinitionId = definitionId }, updatedModel, cancellation: cancellationToken); else - await SendOkAsync(response, cancellationToken); + await SendOkAsync(updatedModel, cancellationToken); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs index 8978d6f1e..2677f9d02 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs @@ -1,6 +1,5 @@ using Elsa.Abstractions; using Elsa.Common.Models; -using Elsa.Workflows.Api.Models; using Elsa.Workflows.Core.Activities; using Elsa.Workflows.Management.Mappers; using Elsa.Workflows.Management.Materializers; @@ -8,7 +7,6 @@ using Elsa.Workflows.Management.Models; using JetBrains.Annotations; using Medallion.Threading; using System.Text.Json; -using Elsa.Workflows.Api.Mappers; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Serialization.Converters; using Elsa.Workflows.Management.Contracts; @@ -17,22 +15,25 @@ using Microsoft.AspNetCore.Http; namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Post; [PublicAPI] -internal class Post : ElsaEndpoint +internal class Post : ElsaEndpoint { private readonly IApiSerializer _serializer; private readonly IWorkflowDefinitionPublisher _workflowDefinitionPublisher; private readonly VariableDefinitionMapper _variableDefinitionMapper; + private readonly WorkflowDefinitionMapper _workflowDefinitionMapper; private readonly IDistributedLockProvider _distributedLockProvider; public Post( IApiSerializer serializer, IWorkflowDefinitionPublisher workflowDefinitionPublisher, VariableDefinitionMapper variableDefinitionMapper, + WorkflowDefinitionMapper workflowDefinitionMapper, IDistributedLockProvider distributedLockProvider) { _serializer = serializer; _workflowDefinitionPublisher = workflowDefinitionPublisher; _variableDefinitionMapper = variableDefinitionMapper; + _workflowDefinitionMapper = workflowDefinitionMapper; _distributedLockProvider = distributedLockProvider; } @@ -44,7 +45,8 @@ internal class Post : ElsaEndpoint(); - var outputs = request.Outputs ?? new List(); - var outcomes = request.Outcomes ?? new List(); + var variables = _variableDefinitionMapper.Map(model.Variables).ToList(); + var inputs = model.Inputs ?? new List(); + var outputs = model.Outputs ?? new List(); + var outcomes = model.Outcomes ?? new List(); draft!.StringData = stringData; draft.MaterializerName = JsonWorkflowMaterializer.MaterializerName; - draft.Name = request.Name?.Trim(); - draft.Description = request.Description?.Trim(); - draft.CustomProperties = request.CustomProperties ?? new Dictionary(); + draft.Name = model.Name?.Trim(); + draft.Description = model.Description?.Trim(); + draft.CustomProperties = model.CustomProperties ?? new Dictionary(); draft.Variables = variables; draft.Inputs = inputs; draft.Outputs = outputs; draft.Outcomes = outcomes; - draft.Options = request.Options; - draft.UsableAsActivity = request.UsableAsActivity; + draft.Options = model.Options; + draft.UsableAsActivity = model.UsableAsActivity; draft = request.Publish ? await _workflowDefinitionPublisher.PublishAsync(draft, cancellationToken) : await _workflowDefinitionPublisher.SaveDraftAsync(draft, cancellationToken); - var response = await Map.FromEntityAsync(draft, cancellationToken); + var response = await _workflowDefinitionMapper.MapAsync(draft, cancellationToken); if (isNew) await SendCreatedAtAsync(new { definitionId }, response, cancellation: cancellationToken); diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs index 902ead6d1..930fa562a 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs @@ -1,27 +1,29 @@ using Elsa.Abstractions; using Elsa.Common.Models; -using Elsa.Workflows.Api.Mappers; -using Elsa.Workflows.Api.Models; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Serialization.Converters; using Elsa.Workflows.Management.Contracts; +using Elsa.Workflows.Management.Mappers; +using Elsa.Workflows.Management.Models; using JetBrains.Annotations; using Microsoft.AspNetCore.Http; namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Publish; [PublicAPI] -internal class Publish : ElsaEndpoint +internal class Publish : ElsaEndpoint { private readonly IWorkflowDefinitionStore _store; private readonly IWorkflowDefinitionPublisher _workflowDefinitionPublisher; private readonly IApiSerializer _serializer; + private readonly WorkflowDefinitionMapper _workflowDefinitionMapper; - public Publish(IWorkflowDefinitionStore store, IWorkflowDefinitionPublisher workflowDefinitionPublisher, IApiSerializer serializer) + public Publish(IWorkflowDefinitionStore store, IWorkflowDefinitionPublisher workflowDefinitionPublisher, IApiSerializer serializer, WorkflowDefinitionMapper workflowDefinitionMapper) { _store = store; _workflowDefinitionPublisher = workflowDefinitionPublisher; _serializer = serializer; + _workflowDefinitionMapper = workflowDefinitionMapper; } public override void Configure() @@ -55,7 +57,7 @@ internal class Publish : ElsaEndpoint +internal class Retract : ElsaEndpoint { private readonly IWorkflowDefinitionStore _store; private readonly IWorkflowDefinitionPublisher _workflowDefinitionPublisher; + private readonly WorkflowDefinitionMapper _workflowDefinitionMapper; - public Retract(IWorkflowDefinitionStore store, IWorkflowDefinitionPublisher workflowDefinitionPublisher) + public Retract(IWorkflowDefinitionStore store, IWorkflowDefinitionPublisher workflowDefinitionPublisher, WorkflowDefinitionMapper workflowDefinitionMapper) { _store = store; _workflowDefinitionPublisher = workflowDefinitionPublisher; + _workflowDefinitionMapper = workflowDefinitionMapper; } public override void Configure() @@ -49,7 +51,7 @@ internal class Retract : ElsaEndpoint -{ - public override async Task FromEntityAsync(WorkflowDefinition entity, CancellationToken cancellationToken = default) - { - var workflowDefinitionService = Resolve(); - var variableDefinitionMapper = Resolve(); - var workflow = await workflowDefinitionService.MaterializeWorkflowAsync(entity, cancellationToken); - var variables = variableDefinitionMapper.Map(workflow.Variables).ToList(); - - return new( - entity.Id, - entity.DefinitionId, - entity.Name, - entity.Description, - entity.CreatedAt, - entity.Version, - variables, - entity.Inputs, - entity.Outputs, - entity.Outcomes, - entity.CustomProperties, - entity.IsLatest, - entity.IsPublished, - entity.UsableAsActivity, - workflow.Root, - workflow.Options); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Models/WorkflowDefinitionModel.cs b/src/modules/Elsa.Workflows.Api/Models/WorkflowDefinitionModel.cs deleted file mode 100644 index 87da33042..000000000 --- a/src/modules/Elsa.Workflows.Api/Models/WorkflowDefinitionModel.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Elsa.Workflows.Core.Contracts; -using Elsa.Workflows.Management.Models; - -namespace Elsa.Workflows.Api.Models; - -internal record WorkflowDefinitionModel( - string Id, - string DefinitionId, - string? Name, - string? Description, - DateTimeOffset CreatedAt, - int Version, - ICollection Variables, - ICollection Inputs, - ICollection Outputs, - ICollection Outcomes, - IDictionary Metadata, - bool? UsableAsActivity, - bool IsLatest, - bool IsPublished, - IActivity Root -); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Models/WorkflowDefinitionResponse.cs b/src/modules/Elsa.Workflows.Api/Models/WorkflowDefinitionResponse.cs index a63ecc9f6..a930ad85e 100644 --- a/src/modules/Elsa.Workflows.Api/Models/WorkflowDefinitionResponse.cs +++ b/src/modules/Elsa.Workflows.Api/Models/WorkflowDefinitionResponse.cs @@ -1,61 +1,61 @@ -using Elsa.Workflows.Core.Contracts; -using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Management.Models; - -namespace Elsa.Workflows.Api.Models; - -internal class WorkflowDefinitionResponse -{ - public WorkflowDefinitionResponse( - string id, - string definitionId, - string? name, - string? description, - DateTimeOffset createdAt, - int version, - ICollection variables, - ICollection inputs, - ICollection outputs, - ICollection outcomes, - IDictionary customProperties, - bool isLatest, - bool isPublished, - bool? usableAsActivity, - IActivity root, - WorkflowOptions? options) - { - Id = id; - DefinitionId = definitionId; - Name = name; - Description = description; - CreatedAt = createdAt; - Version = version; - Variables = variables; - Inputs = inputs; - Outputs = outputs; - Outcomes = outcomes; - CustomProperties = customProperties; - IsLatest = isLatest; - IsPublished = isPublished; - UsableAsActivity = usableAsActivity; - Root = root; - Options = options; - } - - public string Id { get; } - public string DefinitionId { get; } - public string? Name { get; } - public string? Description { get; } - public DateTimeOffset CreatedAt { get; } - public int Version { get; } - public ICollection Variables { get; } - public ICollection Inputs { get; } - public ICollection Outputs { get; } - public ICollection Outcomes { get; } - public IDictionary CustomProperties { get; } - public bool IsLatest { get; } - public bool IsPublished { get; } - public bool? UsableAsActivity { get; } - public IActivity Root { get; } - public WorkflowOptions? Options { get; set; } -} \ No newline at end of file +// using Elsa.Workflows.Core.Contracts; +// using Elsa.Workflows.Core.Models; +// using Elsa.Workflows.Management.Models; +// +// namespace Elsa.Workflows.Api.Models; +// +// internal class WorkflowDefinitionResponse +// { +// public WorkflowDefinitionResponse( +// string id, +// string definitionId, +// string? name, +// string? description, +// DateTimeOffset createdAt, +// int version, +// ICollection variables, +// ICollection inputs, +// ICollection outputs, +// ICollection outcomes, +// IDictionary customProperties, +// bool isLatest, +// bool isPublished, +// bool? usableAsActivity, +// IActivity root, +// WorkflowOptions? options) +// { +// Id = id; +// DefinitionId = definitionId; +// Name = name; +// Description = description; +// CreatedAt = createdAt; +// Version = version; +// Variables = variables; +// Inputs = inputs; +// Outputs = outputs; +// Outcomes = outcomes; +// CustomProperties = customProperties; +// IsLatest = isLatest; +// IsPublished = isPublished; +// UsableAsActivity = usableAsActivity; +// Root = root; +// Options = options; +// } +// +// public string Id { get; } +// public string DefinitionId { get; } +// public string? Name { get; } +// public string? Description { get; } +// public DateTimeOffset CreatedAt { get; } +// public int Version { get; } +// public ICollection Variables { get; } +// public ICollection Inputs { get; } +// public ICollection Outputs { get; } +// public ICollection Outcomes { get; } +// public IDictionary CustomProperties { get; } +// public bool IsLatest { get; } +// public bool IsPublished { get; } +// public bool? UsableAsActivity { get; } +// public IActivity Root { get; } +// public WorkflowOptions? Options { get; set; } +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Contracts/IWorkflowDefinitionImporter.cs b/src/modules/Elsa.Workflows.Management/Contracts/IWorkflowDefinitionImporter.cs index cb45a3f84..faac35409 100644 --- a/src/modules/Elsa.Workflows.Management/Contracts/IWorkflowDefinitionImporter.cs +++ b/src/modules/Elsa.Workflows.Management/Contracts/IWorkflowDefinitionImporter.cs @@ -11,7 +11,7 @@ namespace Elsa.Workflows.Management.Contracts /// /// Imports a workflow definition. /// - /// A DTO representing the workflow definition to import. + /// The request. /// The cancellation token. /// The imported workflow definition. Task ImportAsync(SaveWorkflowDefinitionRequest request, CancellationToken cancellationToken = default); diff --git a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowDefinitionService.cs b/src/modules/Elsa.Workflows.Management/Contracts/IWorkflowDefinitionService.cs similarity index 94% rename from src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowDefinitionService.cs rename to src/modules/Elsa.Workflows.Management/Contracts/IWorkflowDefinitionService.cs index f40f2b0af..69ea89587 100644 --- a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowDefinitionService.cs +++ b/src/modules/Elsa.Workflows.Management/Contracts/IWorkflowDefinitionService.cs @@ -2,7 +2,7 @@ using Elsa.Common.Models; using Elsa.Workflows.Core.Models; using Elsa.Workflows.Management.Entities; -namespace Elsa.Workflows.Runtime.Contracts; +namespace Elsa.Workflows.Management.Contracts; /// /// Manages materialization of to objects. diff --git a/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs b/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs index c5616d3fb..73ee5b174 100644 --- a/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs +++ b/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs @@ -148,6 +148,7 @@ public class WorkflowManagementFeature : FeatureBase .AddSingleton() .AddSingleton() .AddActivityProvider() + .AddSingleton() .AddSingleton() ; diff --git a/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs b/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs new file mode 100644 index 000000000..570e4af71 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs @@ -0,0 +1,76 @@ +using Elsa.Workflows.Core.Contracts; +using Elsa.Workflows.Core.Models; +using Elsa.Workflows.Management.Contracts; +using Elsa.Workflows.Management.Entities; +using Elsa.Workflows.Management.Models; + +namespace Elsa.Workflows.Management.Mappers; + +/// +/// Maps to and from and . +/// +public class WorkflowDefinitionMapper +{ + private readonly IActivitySerializer _activitySerializer; + private readonly IWorkflowDefinitionService _workflowDefinitionService; + private readonly VariableDefinitionMapper _variableDefinitionMapper; + + /// + /// Initializes a new instance of the class. + /// + public WorkflowDefinitionMapper(IActivitySerializer activitySerializer, IWorkflowDefinitionService workflowDefinitionService, VariableDefinitionMapper variableDefinitionMapper) + { + _activitySerializer = activitySerializer; + _workflowDefinitionService = workflowDefinitionService; + _variableDefinitionMapper = variableDefinitionMapper; + } + + /// + /// Maps a to a . + /// + /// The source . + /// The mapped . + public Workflow Map(WorkflowDefinition source) + { + var root = _activitySerializer.Deserialize(source.StringData!); + + return new( + new WorkflowIdentity(source.DefinitionId, source.Version, source.Id), + new WorkflowPublication(source.IsLatest, source.IsPublished), + new WorkflowMetadata(source.Name, source.Description, source.CreatedAt), + source.Options, + root, + source.Variables, + source.CustomProperties); + } + + /// + /// Maps a to a . + /// + /// The source . + /// The cancellation token. + /// The mapped . + public async Task MapAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default) + { + var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(workflowDefinition, cancellationToken); + var variables = _variableDefinitionMapper.Map(workflow.Variables).ToList(); + + return new( + workflowDefinition.Id, + workflowDefinition.DefinitionId, + workflowDefinition.Name, + workflowDefinition.Description, + workflowDefinition.CreatedAt, + workflowDefinition.Version, + variables, + workflowDefinition.Inputs, + workflowDefinition.Outputs, + workflowDefinition.Outcomes, + workflowDefinition.CustomProperties, + workflowDefinition.UsableAsActivity, + workflowDefinition.IsLatest, + workflowDefinition.IsPublished, + workflow.Options, + workflow.Root); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Materializers/JsonWorkflowMaterializer.cs b/src/modules/Elsa.Workflows.Management/Materializers/JsonWorkflowMaterializer.cs index 8f9e21222..27dd276bb 100644 --- a/src/modules/Elsa.Workflows.Management/Materializers/JsonWorkflowMaterializer.cs +++ b/src/modules/Elsa.Workflows.Management/Materializers/JsonWorkflowMaterializer.cs @@ -1,7 +1,7 @@ -using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Models; using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Entities; +using Elsa.Workflows.Management.Mappers; namespace Elsa.Workflows.Management.Materializers; @@ -10,7 +10,7 @@ namespace Elsa.Workflows.Management.Materializers; /// public class JsonWorkflowMaterializer : IWorkflowMaterializer { - private readonly IActivitySerializer _activitySerializer; + private readonly WorkflowDefinitionMapper _workflowDefinitionMapper; /// /// The name of the materializer. @@ -23,9 +23,9 @@ public class JsonWorkflowMaterializer : IWorkflowMaterializer /// /// Initializes a new instance of the class. /// - public JsonWorkflowMaterializer(IActivitySerializer activitySerializer) + public JsonWorkflowMaterializer(WorkflowDefinitionMapper workflowDefinitionMapper) { - _activitySerializer = activitySerializer; + _workflowDefinitionMapper = workflowDefinitionMapper; } /// @@ -35,17 +35,5 @@ public class JsonWorkflowMaterializer : IWorkflowMaterializer return new ValueTask(workflow); } - private Workflow ToWorkflow(WorkflowDefinition definition) - { - var root = _activitySerializer.Deserialize(definition.StringData!); - - return new( - new WorkflowIdentity(definition.DefinitionId, definition.Version, definition.Id), - new WorkflowPublication(definition.IsLatest, definition.IsPublished), - new WorkflowMetadata(definition.Name, definition.Description, definition.CreatedAt), - definition.Options, - root, - definition.Variables, - definition.CustomProperties); - } + private Workflow ToWorkflow(WorkflowDefinition definition) => _workflowDefinitionMapper.Map(definition); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Models/SaveWorkflowDefinitionRequest.cs b/src/modules/Elsa.Workflows.Management/Models/SaveWorkflowDefinitionRequest.cs index 35b834990..47a82b4e3 100644 --- a/src/modules/Elsa.Workflows.Management/Models/SaveWorkflowDefinitionRequest.cs +++ b/src/modules/Elsa.Workflows.Management/Models/SaveWorkflowDefinitionRequest.cs @@ -1,21 +1,18 @@ -using Elsa.Workflows.Core.Contracts; -using Elsa.Workflows.Core.Models; - -namespace Elsa.Workflows.Management.Models +namespace Elsa.Workflows.Management.Models { + /// + /// Represents a request to save a workflow definition. + /// public class SaveWorkflowDefinitionRequest { - public string? DefinitionId { get; set; } - public string? Name { get; set; } - public string? Description { get; set; } - public ICollection? Variables { get; set; } - public ICollection? Inputs { get; set; } - public ICollection? Outputs { get; set; } - public ICollection? Outcomes { get; set; } - public IDictionary? CustomProperties { get; set; } - public IActivity? Root { get; set; } - public WorkflowOptions? Options { get; set; } + /// + /// The workflow definition to save. + /// + public WorkflowDefinitionModel Model { get; set; } = default!; + + /// + /// Whether the workflow definition should be published. + /// public bool Publish { get; set; } - public bool? UsableAsActivity { get; set; } } } diff --git a/src/modules/Elsa.Workflows.Management/Models/WorkflowDefinitionModel.cs b/src/modules/Elsa.Workflows.Management/Models/WorkflowDefinitionModel.cs new file mode 100644 index 000000000..c02807005 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Models/WorkflowDefinitionModel.cs @@ -0,0 +1,66 @@ +using Elsa.Workflows.Core.Contracts; +using Elsa.Workflows.Core.Models; +using JetBrains.Annotations; + +namespace Elsa.Workflows.Management.Models; + +/// +/// Represents a serializable workflow definition. +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// The type of to apply when new instances are requested to be created. +/// +[PublicAPI] +public record WorkflowDefinitionModel( + string Id, + string DefinitionId, + string? Name, + string? Description, + DateTimeOffset CreatedAt, + int Version, + ICollection? Variables, + ICollection? Inputs, + ICollection? Outputs, + ICollection? Outcomes, + IDictionary? CustomProperties, + bool? UsableAsActivity, + bool IsLatest, + bool IsPublished, + WorkflowOptions? Options, + IActivity? Root +) +{ + /// + public WorkflowDefinitionModel() : this( + default!, + default!, + default!, + default!, + default!, + default!, + default!, + default!, + default!, + default!, + default!, + default!, + default!, + default!, + default!, + default!) + { + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionImporter.cs b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionImporter.cs index c717cdddf..afe833344 100644 --- a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionImporter.cs +++ b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionImporter.cs @@ -1,4 +1,5 @@ using Elsa.Common.Models; +using Elsa.Workflows.Core.Activities; using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Entities; using Elsa.Workflows.Management.Mappers; @@ -31,7 +32,8 @@ namespace Elsa.Workflows.Management.Services /// public async Task ImportAsync(SaveWorkflowDefinitionRequest request, CancellationToken cancellationToken = default) { - var definitionId = request.DefinitionId; + var model = request.Model; + var definitionId = model.DefinitionId; // Get a workflow draft version. var draft = !string.IsNullOrWhiteSpace(definitionId) @@ -50,21 +52,21 @@ namespace Elsa.Workflows.Management.Services } // Update the draft with the received model. - var root = request.Root!; + var root = model.Root ?? new Sequence(); var stringData = _serializer.Serialize(root); - var variables = _variableDefinitionMapper.Map(request.Variables).ToList(); + var variables = _variableDefinitionMapper.Map(model.Variables).ToList(); draft!.StringData = stringData; draft.MaterializerName = JsonWorkflowMaterializer.MaterializerName; - draft.Name = request.Name?.Trim(); - draft.Description = request.Description?.Trim(); - draft.CustomProperties = request.CustomProperties ?? new Dictionary(); + draft.Name = model.Name?.Trim(); + draft.Description = model.Description?.Trim(); + draft.CustomProperties = model.CustomProperties ?? new Dictionary(); draft.Variables = variables; - draft.Inputs = request.Inputs ?? new List(); - draft.Outputs = request.Outputs ?? new List(); - draft.Outcomes = request.Outcomes ?? new List(); - draft.Options = request.Options; - draft.UsableAsActivity = request.UsableAsActivity; + draft.Inputs = model.Inputs ?? new List(); + draft.Outputs = model.Outputs ?? new List(); + draft.Outcomes = model.Outcomes ?? new List(); + draft.Options = model.Options; + draft.UsableAsActivity = model.UsableAsActivity; draft = request.Publish ? await _workflowDefinitionPublisher.PublishAsync(draft, cancellationToken) : await _workflowDefinitionPublisher.SaveDraftAsync(draft, cancellationToken); return draft; diff --git a/src/modules/Elsa.Workflows.Runtime/Services/WorkflowDefinitionService.cs b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionService.cs similarity index 85% rename from src/modules/Elsa.Workflows.Runtime/Services/WorkflowDefinitionService.cs rename to src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionService.cs index 1187332b8..17bf37078 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/WorkflowDefinitionService.cs +++ b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionService.cs @@ -4,9 +4,8 @@ using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Models; using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Entities; -using Elsa.Workflows.Runtime.Contracts; -namespace Elsa.Workflows.Runtime.Services; +namespace Elsa.Workflows.Management.Services; /// public class WorkflowDefinitionService : IWorkflowDefinitionService @@ -14,7 +13,7 @@ public class WorkflowDefinitionService : IWorkflowDefinitionService private readonly IWorkflowDefinitionStore _workflowDefinitionStore; private readonly IActivityVisitor _activityVisitor; private readonly IIdentityGraphService _identityGraphService; - private readonly IEnumerable _materializers; + private readonly Func> _materializers; /// /// Constructor. @@ -23,7 +22,7 @@ public class WorkflowDefinitionService : IWorkflowDefinitionService IWorkflowDefinitionStore workflowDefinitionStore, IActivityVisitor activityVisitor, IIdentityGraphService identityGraphService, - IEnumerable materializers) + Func> materializers) { _workflowDefinitionStore = workflowDefinitionStore; _activityVisitor = activityVisitor; @@ -34,7 +33,8 @@ public class WorkflowDefinitionService : IWorkflowDefinitionService /// public async Task MaterializeWorkflowAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default) { - var provider = _materializers.FirstOrDefault(x => x.Name == definition.MaterializerName); + var materializers = _materializers(); + var provider = materializers.FirstOrDefault(x => x.Name == definition.MaterializerName); if (provider == null) throw new Exception("Provider not found"); diff --git a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowDefinitionProvider.cs b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowDefinitionProvider.cs index 6fd2e203f..970613f7c 100644 --- a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowDefinitionProvider.cs +++ b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowDefinitionProvider.cs @@ -1,5 +1,4 @@ -using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Management.Entities; +using Elsa.Workflows.Runtime.Models; namespace Elsa.Workflows.Runtime.Contracts; @@ -8,9 +7,15 @@ namespace Elsa.Workflows.Runtime.Contracts; /// public interface IWorkflowDefinitionProvider { + /// + /// Gets the name of the provider. + /// string Name { get; } + + /// + /// Gets the workflow definitions. + /// + /// The cancellation token. + /// The workflow definitions. ValueTask> GetWorkflowDefinitionsAsync(CancellationToken cancellationToken = default); -} - - -public record WorkflowDefinitionResult(WorkflowDefinition Definition, Workflow Workflow); \ No newline at end of file +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Extensions/DependencyInjectionExtensions.cs b/src/modules/Elsa.Workflows.Runtime/Extensions/DependencyInjectionExtensions.cs index 6cc7a0a3b..c6bcee306 100644 --- a/src/modules/Elsa.Workflows.Runtime/Extensions/DependencyInjectionExtensions.cs +++ b/src/modules/Elsa.Workflows.Runtime/Extensions/DependencyInjectionExtensions.cs @@ -1,9 +1,19 @@ using Elsa.Workflows.Runtime.Contracts; +using Elsa.Workflows.Runtime.Services; // ReSharper disable once CheckNamespace namespace Microsoft.Extensions.DependencyInjection; +/// +/// Provides extension methods for . +/// public static class DependencyInjectionExtensions { + /// + /// Adds the to the service collection. + /// + /// The service collection. + /// The type of the workflow definition provider. + /// The service collection. public static IServiceCollection AddWorkflowDefinitionProvider(this IServiceCollection services) where T : class, IWorkflowDefinitionProvider => services.AddSingleton(); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs index dcd81e421..623cc0d08 100644 --- a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs +++ b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs @@ -6,7 +6,9 @@ using Elsa.Features.Services; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Notifications; using Elsa.Workflows.Core.State; +using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Notifications; +using Elsa.Workflows.Management.Services; using Elsa.Workflows.Runtime.ActivationValidators; using Elsa.Workflows.Runtime.Commands; using Elsa.Workflows.Runtime.Contracts; @@ -136,6 +138,10 @@ public class WorkflowRuntimeFeature : FeatureBase .AddSingleton() .AddSingleton() .AddSingleton() + + // Lazy services. + .AddSingleton>>(sp => sp.GetServices) + .AddSingleton>>(sp => sp.GetServices) // Memory stores. .AddMemoryStore() diff --git a/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs b/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs index 1dd685bb8..5b0debc9f 100644 --- a/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs +++ b/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs @@ -13,7 +13,7 @@ namespace Elsa.Workflows.Runtime.HostedServices; /// public class PopulateWorkflowDefinitionStore : IHostedService { - private readonly IEnumerable _workflowDefinitionProviders; + private readonly Func> _workflowDefinitionProviders; private readonly ITriggerIndexer _triggerIndexer; private readonly IWorkflowDefinitionStore _workflowDefinitionStore; @@ -21,7 +21,7 @@ public class PopulateWorkflowDefinitionStore : IHostedService /// Constructor. /// public PopulateWorkflowDefinitionStore( - IEnumerable workflowDefinitionProviders, + Func> workflowDefinitionProviders, ITriggerIndexer triggerIndexer, IWorkflowDefinitionStore workflowDefinitionStore) { @@ -33,7 +33,8 @@ public class PopulateWorkflowDefinitionStore : IHostedService /// public async Task StartAsync(CancellationToken cancellationToken) { - foreach (var provider in _workflowDefinitionProviders) + var providers = _workflowDefinitionProviders(); + foreach (var provider in providers) { var results = await provider.GetWorkflowDefinitionsAsync(cancellationToken).AsTask().ToList(); diff --git a/src/modules/Elsa.Workflows.Runtime/Models/WorkflowDefinitionResult.cs b/src/modules/Elsa.Workflows.Runtime/Models/WorkflowDefinitionResult.cs new file mode 100644 index 000000000..4dbab13d2 --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Models/WorkflowDefinitionResult.cs @@ -0,0 +1,11 @@ +using Elsa.Workflows.Core.Models; +using Elsa.Workflows.Management.Entities; + +namespace Elsa.Workflows.Runtime.Models; + +/// +/// Represents a workflow definition and its workflow. +/// +/// The workflow definition. +/// The workflow materialized from its workflow definition. +public record WorkflowDefinitionResult(WorkflowDefinition Definition, Workflow Workflow); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/ClrWorkflowDefinitionProvider.cs b/src/modules/Elsa.Workflows.Runtime/Services/ClrWorkflowDefinitionProvider.cs index 27ff528b7..dca422465 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/ClrWorkflowDefinitionProvider.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/ClrWorkflowDefinitionProvider.cs @@ -4,6 +4,7 @@ using Elsa.Workflows.Management.Entities; using Elsa.Workflows.Management.Materializers; using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Runtime.Features; +using Elsa.Workflows.Runtime.Models; using Elsa.Workflows.Runtime.Options; using Microsoft.Extensions.Options; diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultBackgroundActivityInvoker.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultBackgroundActivityInvoker.cs index a7f9baa59..e00940519 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/DefaultBackgroundActivityInvoker.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultBackgroundActivityInvoker.cs @@ -2,6 +2,7 @@ using Elsa.Common.Models; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Models; using Elsa.Workflows.Core.Services; +using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Runtime.Middleware.Activities; using Elsa.Workflows.Runtime.Models; diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowRuntime.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowRuntime.cs index 03825359f..f6165a34f 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowRuntime.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowRuntime.cs @@ -2,6 +2,7 @@ using Elsa.Common.Models; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Models; using Elsa.Workflows.Core.State; +using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Entities; using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Runtime.Entities; diff --git a/src/modules/Elsa.Workflows.Runtime/Services/TriggerIndexer.cs b/src/modules/Elsa.Workflows.Runtime/Services/TriggerIndexer.cs index e4ba533dc..c0e5d717f 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/TriggerIndexer.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/TriggerIndexer.cs @@ -5,6 +5,7 @@ using Elsa.Extensions; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Helpers; using Elsa.Workflows.Core.Models; +using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Entities; using Elsa.Workflows.Runtime.Comparers; using Elsa.Workflows.Runtime.Contracts; diff --git a/src/modules/Elsa.Workflows.Sinks/Services/DefaultWorkflowSinkInvoker.cs b/src/modules/Elsa.Workflows.Sinks/Services/DefaultWorkflowSinkInvoker.cs index 118338e03..862f59a72 100644 --- a/src/modules/Elsa.Workflows.Sinks/Services/DefaultWorkflowSinkInvoker.cs +++ b/src/modules/Elsa.Workflows.Sinks/Services/DefaultWorkflowSinkInvoker.cs @@ -1,5 +1,6 @@ using Elsa.Common.Models; using Elsa.Workflows.Core.State; +using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Sinks.Contracts; using Elsa.Workflows.Sinks.Models; diff --git a/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Elsa.Samples.JsonWorkflowProvider.csproj b/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Elsa.Samples.JsonWorkflowProvider.csproj new file mode 100644 index 000000000..97afeb2ad --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Elsa.Samples.JsonWorkflowProvider.csproj @@ -0,0 +1,22 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + PreserveNewest + + + + diff --git a/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Program.cs b/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Program.cs new file mode 100644 index 000000000..7c9651ccf --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Program.cs @@ -0,0 +1,39 @@ +using Elsa.Extensions; + +var builder = WebApplication.CreateBuilder(args); +var services = builder.Services; + +// Add Elsa services. +services.AddElsa(elsa => elsa + // Configure the workflow runtime. + .UseFluentStorageProvider() + + // Expose API endpoints. + .UseWorkflowsApi() + + // Configure identity so that we can create a default admin user. + .UseIdentity(identity => + { + identity.UseAdminUserProvider(); + identity.TokenOptions = options => + { + options.SigningKey = "secret-token-signing-key"; + options.AccessTokenLifetime = TimeSpan.FromDays(1); + }; + }) + + // Use default authentication (JWT). + .UseDefaultAuthentication(auth => auth.UseAdminApiKey()) +); + +// Configure middleware pipeline. +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) + app.UseDeveloperExceptionPage(); + +// Configure the HTTP request pipeline. +app.UseAuthentication(); +app.UseAuthorization(); +app.UseWorkflowsApi(); +app.Run(); \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Properties/launchSettings.json b/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Properties/launchSettings.json new file mode 100644 index 000000000..6b06392c8 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Properties/launchSettings.json @@ -0,0 +1,37 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:55253", + "sslPort": 44338 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5129", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7230;http://localhost:5129", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Workflows/HelloWorld.json b/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Workflows/HelloWorld.json new file mode 100644 index 000000000..75665461c --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/Workflows/HelloWorld.json @@ -0,0 +1,28 @@ +{ + "id": "hello-world-v1", + "definitionId": "hello-world", + "name": "Hello World", + "isLatest": true, + "isPublished": true, + "version": 1, + "root": { + "id": "Flowchart1", + "type": "Elsa.Flowchart", + "activities": [ + { + "id": "WriteLine1", + "type": "Elsa.WriteLine", + "text": { + "typeName": "String", + "expression": { + "type": "Literal", + "value": "Hello World!" + }, + "memoryReference": { + "id": "WriteLine1:input-1" + } + } + } + ] + } +} \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/appsettings.json b/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.JsonWorkflowProvider/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/samples/console/Elsa.Samples.JsonWorkflows/Elsa.Samples.JsonWorkflows.csproj b/src/samples/console/Elsa.Samples.JsonWorkflows/Elsa.Samples.JsonWorkflows.csproj new file mode 100644 index 000000000..3d416d0aa --- /dev/null +++ b/src/samples/console/Elsa.Samples.JsonWorkflows/Elsa.Samples.JsonWorkflows.csproj @@ -0,0 +1,22 @@ + + + + Exe + net7.0 + enable + enable + false + + + + + + + + + + PreserveNewest + + + + diff --git a/src/samples/console/Elsa.Samples.JsonWorkflows/HelloWorld.json b/src/samples/console/Elsa.Samples.JsonWorkflows/HelloWorld.json new file mode 100644 index 000000000..ec3bf36b0 --- /dev/null +++ b/src/samples/console/Elsa.Samples.JsonWorkflows/HelloWorld.json @@ -0,0 +1,24 @@ +{ + "definitionId": "11e2b350dae8485498c16d1ce3843829", + "name": "Hello World", + "root": { + "id": "Flowchart1", + "type": "Elsa.Flowchart", + "activities": [ + { + "id": "WriteLine1", + "type": "Elsa.WriteLine", + "text": { + "typeName": "String", + "expression": { + "type": "Literal", + "value": "Hello World!" + }, + "memoryReference": { + "id": "WriteLine1:input-1" + } + } + } + ] + } +} \ No newline at end of file diff --git a/src/samples/console/Elsa.Samples.JsonWorkflows/Program.cs b/src/samples/console/Elsa.Samples.JsonWorkflows/Program.cs new file mode 100644 index 000000000..6f7fb5427 --- /dev/null +++ b/src/samples/console/Elsa.Samples.JsonWorkflows/Program.cs @@ -0,0 +1,33 @@ +using Elsa.Extensions; +using Elsa.Testing.Shared; +using Elsa.Workflows.Core.Activities; +using Elsa.Workflows.Core.Contracts; +using Elsa.Workflows.Core.Models; +using Microsoft.Extensions.DependencyInjection; + +// Setup service container. +var services = new ServiceCollection(); + +// Add Elsa services. +services.AddElsa(); + +// Build service container. +var serviceProvider = services.BuildServiceProvider(); + +// Populate registries. This is only necessary for applications that are not using hosted services. +await serviceProvider.PopulateRegistriesAsync(); + +// Import a workflow from a JSON file. +var workflowJson = await File.ReadAllTextAsync("HelloWorld.json"); + +// Get a serializer to deserialize the workflow. +var serializer = serviceProvider.GetRequiredService(); + +// Deserialize the workflow. +var workflow = serializer.Deserialize(workflowJson); + +// Resolve a workflow runner to run the workflow. +var workflowRunner = serviceProvider.GetRequiredService(); + +// Run the workflow. +await workflowRunner.RunAsync(workflow); \ No newline at end of file diff --git a/src/samples/console/Elsa.Samples.JsonWorkflows/Readme.md b/src/samples/console/Elsa.Samples.JsonWorkflows/Readme.md new file mode 100644 index 000000000..3f0545061 --- /dev/null +++ b/src/samples/console/Elsa.Samples.JsonWorkflows/Readme.md @@ -0,0 +1,5 @@ +# JSON workflows + +This sample demonstrates how to load a JSON workflow from a file and then execute it. + +There is another sample in the `aspnet` folder that demonstrates the use of the `JsonWorkflowDefinitionProvider` that provides workflows to the system loaded from JSON files. \ No newline at end of file