From c193b28ba190d622afc9b00fb0967a08bc80529d Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 17 Nov 2020 20:12:18 +0100 Subject: [PATCH] Incremental work on saving and loading workflow definitions --- .editorconfig | 2 + .../Models/ConnectionDefinition.cs | 10 +- src/clients/Elsa.Client/Models/List.cs | 4 +- src/clients/Elsa.Client/Models/PagedList.cs | 11 +- .../Services/IWorkflowDefinitionsApi.cs | 17 +- .../BlazorRuntimeModel.cs | 8 + .../Pages/_Host.cshtml | 2 +- .../Program.cs | 12 +- .../Startup.cs | 8 +- .../ClientApp/public/index.html | 9 +- .../ClientApp/src/index.html | 9 +- .../Models/MenuItem.cs | 13 +- .../Pages/Designer.razor | 5 +- .../Pages/Designer.razor.cs | 52 +++++- .../Pages/Index.razor | 11 +- .../Pages/Workflows.razor | 164 +++++++++--------- .../Pages/Workflows.razor.cs | 18 ++ .../Services/BackgroundWorker.cs | 37 ++++ .../Shared/ActivityPicker.razor | 4 +- .../Shared/ActivityPicker.razor.cs | 2 +- .../Shared/MainLayout.razor | 28 --- .../Shared/NavMenu.razor | 62 ++----- .../Shared/NavMenu.razor.cs | 6 +- .../Shared/WorkflowDesigner.razor.cs | 42 ++++- .../Extensions/ServiceCollectionExtensions.cs | 1 + .../Rpc/ActivityService.cs | 8 +- .../Rpc/WorkflowDefinitionManager.cs | 7 - .../Rpc/WorkflowDefinitionService.cs | 23 +++ .../Rpc/IActivityService.cs | 17 +- .../Rpc/IWorkflowDefinitionService.cs | 32 +++- .../ActivityPropertyDescriptorSurrogate.cs | 4 - .../Surrogates/RuntimeTypeModelExtensions.cs | 3 +- .../Surrogates/VersionOptionsSurrogate.cs | 27 +++ .../Elsa.Samples.HelloWorldConsole/Program.cs | 5 +- .../{Get.cs => GetByDefinitionAndVersion.cs} | 12 +- .../WorkflowDefinitions/GetByVersionId.cs | 46 +++++ .../Endpoints/WorkflowDefinitions/Post.cs | 2 +- 37 files changed, 474 insertions(+), 249 deletions(-) create mode 100644 src/dashboards/blazor/ElsaDashboard.Application.Server/BlazorRuntimeModel.cs create mode 100644 src/dashboards/blazor/ElsaDashboard.Application/Pages/Workflows.razor.cs create mode 100644 src/dashboards/blazor/ElsaDashboard.Application/Services/BackgroundWorker.cs delete mode 100644 src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WorkflowDefinitionManager.cs create mode 100644 src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WorkflowDefinitionService.cs create mode 100644 src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/VersionOptionsSurrogate.cs rename src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/{Get.cs => GetByDefinitionAndVersion.cs} (75%) create mode 100644 src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/GetByVersionId.cs diff --git a/.editorconfig b/.editorconfig index 17ab15ef1..2758d76b2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,7 +9,9 @@ indent_size=4 # ReSharper properties resharper_csharp_max_line_length=240 resharper_keep_user_linebreaks=true +resharper_place_field_attribute_on_same_line=if_owner_is_single_line resharper_space_within_single_line_array_initializer_braces=true +resharper_wrap_before_linq_expression=true # Microsoft .NET properties csharp_new_line_before_members_in_object_initializers=false diff --git a/src/clients/Elsa.Client/Models/ConnectionDefinition.cs b/src/clients/Elsa.Client/Models/ConnectionDefinition.cs index 34f3cf4fd..283af354d 100644 --- a/src/clients/Elsa.Client/Models/ConnectionDefinition.cs +++ b/src/clients/Elsa.Client/Models/ConnectionDefinition.cs @@ -5,10 +5,6 @@ namespace Elsa.Client.Models [DataContract] public class ConnectionDefinition { - public ConnectionDefinition() - { - } - public ConnectionDefinition(string sourceActivityId, string targetActivityId, string outcome) { SourceActivityId = sourceActivityId; @@ -16,8 +12,8 @@ namespace Elsa.Client.Models Outcome = outcome; } - [DataMember(Order = 1)] public string? SourceActivityId { get; set; } - [DataMember(Order = 2)] public string? TargetActivityId { get; set; } - [DataMember(Order = 3)] public string? Outcome { get; set; } + [DataMember(Order = 1)] public string SourceActivityId { get; set; } + [DataMember(Order = 2)] public string TargetActivityId { get; set; } + [DataMember(Order = 3)] public string Outcome { get; set; } } } \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/List.cs b/src/clients/Elsa.Client/Models/List.cs index 58f96f0a5..3e6121495 100644 --- a/src/clients/Elsa.Client/Models/List.cs +++ b/src/clients/Elsa.Client/Models/List.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; +using System.Runtime.Serialization; namespace Elsa.Client.Models { + [DataContract] public class List { - public ICollection Items { get; set; } = new System.Collections.Generic.List(); + [DataMember(Order = 1)] public ICollection Items { get; set; } = new System.Collections.Generic.List(); } } \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/PagedList.cs b/src/clients/Elsa.Client/Models/PagedList.cs index e0fa1f390..9f72932eb 100644 --- a/src/clients/Elsa.Client/Models/PagedList.cs +++ b/src/clients/Elsa.Client/Models/PagedList.cs @@ -1,9 +1,12 @@ -namespace Elsa.Client.Models +using System.Runtime.Serialization; + +namespace Elsa.Client.Models { + [DataContract] public class PagedList : List { - public int? Page { get; set; } - public int? PageSize { get; set; } - public int TotalCount { get; set; } + [DataMember(Order = 1)] public int? Page { get; set; } + [DataMember(Order = 2)] public int? PageSize { get; set; } + [DataMember(Order = 3)] public int TotalCount { get; set; } } } \ No newline at end of file diff --git a/src/clients/Elsa.Client/Services/IWorkflowDefinitionsApi.cs b/src/clients/Elsa.Client/Services/IWorkflowDefinitionsApi.cs index dc4441bfd..beceb20bf 100644 --- a/src/clients/Elsa.Client/Services/IWorkflowDefinitionsApi.cs +++ b/src/clients/Elsa.Client/Services/IWorkflowDefinitionsApi.cs @@ -7,16 +7,19 @@ namespace Elsa.Client.Services { public interface IWorkflowDefinitionsApi { - [Get("/v1/workflow-definitions/{workflowDefinitionId}")] - Task GetAsync(string workflowDefinitionId, VersionOptions? versionOptions = default, CancellationToken cancellationToken = default); + [Get("/v1/workflow-definitions/{workflowDefinitionId}/{versionOptions}")] + Task GetByDefinitionAndVersionAsync(string workflowDefinitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default); + [Get("/v1/workflow-definitions/{workflowDefinitionVersionId}")] + Task GetByVersionIdAsync(string workflowDefinitionVersionId, CancellationToken cancellationToken = default); + [Get("/v1/workflow-definitions")] - Task ListAsync(string workflowDefinitionId, VersionOptions? versionOptions = default, CancellationToken cancellationToken = default); - + Task> ListAsync(int? page = default, int? pageSize = default, VersionOptions? versionOptions = default, CancellationToken cancellationToken = default); + [Post("/v1/workflow-definitions")] - Task PostAsync([Body(BodySerializationMethod.Serialized)]PostWorkflowDefinitionRequest request, CancellationToken cancellationToken = default); - + Task PostAsync([Body(BodySerializationMethod.Serialized)] PostWorkflowDefinitionRequest request, CancellationToken cancellationToken = default); + [Post("/v1/workflow-definitions/{workflowDefinitionId}")] - Task PostAsync(string workflowDefinitionId, [Body(BodySerializationMethod.Serialized)]PostWorkflowDefinitionRequest request, CancellationToken cancellationToken = default); + Task PostAsync(string workflowDefinitionId, [Body(BodySerializationMethod.Serialized)] PostWorkflowDefinitionRequest request, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application.Server/BlazorRuntimeModel.cs b/src/dashboards/blazor/ElsaDashboard.Application.Server/BlazorRuntimeModel.cs new file mode 100644 index 000000000..5f8784399 --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Application.Server/BlazorRuntimeModel.cs @@ -0,0 +1,8 @@ +namespace ElsaDashboard.Application.Server +{ + public enum BlazorRuntimeModel + { + Server, + Browser + } +} \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application.Server/Pages/_Host.cshtml b/src/dashboards/blazor/ElsaDashboard.Application.Server/Pages/_Host.cshtml index 6676d0739..9a7818e00 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application.Server/Pages/_Host.cshtml +++ b/src/dashboards/blazor/ElsaDashboard.Application.Server/Pages/_Host.cshtml @@ -36,7 +36,7 @@ 🗙 -@if (Program.UseBlazorServer) +@if (Program.RuntimeModel == BlazorRuntimeModel.Server) { } diff --git a/src/dashboards/blazor/ElsaDashboard.Application.Server/Program.cs b/src/dashboards/blazor/ElsaDashboard.Application.Server/Program.cs index bc2d0939f..dbc88da5a 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application.Server/Program.cs +++ b/src/dashboards/blazor/ElsaDashboard.Application.Server/Program.cs @@ -6,9 +6,8 @@ namespace ElsaDashboard.Application.Server { public class Program { - public static bool UseBlazorServer = true; - public static bool UseBlazorWebAssembly => !UseBlazorServer; - public static RenderMode RenderMode => UseBlazorServer ? RenderMode.ServerPrerendered: RenderMode.WebAssemblyPrerendered; + public static BlazorRuntimeModel RuntimeModel => BlazorRuntimeModel.Browser; + public static RenderMode RenderMode => RuntimeModel == BlazorRuntimeModel.Server ? RenderMode.ServerPrerendered : RenderMode.WebAssemblyPrerendered; public static void Main(string[] args) { @@ -17,9 +16,6 @@ namespace ElsaDashboard.Application.Server public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); } -} +} \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application.Server/Startup.cs b/src/dashboards/blazor/ElsaDashboard.Application.Server/Startup.cs index 6f4666747..afcf670ac 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application.Server/Startup.cs +++ b/src/dashboards/blazor/ElsaDashboard.Application.Server/Startup.cs @@ -25,7 +25,7 @@ namespace ElsaDashboard.Application.Server services.AddElsaDashboardUI(); services.AddElsaDashboardBackend(options => options.ServerUrl = new Uri("https://localhost:11000")); - if (Program.UseBlazorServer) + if (Program.RuntimeModel == BlazorRuntimeModel.Server) services.AddServerSideBlazor(options => { options.DetailedErrors = !Environment.IsProduction(); @@ -40,7 +40,7 @@ namespace ElsaDashboard.Application.Server { app.UseDeveloperExceptionPage(); - if(Program.UseBlazorWebAssembly) + if (Program.RuntimeModel == BlazorRuntimeModel.Browser) app.UseWebAssemblyDebugging(); } else @@ -50,7 +50,7 @@ namespace ElsaDashboard.Application.Server app.UseHsts(); } - if(Program.UseBlazorWebAssembly) + if (Program.RuntimeModel == BlazorRuntimeModel.Browser) app.UseBlazorFrameworkFiles(); app.UseStaticFiles(); @@ -58,7 +58,7 @@ namespace ElsaDashboard.Application.Server app.UseElsaGrpcServices(); app.UseEndpoints(endpoints => { - if(Program.UseBlazorServer) + if (Program.RuntimeModel == BlazorRuntimeModel.Server) endpoints.MapBlazorHub(); endpoints.MapFallbackToPage("/_Host"); diff --git a/src/dashboards/blazor/ElsaDashboard.Application/ClientApp/public/index.html b/src/dashboards/blazor/ElsaDashboard.Application/ClientApp/public/index.html index 0d0423943..192580807 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/ClientApp/public/index.html +++ b/src/dashboards/blazor/ElsaDashboard.Application/ClientApp/public/index.html @@ -266,7 +266,14 @@
- + +
+
+

