From c9199bb3350ee52a223e5ba441192f3c7c42c2cc Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 10 Dec 2020 22:08:53 +0100 Subject: [PATCH] Incremental work on dashboard + API --- .../Elsa.Client/Models/WorkflowInstance.cs | 43 +++--- .../Builders/ICompositeActivity.cs | 2 +- .../Builders/IWorkflowBuilder.cs | 1 + .../Models/WorkflowInstance.cs | 3 + .../Elsa.Core/Builders/WorkflowBuilder.cs | 15 +- .../Pages/_Host.cshtml | 2 + .../ElsaDashboard.Application/App.razor | 22 +-- .../ElsaDashboard.Application.csproj | 1 + .../Extensions/ServiceCollectionExtensions.cs | 7 +- .../WorkflowInstances/DeleteDialog.razor | 1 + .../Pages/WorkflowInstances/List.razor | 145 ++++-------------- .../Pages/WorkflowInstances/List.razor.cs | 44 +++++- .../Shared/ContextMenu.razor | 21 +++ .../Shared/ContextMenu.razor.cs | 9 ++ .../Shared/ContextMenuDivider.razor | 1 + .../Shared/ContextMenuItem.razor | 42 +++++ .../Shared/Pager.razor | 68 ++++++++ .../Shared/Pager.razor.cs | 16 ++ .../Shared/TailwindModal.razor | 62 ++++++++ .../ElsaDashboard.Application/_Imports.razor | 2 + .../ElsaDashboard.Backend.csproj | 2 + .../Extensions/ServiceCollectionExtensions.cs | 3 +- .../Rpc/WorkflowInstanceService.cs | 5 +- .../Surrogates/RuntimeTypeModelExtensions.cs | 7 +- .../Surrogates/WorkflowInstanceSurrogate.cs | 35 +++++ .../ElsaDashboard.WebAssembly.csproj | 1 + .../Extensions/ServiceCollectionExtensions.cs | 1 + .../Elsa.Server.Api/Elsa.Server.Api.csproj | 6 +- .../Mapping/AutoMapperProfile.cs | 1 + .../Models/WorkflowInstance.cs | 50 ++++++ .../Elsa.Server.Host/Workflows/HelloWorld.cs | 1 + 31 files changed, 455 insertions(+), 164 deletions(-) create mode 100644 src/dashboards/blazor/ElsaDashboard.Application/Pages/WorkflowInstances/DeleteDialog.razor create mode 100644 src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenu.razor create mode 100644 src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenu.razor.cs create mode 100644 src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenuDivider.razor create mode 100644 src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenuItem.razor create mode 100644 src/dashboards/blazor/ElsaDashboard.Application/Shared/Pager.razor create mode 100644 src/dashboards/blazor/ElsaDashboard.Application/Shared/Pager.razor.cs create mode 100644 src/dashboards/blazor/ElsaDashboard.Application/Shared/TailwindModal.razor create mode 100644 src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/WorkflowInstanceSurrogate.cs create mode 100644 src/server/Elsa.Server.Api/Models/WorkflowInstance.cs diff --git a/src/clients/Elsa.Client/Models/WorkflowInstance.cs b/src/clients/Elsa.Client/Models/WorkflowInstance.cs index ecad13631..5b5201edf 100644 --- a/src/clients/Elsa.Client/Models/WorkflowInstance.cs +++ b/src/clients/Elsa.Client/Models/WorkflowInstance.cs @@ -1,13 +1,15 @@ using System.Collections.Generic; +using System.Runtime.Serialization; using Elsa.Client.Comparers; using NodaTime; namespace Elsa.Client.Models { + [DataContract] public class WorkflowInstance { - private HashSet _blockingActivities = new HashSet(BlockingActivityEqualityComparer.Instance); - + private HashSet _blockingActivities = new(BlockingActivityEqualityComparer.Instance); + public WorkflowInstance() { Variables = new Variables(); @@ -17,27 +19,30 @@ namespace Elsa.Client.Models PostScheduledActivities = new Stack(); } - public int Id { get; set; } - public string WorkflowInstanceId { get; set; } = default!; - public string WorkflowDefinitionId { get; set; } = default!; - public int Version { get; set; } - public WorkflowStatus Status { get; set; } - public string? CorrelationId { get; set; } - public string? ContextId { get; set; } - public Instant CreatedAt { get; set; } - public Variables Variables { get; set; } - public object? Output { get; set; } - public ICollection Activities { get; set; } + [DataMember(Order = 1)] public int Id { get; set; } + [DataMember(Order = 2)] public string WorkflowInstanceId { get; set; } = default!; + [DataMember(Order = 3)] public string WorkflowDefinitionId { get; set; } = default!; + [DataMember(Order = 4)] public int Version { get; set; } + [DataMember(Order = 5)] public WorkflowStatus Status { get; set; } + [DataMember(Order = 6)] public string? CorrelationId { get; set; } + [DataMember(Order = 7)] public string? ContextId { get; set; } + [DataMember(Order = 8)] public Instant CreatedAt { get; set; } + [DataMember(Order = 9)] public Instant? LastExecutedAt { get; set; } + [DataMember(Order = 10)] public Instant? LastBurstAt { get; set; } + [DataMember(Order = 11)] public Instant? CompletedAt { get; set; } + [DataMember(Order = 12)] public Variables Variables { get; set; } + [DataMember(Order = 13)] public object? Output { get; set; } + [DataMember(Order = 14)] public ICollection Activities { get; set; } - public HashSet BlockingActivities + [DataMember(Order = 15)] public HashSet BlockingActivities { get => _blockingActivities; set => _blockingActivities = new HashSet(value, BlockingActivityEqualityComparer.Instance); } - - public ICollection ExecutionLog { get; set; } - public WorkflowFault? Fault { get; set; } - public Stack ScheduledActivities { get; set; } - public Stack PostScheduledActivities { get; set; } + + [DataMember(Order = 16)] public ICollection ExecutionLog { get; set; } + [DataMember(Order = 17)] public WorkflowFault? Fault { get; set; } + [DataMember(Order = 18)] public Stack ScheduledActivities { get; set; } + [DataMember(Order = 19)] public Stack PostScheduledActivities { get; set; } } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Builders/ICompositeActivity.cs b/src/core/Elsa.Abstractions/Builders/ICompositeActivity.cs index a8c510186..710d5305d 100644 --- a/src/core/Elsa.Abstractions/Builders/ICompositeActivity.cs +++ b/src/core/Elsa.Abstractions/Builders/ICompositeActivity.cs @@ -2,7 +2,7 @@ { public interface ICompositeActivity where TBuilder: ICompositeActivityBuilder { - void Build(TBuilder compositeActivity); + void Build(TBuilder workflow); } public interface ICompositeActivity : ICompositeActivity diff --git a/src/core/Elsa.Abstractions/Builders/IWorkflowBuilder.cs b/src/core/Elsa.Abstractions/Builders/IWorkflowBuilder.cs index d8c430414..bfdb01180 100644 --- a/src/core/Elsa.Abstractions/Builders/IWorkflowBuilder.cs +++ b/src/core/Elsa.Abstractions/Builders/IWorkflowBuilder.cs @@ -14,6 +14,7 @@ namespace Elsa.Builders bool DeleteCompletedInstances { get; } bool IsEnabled { get; } IWorkflowBuilder WithWorkflowDefinitionId(string value); + IWorkflowBuilder WithDisplayName(string value); IWorkflowBuilder WithDescription(string value); IWorkflowBuilder WithVersion(int value); IWorkflowBuilder AsSingleton(); diff --git a/src/core/Elsa.Abstractions/Models/WorkflowInstance.cs b/src/core/Elsa.Abstractions/Models/WorkflowInstance.cs index 6813d73d3..7beca58e9 100644 --- a/src/core/Elsa.Abstractions/Models/WorkflowInstance.cs +++ b/src/core/Elsa.Abstractions/Models/WorkflowInstance.cs @@ -27,6 +27,9 @@ namespace Elsa.Models public string? CorrelationId { get; set; } public string? ContextId { get; set; } public Instant CreatedAt { get; set; } + public Instant? LastExecutedAt { get; set; } + public Instant? LastBurstAt { get; set; } + public Instant? CompletedAt { get; set; } public Variables Variables { get; set; } public object? Output { get; set; } public ICollection Activities { get; set; } diff --git a/src/core/Elsa.Core/Builders/WorkflowBuilder.cs b/src/core/Elsa.Core/Builders/WorkflowBuilder.cs index 7bbf92986..15fa192c1 100644 --- a/src/core/Elsa.Core/Builders/WorkflowBuilder.cs +++ b/src/core/Elsa.Core/Builders/WorkflowBuilder.cs @@ -45,6 +45,12 @@ namespace Elsa.Builders return this; } + public IWorkflowBuilder WithDisplayName(string value) + { + DisplayName = value; + return this; + } + public IWorkflowBuilder WithDescription(string? value) { Description = value; @@ -123,7 +129,12 @@ namespace Elsa.Builders public IWorkflowBlueprint Build(IWorkflow workflow, string activityIdPrefix) { - WithId(workflow.GetType().Name); + var workflowTypeName = workflow.GetType().Name; + + Name ??= workflowTypeName; + DisplayName ??= workflowTypeName; + + WithId(workflowTypeName); workflow.Build(this); return BuildBlueprint(activityIdPrefix); } @@ -139,7 +150,7 @@ namespace Elsa.Builders public IWorkflowBlueprint BuildBlueprint(string activityIdPrefix) { var compositeRoot = Build(activityIdPrefix); - + return new WorkflowBlueprint( ActivityId, Version, diff --git a/src/dashboards/blazor/ElsaDashboard.Application.Server/Pages/_Host.cshtml b/src/dashboards/blazor/ElsaDashboard.Application.Server/Pages/_Host.cshtml index 9a7818e00..30223c6fd 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application.Server/Pages/_Host.cshtml +++ b/src/dashboards/blazor/ElsaDashboard.Application.Server/Pages/_Host.cshtml @@ -17,6 +17,7 @@ + @@ -45,5 +46,6 @@ else } + \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/App.razor b/src/dashboards/blazor/ElsaDashboard.Application/App.razor index 1dd72e7c1..40128bbc6 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/App.razor +++ b/src/dashboards/blazor/ElsaDashboard.Application/App.razor @@ -1,10 +1,12 @@ - - - - - - -

Sorry, there's nothing at this address.

-
-
-
+ + + + + + + +

Sorry, there's nothing at this address.

+
+
+
+
\ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/ElsaDashboard.Application.csproj b/src/dashboards/blazor/ElsaDashboard.Application/ElsaDashboard.Application.csproj index 5c49a3306..20091d14a 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/ElsaDashboard.Application.csproj +++ b/src/dashboards/blazor/ElsaDashboard.Application/ElsaDashboard.Application.csproj @@ -7,6 +7,7 @@ + diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Extensions/ServiceCollectionExtensions.cs b/src/dashboards/blazor/ElsaDashboard.Application/Extensions/ServiceCollectionExtensions.cs index 64d198fa0..514d6a0ac 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/Extensions/ServiceCollectionExtensions.cs +++ b/src/dashboards/blazor/ElsaDashboard.Application/Extensions/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ -using ElsaDashboard.Application.Services; +using Blazored.Modal; +using ElsaDashboard.Application.Services; // ReSharper disable once CheckNamespace namespace Microsoft.Extensions.DependencyInjection @@ -7,7 +8,9 @@ namespace Microsoft.Extensions.DependencyInjection { public static IServiceCollection AddElsaDashboardUI(this IServiceCollection services) { - return services.AddScoped(); + return services + .AddBlazoredModal() + .AddScoped(); } } } \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Pages/WorkflowInstances/DeleteDialog.razor b/src/dashboards/blazor/ElsaDashboard.Application/Pages/WorkflowInstances/DeleteDialog.razor new file mode 100644 index 000000000..374446494 --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Application/Pages/WorkflowInstances/DeleteDialog.razor @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Pages/WorkflowInstances/List.razor b/src/dashboards/blazor/ElsaDashboard.Application/Pages/WorkflowInstances/List.razor index 59fb11464..cf444901b 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/Pages/WorkflowInstances/List.razor +++ b/src/dashboards/blazor/ElsaDashboard.Application/Pages/WorkflowInstances/List.razor @@ -60,7 +60,10 @@ @foreach (var workflowInstance in WorkflowInstances.Items) { - var displayName = workflowInstance.Id; + var workflowBlueprint = WorkflowBlueprints[(workflowInstance.WorkflowDefinitionId, workflowInstance.Version)]; + var displayName = workflowBlueprint.DisplayName; + var statusColor = GetStatusColor(workflowInstance.Status); + var viewUrl = $"workflow-instances/{workflowInstance.WorkflowInstanceId}/viewer"; @@ -69,140 +72,48 @@ - Hello World + @displayName - 1 + @workflowInstance.Version
-
- Completed +
+ @workflowInstance.Status
- Today + @workflowInstance.CreatedAt - Today + @(workflowInstance.CompletedAt != null ? workflowInstance.CompletedAt.ToString() : "-") - + View + + + + + + + + Delete + + } -
- - -
+ \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Pages/WorkflowInstances/List.razor.cs b/src/dashboards/blazor/ElsaDashboard.Application/Pages/WorkflowInstances/List.razor.cs index d31e79b1c..c9eacdb02 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/Pages/WorkflowInstances/List.razor.cs +++ b/src/dashboards/blazor/ElsaDashboard.Application/Pages/WorkflowInstances/List.razor.cs @@ -1,5 +1,11 @@ -using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Blazored.Modal; +using Blazored.Modal.Services; using Elsa.Client.Models; +using ElsaDashboard.Application.Shared; using ElsaDashboard.Shared.Rpc; using Microsoft.AspNetCore.Components; @@ -8,11 +14,45 @@ namespace ElsaDashboard.Application.Pages.WorkflowInstances partial class List { [Inject] private IWorkflowInstanceService WorkflowInstanceService { get; set; } = default!; + [Inject] private IWorkflowRegistryService WorkflowRegistryService { get; set; } = default!; + [Inject] private IModalService ModalService { get; set; } = default!; private PagedList WorkflowInstances { get; set; } = new(); + private IDictionary<(string, int), WorkflowBlueprint> WorkflowBlueprints { get; set; } = new Dictionary<(string, int), WorkflowBlueprint>(); protected override async Task OnInitializedAsync() { - WorkflowInstances = await WorkflowInstanceService.ListAsync(); + var workflowBlueprintsTask = WorkflowRegistryService.ListAsync(); + var workflowInstancesTask = WorkflowInstanceService.ListAsync(); + + await Task.WhenAll(workflowBlueprintsTask, workflowInstancesTask); + + WorkflowBlueprints = workflowBlueprintsTask.Result.Items.ToDictionary(x => (x.Id, x.Version)); + WorkflowInstances = workflowInstancesTask.Result; } + + private async Task OnDeleteWorkflowInstanceClick(WorkflowInstance workflowInstance) + { + var options = new ModalOptions { UseCustomLayout = true }; + var parameters = new ModalParameters(); + parameters.Add("Title", "Are you sure?"); + var result = await ModalService.Show("Are you sure?").Result; + + if (result.Cancelled) + return; + + // Delete. + } + + private static string GetStatusColor(WorkflowStatus status) => + status switch + { + WorkflowStatus.Idle => "gray", + WorkflowStatus.Running => "pink", + WorkflowStatus.Suspended => "blue", + WorkflowStatus.Finished => "green", + WorkflowStatus.Faulted => "red", + WorkflowStatus.Cancelled => "yellow", + _ => throw new ArgumentOutOfRangeException(nameof(status), status, null) + }; } } \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenu.razor b/src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenu.razor new file mode 100644 index 000000000..fb5e1caa6 --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenu.razor @@ -0,0 +1,21 @@ +
+ +
+ +
+
\ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenu.razor.cs b/src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenu.razor.cs new file mode 100644 index 000000000..67d57d63c --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenu.razor.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Components; + +namespace ElsaDashboard.Application.Shared +{ + partial class ContextMenu + { + [Parameter]public RenderFragment ChildContent { get; set; } + } +} \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenuDivider.razor b/src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenuDivider.razor new file mode 100644 index 000000000..399c42f3e --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenuDivider.razor @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenuItem.razor b/src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenuItem.razor new file mode 100644 index 000000000..5e665ee60 --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Application/Shared/ContextMenuItem.razor @@ -0,0 +1,42 @@ + + @if (ChildContent != null) + { + @ChildContent + } + else + { + + + + @Text + } + + +@code +{ + [Inject] + private NavigationManager NavigationManager { get; set; } + + [Parameter] + public string Text { get; set; } = default!; + + [Parameter] + public string Icon { get; set; } = default!; + + [Parameter] + public RenderFragment? ChildContent { get; set; } + + [Parameter] + public string? Url { get; set; } + + [Parameter] + public EventCallback OnClick { get; set; } + + protected override void OnParametersSet() + { + if (Url != null) + OnClick = new EventCallbackFactory().Create(this, Navigate); + } + + private void Navigate() => NavigationManager.NavigateTo(Url!); +} \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Shared/Pager.razor b/src/dashboards/blazor/ElsaDashboard.Application/Shared/Pager.razor new file mode 100644 index 000000000..132169dd0 --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Application/Shared/Pager.razor @@ -0,0 +1,68 @@ +@if (Page != null) +{ +
+
+ @if (Page > 0) + { + + Previous + + } + @if (Page < PageCount) + { + + Next + + } +
+ +
+} \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Shared/Pager.razor.cs b/src/dashboards/blazor/ElsaDashboard.Application/Shared/Pager.razor.cs new file mode 100644 index 000000000..b654cc014 --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Application/Shared/Pager.razor.cs @@ -0,0 +1,16 @@ +using System; +using Microsoft.AspNetCore.Components; + +namespace ElsaDashboard.Application.Shared +{ + partial class Pager + { + [Parameter] public int? Page { get; set; } + [Parameter] public int? PageSize { get; set; } + [Parameter] public int TotalCount { get; set; } + + private int From => Page!.Value * PageSize!.Value + 1; + private int To => Math.Min(From + PageSize!.Value, TotalCount); + private int PageCount => TotalCount / PageSize!.Value; + } +} \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/Shared/TailwindModal.razor b/src/dashboards/blazor/ElsaDashboard.Application/Shared/TailwindModal.razor new file mode 100644 index 000000000..9b97e036f --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Application/Shared/TailwindModal.razor @@ -0,0 +1,62 @@ + +
+
+ + + + + + +
+
+ +@code { + + [CascadingParameter] + BlazoredModalInstance BlazoredModal { get; set; } + + [Parameter] + public string Message { get; set; } + + async Task Close() => await BlazoredModal.Close(ModalResult.Ok(true)); + async Task Cancel() => await BlazoredModal.Cancel(); + +} \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Application/_Imports.razor b/src/dashboards/blazor/ElsaDashboard.Application/_Imports.razor index 034d37fcd..26d8a7fd5 100644 --- a/src/dashboards/blazor/ElsaDashboard.Application/_Imports.razor +++ b/src/dashboards/blazor/ElsaDashboard.Application/_Imports.razor @@ -1,5 +1,7 @@ @using System.Net.Http @using System.Net.Http.Json +@using Blazored.Modal +@using Blazored.Modal.Services @using ElsaDashboard.Application @using ElsaDashboard.Application.Models @using ElsaDashboard.Application.Shared diff --git a/src/dashboards/blazor/ElsaDashboard.Backend/ElsaDashboard.Backend.csproj b/src/dashboards/blazor/ElsaDashboard.Backend/ElsaDashboard.Backend.csproj index ebd569ca6..8d9d0baeb 100644 --- a/src/dashboards/blazor/ElsaDashboard.Backend/ElsaDashboard.Backend.csproj +++ b/src/dashboards/blazor/ElsaDashboard.Backend/ElsaDashboard.Backend.csproj @@ -13,7 +13,9 @@ + + diff --git a/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ServiceCollectionExtensions.cs b/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ServiceCollectionExtensions.cs index 234690cf6..1eb1ca0b0 100644 --- a/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ServiceCollectionExtensions.cs +++ b/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ServiceCollectionExtensions.cs @@ -17,9 +17,10 @@ namespace ElsaDashboard.Backend.Extensions public static IServiceCollection AddElsaDashboardBackend(this IServiceCollection services, Action? configure = default) { RuntimeTypeModel.Default.AddElsaGrpcSurrogates(); + RuntimeTypeModel.Default.AddNodaTime(); services.AddCors(); services.AddElsaClient(configure); - services.AddCodeFirstGrpc(options => options.ResponseCompressionLevel = CompressionLevel.Optimal); + services.AddCodeFirstGrpc(options => options.ResponseCompressionLevel = CompressionLevel.Fastest); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WorkflowInstanceService.cs b/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WorkflowInstanceService.cs index 0c0e6a464..7d7415da8 100644 --- a/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WorkflowInstanceService.cs +++ b/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WorkflowInstanceService.cs @@ -10,10 +10,7 @@ namespace ElsaDashboard.Backend.Rpc { private readonly IElsaClient _elsaClient; public WorkflowInstanceService(IElsaClient elsaClient) => _elsaClient = elsaClient; - - public Task> ListAsync(int page = 0, int pageSize = 50, CallContext context = default) => - _elsaClient.WorkflowInstanceses.ListAsync(page, pageSize, context.CancellationToken); - + public Task> ListAsync(int page = 0, int pageSize = 50, CallContext context = default) => _elsaClient.WorkflowInstanceses.ListAsync(page, pageSize, context.CancellationToken); public Task GetById(string id, CallContext context = default) => _elsaClient.WorkflowInstanceses.GetByIdAsync(id, context.CancellationToken); } } \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/RuntimeTypeModelExtensions.cs b/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/RuntimeTypeModelExtensions.cs index 92fe8102c..8187d3751 100644 --- a/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/RuntimeTypeModelExtensions.cs +++ b/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/RuntimeTypeModelExtensions.cs @@ -13,7 +13,8 @@ namespace ElsaDashboard.Shared.Surrogates private static readonly IDictionary SurrogateMapping = new Dictionary { [typeof(ActivityPropertyInfo)] = typeof(ActivityPropertyDescriptorSurrogate), - [typeof(VersionOptions)] = typeof(VersionOptionsSurrogate) + [typeof(VersionOptions)] = typeof(VersionOptionsSurrogate), + [typeof(WorkflowInstance)] = typeof(WorkflowInstanceSurrogate) }; /// @@ -21,9 +22,9 @@ namespace ElsaDashboard.Shared.Surrogates /// public static RuntimeTypeModel AddElsaGrpcSurrogates(this RuntimeTypeModel runtimeTypeModel) { - foreach (var map in SurrogateMapping) + foreach (var map in SurrogateMapping) runtimeTypeModel.Add(map.Key, false).SetSurrogate(map.Value); - + return runtimeTypeModel; } } diff --git a/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/WorkflowInstanceSurrogate.cs b/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/WorkflowInstanceSurrogate.cs new file mode 100644 index 000000000..02975c555 --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Shared/Surrogates/WorkflowInstanceSurrogate.cs @@ -0,0 +1,35 @@ +using Elsa.Client.Models; +using Newtonsoft.Json; +using NodaTime; +using NodaTime.Serialization.JsonNet; +using ProtoBuf; + +namespace ElsaDashboard.Shared.Surrogates +{ + [ProtoContract(IgnoreListHandling = true)] + public class WorkflowInstanceSurrogate + { + private static readonly JsonSerializerSettings SerializerSettings; + + static WorkflowInstanceSurrogate() + { + SerializerSettings = new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.Auto + }.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); + } + + public WorkflowInstanceSurrogate(WorkflowInstance value) + { + Value = Serialize(value); + } + + [ProtoMember(1)] public string Value { get; } + + public static implicit operator WorkflowInstance(WorkflowInstanceSurrogate surrogate) => Deserialize(surrogate.Value)!; + public static implicit operator WorkflowInstanceSurrogate(WorkflowInstance source) => new(source); + + private static WorkflowInstance Deserialize(string json) => JsonConvert.DeserializeObject(json, SerializerSettings)!; + private static string Serialize(WorkflowInstance value) => JsonConvert.SerializeObject(value, SerializerSettings)!; + } +} \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.WebAssembly/ElsaDashboard.WebAssembly.csproj b/src/dashboards/blazor/ElsaDashboard.WebAssembly/ElsaDashboard.WebAssembly.csproj index ab855c712..4511dd6b2 100644 --- a/src/dashboards/blazor/ElsaDashboard.WebAssembly/ElsaDashboard.WebAssembly.csproj +++ b/src/dashboards/blazor/ElsaDashboard.WebAssembly/ElsaDashboard.WebAssembly.csproj @@ -6,6 +6,7 @@ + diff --git a/src/dashboards/blazor/ElsaDashboard.WebAssembly/Extensions/ServiceCollectionExtensions.cs b/src/dashboards/blazor/ElsaDashboard.WebAssembly/Extensions/ServiceCollectionExtensions.cs index b995c1b7c..fefb7348f 100644 --- a/src/dashboards/blazor/ElsaDashboard.WebAssembly/Extensions/ServiceCollectionExtensions.cs +++ b/src/dashboards/blazor/ElsaDashboard.WebAssembly/Extensions/ServiceCollectionExtensions.cs @@ -10,6 +10,7 @@ namespace ElsaDashboard.WebAssembly.Extensions public static IServiceCollection AddElsaDashboardBackend(this IServiceCollection services) { RuntimeTypeModel.Default.AddElsaGrpcSurrogates(); + RuntimeTypeModel.Default.AddNodaTime(); return services .AddGrpcClient() .AddGrpcClient(); diff --git a/src/server/Elsa.Server.Api/Elsa.Server.Api.csproj b/src/server/Elsa.Server.Api/Elsa.Server.Api.csproj index 6d84ab1bd..e87ab5361 100644 --- a/src/server/Elsa.Server.Api/Elsa.Server.Api.csproj +++ b/src/server/Elsa.Server.Api/Elsa.Server.Api.csproj @@ -18,9 +18,9 @@ enable - - - + + + diff --git a/src/server/Elsa.Server.Api/Mapping/AutoMapperProfile.cs b/src/server/Elsa.Server.Api/Mapping/AutoMapperProfile.cs index 0133e32a2..64a0d4657 100644 --- a/src/server/Elsa.Server.Api/Mapping/AutoMapperProfile.cs +++ b/src/server/Elsa.Server.Api/Mapping/AutoMapperProfile.cs @@ -1,4 +1,5 @@ using AutoMapper; +using Elsa.Models; using Elsa.Server.Api.Endpoints.WorkflowRegistry; using Elsa.Services.Models; diff --git a/src/server/Elsa.Server.Api/Models/WorkflowInstance.cs b/src/server/Elsa.Server.Api/Models/WorkflowInstance.cs new file mode 100644 index 000000000..6af0f2c6b --- /dev/null +++ b/src/server/Elsa.Server.Api/Models/WorkflowInstance.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; +using Elsa.Comparers; +using Elsa.Models; +using Newtonsoft.Json.Linq; +using NodaTime; + +namespace Elsa.Server.Api.Models +{ + [DataContract] + public class WorkflowInstanceModel + { + private HashSet _blockingActivities = new(BlockingActivityEqualityComparer.Instance); + + public WorkflowInstanceModel() + { + Variables = new Variables(); + Activities = new List(); + ExecutionLog = new List(); + ScheduledActivities = new Stack(); + PostScheduledActivities = new Stack(); + } + + [DataMember(Order = 1)] public int Id { get; set; } + [DataMember(Order = 2)] public string WorkflowInstanceId { get; set; } = default!; + [DataMember(Order = 3)] public string WorkflowDefinitionId { get; set; } = default!; + [DataMember(Order = 4)] public int Version { get; set; } + [DataMember(Order = 5)] public WorkflowStatus Status { get; set; } + [DataMember(Order = 6)] public string? CorrelationId { get; set; } + [DataMember(Order = 7)] public string? ContextId { get; set; } + [DataMember(Order = 8)] public Instant CreatedAt { get; set; } + [DataMember(Order = 9)] public Instant? LastExecutedAt { get; set; } + [DataMember(Order = 10)] public Instant? LastBurstAt { get; set; } + [DataMember(Order = 11)] public Instant? CompletedAt { get; set; } + [DataMember(Order = 12)] public Variables Variables { get; set; } + [DataMember(Order = 13)] public JObject? Output { get; set; } + [DataMember(Order = 14)] public ICollection Activities { get; set; } + + [DataMember(Order = 15)] public HashSet BlockingActivities + { + get => _blockingActivities; + set => _blockingActivities = new HashSet(value, BlockingActivityEqualityComparer.Instance); + } + + [DataMember(Order = 16)] public ICollection ExecutionLog { get; set; } + [DataMember(Order = 17)] public WorkflowFault? Fault { get; set; } + [DataMember(Order = 18)] public Stack ScheduledActivities { get; set; } + [DataMember(Order = 19)] public Stack PostScheduledActivities { get; set; } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.Host/Workflows/HelloWorld.cs b/src/server/Elsa.Server.Host/Workflows/HelloWorld.cs index 6f13cdf91..b4d81c79a 100644 --- a/src/server/Elsa.Server.Host/Workflows/HelloWorld.cs +++ b/src/server/Elsa.Server.Host/Workflows/HelloWorld.cs @@ -9,6 +9,7 @@ namespace Elsa.Server.Host.Workflows public void Build(IWorkflowBuilder workflow) { workflow + .WithDisplayName("Hello World!") .HttpRequestReceived("/hello-world") .WriteHttpResponse(HttpStatusCode.OK, "Hello World!", "text/plain"); }