+ Home +

+
+
diff --git a/src/dashboards/blazor/ElsaDashboard.Application/ClientApp/src/index.html b/src/dashboards/blazor/ElsaDashboard.Application/ClientApp/src/index.html index d1ca9cb6a..98e524ca8 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/ClientApp/src/index.html +++ b/src/dashboards/blazor/ElsaDashboard.Application/ClientApp/src/index.html @@ -1,3 +1,10 @@ - + +
+
+

+ Home +

+
+
\ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Models/MenuItem.cs b/src/dashboards/blazor/ElsaDashboard.Application/Models/MenuItem.cs index dca8da307..8ef7f8652 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/Models/MenuItem.cs +++ b/src/dashboards/blazor/ElsaDashboard.Application/Models/MenuItem.cs @@ -1,16 +1,19 @@ -namespace ElsaDashboard.Application.Models +using Microsoft.AspNetCore.Components.Routing; + +namespace ElsaDashboard.Application.Models { public class MenuItem { - public MenuItem(string text, string url, string icon) + public MenuItem(string text, string url, string icon, NavLinkMatch match = NavLinkMatch.Prefix) { Text = text; Url = url; Icon = icon; } - public string Text { get; set; } - public string Icon { get; set; } - public string Url { get; set; } + public string Text { get; init; } + public string Icon { get; init; } + public string Url { get; init; } + public NavLinkMatch Match { get; init; } = NavLinkMatch.Prefix; } } \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Pages/Designer.razor b/src/dashboards/blazor/ElsaDashboard.Application/Pages/Designer.razor index 7a168ace5..10e967850 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/Pages/Designer.razor +++ b/src/dashboards/blazor/ElsaDashboard.Application/Pages/Designer.razor @@ -1,4 +1,5 @@ -@page "/designer" +@page "/workflows/designer" +@page "/workflows/{workflowDefinitionVersionId}/designer"

@@ -13,4 +14,4 @@

- \ No newline at end of file + \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Pages/Designer.razor.cs b/src/dashboards/blazor/ElsaDashboard.Application/Pages/Designer.razor.cs index d27d81f02..e51a26993 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/Pages/Designer.razor.cs +++ b/src/dashboards/blazor/ElsaDashboard.Application/Pages/Designer.razor.cs @@ -1,6 +1,56 @@ -namespace ElsaDashboard.Application.Pages +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Elsa.Client.Models; +using ElsaDashboard.Application.Models; +using ElsaDashboard.Shared.Rpc; +using Microsoft.AspNetCore.Components; + +namespace ElsaDashboard.Application.Pages { partial class Designer { + [Parameter]public string? WorkflowDefinitionVersionId { get; set; } + [Inject] private IWorkflowDefinitionService WorkflowDefinitionService { get; set; } = default!; + [Inject] private IActivityService ActivityService { get; set; } = default!; + private IDictionary ActivityDescriptors { get; set; } = default!; + private WorkflowModel WorkflowModel { get; set; } = WorkflowModel.Blank(); + + protected override async Task OnInitializedAsync() + { + ActivityDescriptors = (await ActivityService.GetActivitiesAsync()).ToDictionary(x => x.Type); + + if (WorkflowDefinitionVersionId != null) + { + var workflowDefinition = await WorkflowDefinitionService.GetByVersionIdAsync(WorkflowDefinitionVersionId); + WorkflowModel = CreateWorkflowModel(workflowDefinition); + } + else + { + WorkflowModel = WorkflowModel.Blank(); + } + } + + private WorkflowModel CreateWorkflowModel(WorkflowDefinition workflowDefinition) + { + return new WorkflowModel + { + Name = workflowDefinition.Name, + Activities = workflowDefinition.Activities.Select(CreateActivityModel).ToImmutableList(), + Connections = workflowDefinition.Connections.Select(CreateConnectionModel).ToImmutableList() + }; + } + + private ConnectionModel CreateConnectionModel(ConnectionDefinition connectionDefinition) + { + return new(connectionDefinition.SourceActivityId, connectionDefinition.TargetActivityId, connectionDefinition.Outcome); + } + + private ActivityModel CreateActivityModel(ActivityDefinition activityDefinition) + { + var descriptor = ActivityDescriptors[activityDefinition.Type]; + return new ActivityModel(activityDefinition.ActivityId, activityDefinition.Type, descriptor.Outcomes); + } } } \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Pages/Index.razor b/src/dashboards/blazor/ElsaDashboard.Application/Pages/Index.razor index e6a33ccd4..d989302fd 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/Pages/Index.razor +++ b/src/dashboards/blazor/ElsaDashboard.Application/Pages/Index.razor @@ -1,5 +1,10 @@ @page "/" -

Hello, world!

- -Welcome to your new app. + +
+
+

+ Home +

+
+
\ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Pages/Workflows.razor b/src/dashboards/blazor/ElsaDashboard.Application/Pages/Workflows.razor index 5ec576527..3196e4459 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/Pages/Workflows.razor +++ b/src/dashboards/blazor/ElsaDashboard.Application/Pages/Workflows.razor @@ -8,7 +8,7 @@
- + Create @@ -108,90 +108,92 @@ - - - - - -
+ @foreach (var workflowDefinition in WorkflowDefinitions.Items) + { + var displayName = workflowDefinition.DisplayName ?? workflowDefinition.Name ?? "Unnamed"; + + + + +
-
- 4 - 999 - 75 -
-
-
- - - 1 - - - 1 - - -
- -
- - - - - + + + 1 + + + 1 + + + + + + }
diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Pages/Workflows.razor.cs b/src/dashboards/blazor/ElsaDashboard.Application/Pages/Workflows.razor.cs new file mode 100644 index 000000000..e301d3e34 --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Application/Pages/Workflows.razor.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using Elsa.Client.Models; +using ElsaDashboard.Shared.Rpc; +using Microsoft.AspNetCore.Components; + +namespace ElsaDashboard.Application.Pages +{ + partial class Workflows + { + [Inject] private IWorkflowDefinitionService WorkflowDefinitionService { get; set; } = default!; + private PagedList WorkflowDefinitions { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + WorkflowDefinitions = await WorkflowDefinitionService.ListAsync(); + } + } +} \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Services/BackgroundWorker.cs b/src/dashboards/blazor/ElsaDashboard.Application/Services/BackgroundWorker.cs new file mode 100644 index 000000000..f124e5eac --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Application/Services/BackgroundWorker.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; + +namespace ElsaDashboard.Application.Services +{ + public class BackgroundWorker + { + private Channel> _channel; + + public BackgroundWorker() + { + _channel = Channel.CreateBounded>(10); + } + + public async Task ScheduleTask(Func task, CancellationToken cancellationToken = default) => await _channel.Writer.WriteAsync(task, cancellationToken); + + public async Task ScheduleTask(Action task, CancellationToken cancellationToken = default) => + await _channel.Writer.WriteAsync(() => + { + task(); + return ValueTask.CompletedTask; + }, cancellationToken); + + public async Task StartAsync(CancellationToken cancellationToken = default) + { + while (await _channel.Reader.WaitToReadAsync(cancellationToken)) + { + while (_channel.Reader.TryRead(out var task)) + { + await task(); + } + } + } + } +} \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Shared/ActivityPicker.razor b/src/dashboards/blazor/ElsaDashboard.Application/Shared/ActivityPicker.razor index 6387f453f..3ba9521f5 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/Shared/ActivityPicker.razor +++ b/src/dashboards/blazor/ElsaDashboard.Application/Shared/ActivityPicker.razor @@ -29,13 +29,13 @@
- @foreach (var grouping in ActivityGroupings) + @foreach (var grouping in ActivityGroupings.OrderBy(x => x.Key)) {

@grouping.Key

    - @foreach (var activity in grouping) + @foreach (var activity in grouping.OrderBy(x => x.DisplayName)) {
  • diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Shared/ActivityPicker.razor.cs b/src/dashboards/blazor/ElsaDashboard.Application/Shared/ActivityPicker.razor.cs index e656e6f96..ff13df539 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/Shared/ActivityPicker.razor.cs +++ b/src/dashboards/blazor/ElsaDashboard.Application/Shared/ActivityPicker.razor.cs @@ -28,7 +28,7 @@ namespace ElsaDashboard.Application.Shared protected override async Task OnInitializedAsync() { var response = (await ActivityService.GetActivitiesAsync()); - Activities = response.Activities.ToList(); + Activities = response.ToList(); } private void OnActivityTypeFilterClick(ActivityTraitFilter activityTraitFilter) diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Shared/MainLayout.razor b/src/dashboards/blazor/ElsaDashboard.Application/Shared/MainLayout.razor index 18a2d1305..15b483a2d 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/Shared/MainLayout.razor +++ b/src/dashboards/blazor/ElsaDashboard.Application/Shared/MainLayout.razor @@ -27,34 +27,6 @@
-
- -
-
- -
- -
-
diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Shared/NavMenu.razor b/src/dashboards/blazor/ElsaDashboard.Application/Shared/NavMenu.razor index 7712ff995..3d42552f0 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/Shared/NavMenu.razor +++ b/src/dashboards/blazor/ElsaDashboard.Application/Shared/NavMenu.razor @@ -17,28 +17,15 @@ @@ -72,27 +59,16 @@
diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Shared/NavMenu.razor.cs b/src/dashboards/blazor/ElsaDashboard.Application/Shared/NavMenu.razor.cs index 0a1597084..e15e7af4d 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/Shared/NavMenu.razor.cs +++ b/src/dashboards/blazor/ElsaDashboard.Application/Shared/NavMenu.razor.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using ElsaDashboard.Application.Models; +using Microsoft.AspNetCore.Components.Routing; namespace ElsaDashboard.Application.Shared { @@ -9,8 +10,9 @@ namespace ElsaDashboard.Application.Shared { MenuItems = new[] { - new MenuItem("Dashboard", "/", "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"), - new MenuItem("Workflows", "/workflows", "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"), + new MenuItem("Home", "", "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6", NavLinkMatch.All), + new MenuItem("Workflows", "workflows", "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z", NavLinkMatch.Prefix), + new MenuItem("Composite Activities", "composite-activities", "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z", NavLinkMatch.Prefix), }; } diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Shared/WorkflowDesigner.razor.cs b/src/dashboards/blazor/ElsaDashboard.Application/Shared/WorkflowDesigner.razor.cs index 06aaa367f..a58c9b29a 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/Shared/WorkflowDesigner.razor.cs +++ b/src/dashboards/blazor/ElsaDashboard.Application/Shared/WorkflowDesigner.razor.cs @@ -3,12 +3,14 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Formats.Asn1; using System.Linq; +using System.Threading.Channels; using System.Threading.Tasks; using Elsa.Client.Models; using ElsaDashboard.Application.Extensions; using ElsaDashboard.Application.Models; using ElsaDashboard.Application.Services; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Routing; using Microsoft.AspNetCore.Components.Web; using Microsoft.JSInterop; @@ -18,17 +20,36 @@ namespace ElsaDashboard.Application.Shared { private static Action _connectionCreatedAction = default!; - [Parameter] public WorkflowModel Model { private get; set; } = WorkflowModel.Demo(); + [Parameter] public WorkflowModel Model { private get; set; } = WorkflowModel.Blank(); [Inject] private IJSRuntime JS { get; set; } = default!; [Inject] private IFlyoutPanelService FlyoutPanelService { get; set; } = default!; private IJSObjectReference _designerModule = default!; private bool _connectionsChanged = true; private EventCallbackFactory EventCallbackFactory { get; } = new(); + private BackgroundWorker BackgroundWorker { get; } = new(); [JSInvokableAttribute("InvokeConnectionCreated")] public static void InvokeConnectionCreated(ConnectionModel connection) => _connectionCreatedAction(connection); - protected override void OnInitialized() => _connectionCreatedAction = OnConnectionCreated; + + int _currentCount = 0; + + void IncrementCount() + { + _currentCount++; + } + + protected override async Task OnInitializedAsync() + { + _connectionCreatedAction = OnConnectionCreated; + + InvokeAsync(() => BackgroundWorker.StartAsync()); + } + + protected override void OnParametersSet() + { + ConnectionsHasChanged(); + } public async ValueTask DisposeAsync() { @@ -38,14 +59,24 @@ namespace ElsaDashboard.Application.Shared protected override async Task OnAfterRenderAsync(bool firstRender) { - if (firstRender) - { + if (_designerModule == null!) _designerModule = await JS.InvokeAsync("import", "./_content/ElsaDashboard.Application/workflowDesigner.js"); - } await RepaintConnections(); } + private async Task UpdateModelAsync(WorkflowModel model) + { + Model = model; + ConnectionsHasChanged(); + await BackgroundWorker.ScheduleTask(SaveWorkflowAsync); + } + + private async ValueTask SaveWorkflowAsync() + { + + } + private IEnumerable GetRootActivities() => Model.GetChildActivities(null); private async ValueTask RepaintConnections() @@ -197,6 +228,7 @@ namespace ElsaDashboard.Application.Shared Model = model; await FlyoutPanelService.HideAsync(); ConnectionsHasChanged(); + await BackgroundWorker.ScheduleTask(() => Console.WriteLine("TEST")); } private void ConnectionsHasChanged() diff --git a/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ServiceCollectionExtensions.cs b/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ServiceCollectionExtensions.cs index 0f8833a1f..42fb1d33c 100644 --- a/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ServiceCollectionExtensions.cs +++ b/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ServiceCollectionExtensions.cs @@ -20,6 +20,7 @@ namespace ElsaDashboard.Backend.Extensions services.AddElsaClient(configure); services.AddCodeFirstGrpc(options => options.ResponseCompressionLevel = CompressionLevel.Optimal); services.AddScoped(); + services.AddScoped(); return services; } diff --git a/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/ActivityService.cs b/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/ActivityService.cs index 016c0191c..8f150d5ee 100644 --- a/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/ActivityService.cs +++ b/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/ActivityService.cs @@ -1,5 +1,7 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; using Elsa.Client; +using Elsa.Client.Models; using ElsaDashboard.Shared.Rpc; using ProtoBuf.Grpc; @@ -14,10 +16,10 @@ namespace ElsaDashboard.Backend.Rpc _elsaClient = elsaClient; } - public async Task GetActivitiesAsync(CallContext callContext) + public async Task> GetActivitiesAsync(CallContext callContext) { var result = await _elsaClient.Activities.ListAsync(callContext.CancellationToken); - return new GetActivitiesResponse(result.Items); + return result.Items; } } } \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WorkflowDefinitionManager.cs b/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WorkflowDefinitionManager.cs deleted file mode 100644 index af9e76c80..000000000 --- a/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WorkflowDefinitionManager.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ElsaDashboard.Backend.Rpc -{ - public class WorkflowDefinitionManager - { - - } -} \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WorkflowDefinitionService.cs b/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WorkflowDefinitionService.cs new file mode 100644 index 000000000..ff9a70e6b --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WorkflowDefinitionService.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Elsa.Client; +using Elsa.Client.Models; +using ElsaDashboard.Shared.Rpc; +using ProtoBuf.Grpc; + +namespace ElsaDashboard.Backend.Rpc +{ + public class WorkflowDefinitionService : IWorkflowDefinitionService + { + private readonly IElsaClient _elsaClient; + + public WorkflowDefinitionService(IElsaClient elsaClient) + { + _elsaClient = elsaClient; + } + + public async Task> ListAsync(ListWorkflowDefinitionsRequest request, CallContext context) => + await _elsaClient.WorkflowDefinitions.ListAsync(request.Page, request.PageSize, request.VersionOptions, context.CancellationToken); + + public Task GetByVersionIdAsync(string workflowDefinitionVersionId, CallContext context = default) => _elsaClient.WorkflowDefinitions.GetByVersionIdAsync(workflowDefinitionVersionId, context.CancellationToken); + } +} \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Shared/Rpc/IActivityService.cs b/src/dashboards/blazor/ElsaDashboard.Shared/Rpc/IActivityService.cs index 0faf8c4f1..d99d50c5e 100644 --- a/src/dashboards/blazor/ElsaDashboard.Shared/Rpc/IActivityService.cs +++ b/src/dashboards/blazor/ElsaDashboard.Shared/Rpc/IActivityService.cs @@ -12,21 +12,6 @@ namespace ElsaDashboard.Shared.Rpc public interface IActivityService { [ProtoBehavior] - Task GetActivitiesAsync(CallContext context = default); - } - - [ProtoContract] - public sealed class GetActivitiesResponse - { - public GetActivitiesResponse() - { - } - - public GetActivitiesResponse(IEnumerable activities) - { - Activities = activities; - } - - [ProtoMember(1)] public IEnumerable Activities { get; set; } = new System.Collections.Generic.List(); + Task> GetActivitiesAsync(CallContext context = default); } } \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Shared/Rpc/IWorkflowDefinitionService.cs b/src/dashboards/blazor/ElsaDashboard.Shared/Rpc/IWorkflowDefinitionService.cs index 0fda9f294..36752a026 100644 --- a/src/dashboards/blazor/ElsaDashboard.Shared/Rpc/IWorkflowDefinitionService.cs +++ b/src/dashboards/blazor/ElsaDashboard.Shared/Rpc/IWorkflowDefinitionService.cs @@ -1,12 +1,38 @@ +using System.ServiceModel; +using System.Threading.Tasks; +using Elsa.Client.Models; using ProtoBuf; +using ProtoBuf.Grpc; namespace ElsaDashboard.Shared.Rpc { - [ProtoContract] + [ServiceContract] public interface IWorkflowDefinitionService { - //ValueTask> GetAsync(string workflowDefinitionId, VersionOptions version); + Task> ListAsync(ListWorkflowDefinitionsRequest request, CallContext context = default); + Task GetByVersionIdAsync(string workflowDefinitionVersionId, CallContext context = default); } - public record Test(int a, bool b); + [ProtoContract] + public record ListWorkflowDefinitionsRequest + { + public ListWorkflowDefinitionsRequest(int? page = default, int? pageSize = default, VersionOptions? versionOptions = default) + { + Page = page; + PageSize = pageSize; + VersionOptions = versionOptions; + } + + [ProtoMember(1)] public int? Page { get; init; } + [ProtoMember(2)] public int? PageSize { get; init; } + [ProtoMember(3)] public VersionOptions? VersionOptions { get; init; } + } + + public static class WorkflowDefinitionServiceExtensions + { + public static Task> ListAsync(this IWorkflowDefinitionService service, int? page = default, int? pageSize = default, VersionOptions? versionOptions = default) + { + return service.ListAsync(new ListWorkflowDefinitionsRequest(page, pageSize, versionOptions)); + } + } } \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/ActivityPropertyDescriptorSurrogate.cs b/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/ActivityPropertyDescriptorSurrogate.cs index 7a1fad0d5..69caf0029 100644 --- a/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/ActivityPropertyDescriptorSurrogate.cs +++ b/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/ActivityPropertyDescriptorSurrogate.cs @@ -17,10 +17,6 @@ namespace ElsaDashboard.Shared.Surrogates SerializerSettings = new JsonSerializerSettings().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); } - public ActivityPropertyDescriptorSurrogate() - { - } - public ActivityPropertyDescriptorSurrogate(ActivityPropertyDescriptor value) { Name = value.Name; diff --git a/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/RuntimeTypeModelExtensions.cs b/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/RuntimeTypeModelExtensions.cs index b2929ea65..25a728df9 100644 --- a/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/RuntimeTypeModelExtensions.cs +++ b/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/RuntimeTypeModelExtensions.cs @@ -12,7 +12,8 @@ namespace ElsaDashboard.Shared.Surrogates { private static readonly IDictionary SurrogateMapping = new Dictionary { - [typeof(ActivityPropertyDescriptor)] = typeof(ActivityPropertyDescriptorSurrogate) + [typeof(ActivityPropertyDescriptor)] = typeof(ActivityPropertyDescriptorSurrogate), + [typeof(VersionOptions)] = typeof(VersionOptionsSurrogate) }; /// diff --git a/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/VersionOptionsSurrogate.cs b/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/VersionOptionsSurrogate.cs new file mode 100644 index 000000000..db7814377 --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/VersionOptionsSurrogate.cs @@ -0,0 +1,27 @@ +using Elsa.Client.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NodaTime; +using NodaTime.Serialization.JsonNet; +using ProtoBuf; + +namespace ElsaDashboard.Shared.Surrogates +{ + [ProtoContract(IgnoreListHandling = true)] + public class VersionOptionsSurrogate + { + public VersionOptionsSurrogate() + { + } + + public VersionOptionsSurrogate(VersionOptions value) + { + Value = value.ToString(); + } + + [ProtoMember(1)] public string Value { get; } = default!; + + public static implicit operator VersionOptions(VersionOptionsSurrogate surrogate) => VersionOptions.FromString(surrogate.Value); + public static implicit operator VersionOptionsSurrogate(VersionOptions source) => new(source); + } +} \ No newline at end of file diff --git a/src/samples/Elsa.Samples.HelloWorldConsole/Program.cs b/src/samples/Elsa.Samples.HelloWorldConsole/Program.cs index 650dad018..e459b3847 100644 --- a/src/samples/Elsa.Samples.HelloWorldConsole/Program.cs +++ b/src/samples/Elsa.Samples.HelloWorldConsole/Program.cs @@ -1,4 +1,6 @@ using System.Threading.Tasks; +using AutoMapper; +using Elsa.Extensions; using Elsa.Services; using Microsoft.Extensions.DependencyInjection; @@ -13,8 +15,9 @@ namespace Elsa.Samples.HelloWorldConsole .AddElsa() .AddConsoleActivities() .AddWorkflow() + .AddAutoMapperProfiles() .BuildServiceProvider(); - + // Run startup actions (not needed when registering Elsa with a Host). var startupRunner = services.GetRequiredService(); await startupRunner.StartupAsync(); diff --git a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Get.cs b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/GetByDefinitionAndVersion.cs similarity index 75% rename from src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Get.cs rename to src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/GetByDefinitionAndVersion.cs index f08eb8d78..f2160e125 100644 --- a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Get.cs +++ b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/GetByDefinitionAndVersion.cs @@ -14,14 +14,14 @@ namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions { [ApiController] [ApiVersion("1")] - [Route("v{apiVersion:apiVersion}/workflow-definitions/{workflowDefinitionId}")] + [Route("v{apiVersion:apiVersion}/workflow-definitions/{workflowDefinitionId}/{versionOptions}")] [Produces("application/json")] - public class Get : Controller + public class GetByDefinitionAndVersion : Controller { private readonly IWorkflowDefinitionManager _workflowDefinitionManager; private readonly IContentSerializer _serializer; - public Get(IWorkflowDefinitionManager workflowDefinitionManager, IContentSerializer serializer) + public GetByDefinitionAndVersion(IWorkflowDefinitionManager workflowDefinitionManager, IContentSerializer serializer) { _workflowDefinitionManager = workflowDefinitionManager; _serializer = serializer; @@ -33,13 +33,13 @@ namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions [ProducesResponseType(StatusCodes.Status404NotFound)] [SwaggerOperation( Summary = "Returns a single workflow definition.", - Description = "Returns a single workflow definition using the specified workflow definition ID and optional version options. When no version options are specified, the latest version is returned.", + Description = "Returns a single workflow definition using the specified workflow definition ID and version options.", OperationId = "WorkflowDefinitions.Get", Tags = new[] { "WorkflowDefinitions" }) ] - public async Task Handle(string workflowDefinitionId, [FromQuery]VersionOptions? versionOptions = default, CancellationToken cancellationToken = default) + public async Task Handle(string workflowDefinitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) { - var workflowDefinition = await _workflowDefinitionManager.GetAsync(workflowDefinitionId, versionOptions ?? VersionOptions.Latest, cancellationToken); + var workflowDefinition = await _workflowDefinitionManager.GetAsync(workflowDefinitionId, versionOptions, cancellationToken); return workflowDefinition == null ? (IActionResult) NotFound() : Json(workflowDefinition, _serializer.GetSettings()); } } diff --git a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/GetByVersionId.cs b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/GetByVersionId.cs new file mode 100644 index 000000000..9ac547d50 --- /dev/null +++ b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/GetByVersionId.cs @@ -0,0 +1,46 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Extensions; +using Elsa.Models; +using Elsa.Serialization; +using Elsa.Server.Api.Swagger; +using Elsa.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.Filters; + +namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions +{ + [ApiController] + [ApiVersion("1")] + [Route("v{apiVersion:apiVersion}/workflow-definitions/{workflowDefinitionVersionId}")] + [Produces("application/json")] + public class GetByVersionId : Controller + { + private readonly IWorkflowDefinitionManager _workflowDefinitionManager; + private readonly IContentSerializer _serializer; + + public GetByVersionId(IWorkflowDefinitionManager workflowDefinitionManager, IContentSerializer serializer) + { + _workflowDefinitionManager = workflowDefinitionManager; + _serializer = serializer; + } + + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(WorkflowDefinition))] + [SwaggerResponseExample(StatusCodes.Status200OK, typeof(WorkflowDefinitionExample))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [SwaggerOperation( + Summary = "Returns a single workflow definition.", + Description = "Returns a single workflow definition using the specified workflow definition version ID.", + OperationId = "WorkflowDefinitions.Get", + Tags = new[] { "WorkflowDefinitions" }) + ] + public async Task Handle(string workflowDefinitionVersionId, CancellationToken cancellationToken = default) + { + var workflowDefinition = await _workflowDefinitionManager.GetByVersionIdAsync(workflowDefinitionVersionId, cancellationToken); + return workflowDefinition == null ? (IActionResult) NotFound() : Json(workflowDefinition, _serializer.GetSettings()); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Post.cs b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Post.cs index 7e023cbd8..5f61c47f6 100644 --- a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Post.cs +++ b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Post.cs @@ -66,7 +66,7 @@ namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions else await _workflowPublisher.SaveDraftAsync(workflowDefinition, cancellationToken); - return CreatedAtAction("Handle", "Get", new {workflowDefinitionId = workflowDefinition.WorkflowDefinitionId, apiVersion = apiVersion.ToString()}, workflowDefinition); + return CreatedAtAction("Handle", "GetByVersionId", new {workflowDefinitionVersionId = workflowDefinition.WorkflowDefinitionVersionId, apiVersion = apiVersion.ToString()}, workflowDefinition); } } } \ No newline at end of file