From fbf7627607e35fa60aad44a10b97fb5cdb1e25dd Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bianchi Date: Fri, 25 Oct 2024 16:51:13 +0200 Subject: [PATCH] feat(Dashboard): added suspend/resume/cancel actions to workflow instances Signed-off-by: Jean-Baptiste Bianchi --- .../Synapse.Api.Application.csproj | 2 +- .../Synapse.Api.Client.Core.csproj | 2 +- .../Synapse.Api.Client.Http.csproj | 2 +- .../Synapse.Api.Http/Synapse.Api.Http.csproj | 2 +- .../Synapse.Api.Server.csproj | 2 +- src/cli/Synapse.Cli/Synapse.Cli.csproj | 2 +- ...re.Infrastructure.Containers.Docker.csproj | 2 +- ...nfrastructure.Containers.Kubernetes.csproj | 2 +- .../Synapse.Core.Infrastructure.csproj | 2 +- src/core/Synapse.Core/Synapse.Core.csproj | 2 +- .../Synapse.Correlator.csproj | 2 +- .../WorkflowInstancesList.razor | 173 ++++++++++++++++-- .../Extensions/StatusExtensions.cs | 1 + .../Pages/Workflows/Details/Store.cs | 88 +++++++++ .../Pages/Workflows/Details/View.razor | 17 +- .../Synapse.Operator/Synapse.Operator.csproj | 2 +- .../Synapse.Runner/Synapse.Runner.csproj | 2 +- .../Synapse.Runtime.Abstractions.csproj | 2 +- .../Synapse.Runtime.Docker.csproj | 2 +- .../Synapse.Runtime.Kubernetes.csproj | 2 +- .../Synapse.Runtime.Native.csproj | 2 +- 21 files changed, 271 insertions(+), 42 deletions(-) diff --git a/src/api/Synapse.Api.Application/Synapse.Api.Application.csproj b/src/api/Synapse.Api.Application/Synapse.Api.Application.csproj index 8f4fbadcc..559ef2c7c 100644 --- a/src/api/Synapse.Api.Application/Synapse.Api.Application.csproj +++ b/src/api/Synapse.Api.Application/Synapse.Api.Application.csproj @@ -7,7 +7,7 @@ en True 1.0.0 - alpha5 + alpha5.1 $(VersionPrefix) $(VersionPrefix) The Synapse Authors diff --git a/src/api/Synapse.Api.Client.Core/Synapse.Api.Client.Core.csproj b/src/api/Synapse.Api.Client.Core/Synapse.Api.Client.Core.csproj index 485ea739a..0ff59e430 100644 --- a/src/api/Synapse.Api.Client.Core/Synapse.Api.Client.Core.csproj +++ b/src/api/Synapse.Api.Client.Core/Synapse.Api.Client.Core.csproj @@ -7,7 +7,7 @@ en True 1.0.0 - alpha5 + alpha5.1 $(VersionPrefix) $(VersionPrefix) The Synapse Authors diff --git a/src/api/Synapse.Api.Client.Http/Synapse.Api.Client.Http.csproj b/src/api/Synapse.Api.Client.Http/Synapse.Api.Client.Http.csproj index 4fabc3bea..2282c9d6d 100644 --- a/src/api/Synapse.Api.Client.Http/Synapse.Api.Client.Http.csproj +++ b/src/api/Synapse.Api.Client.Http/Synapse.Api.Client.Http.csproj @@ -7,7 +7,7 @@ en True 1.0.0 - alpha5 + alpha5.1 $(VersionPrefix) $(VersionPrefix) The Synapse Authors diff --git a/src/api/Synapse.Api.Http/Synapse.Api.Http.csproj b/src/api/Synapse.Api.Http/Synapse.Api.Http.csproj index 06f850c6a..06e7b3221 100644 --- a/src/api/Synapse.Api.Http/Synapse.Api.Http.csproj +++ b/src/api/Synapse.Api.Http/Synapse.Api.Http.csproj @@ -8,7 +8,7 @@ Library True 1.0.0 - alpha5 + alpha5.1 $(VersionPrefix) $(VersionPrefix) The Synapse Authors diff --git a/src/api/Synapse.Api.Server/Synapse.Api.Server.csproj b/src/api/Synapse.Api.Server/Synapse.Api.Server.csproj index d4fd6f08f..91debeaf9 100644 --- a/src/api/Synapse.Api.Server/Synapse.Api.Server.csproj +++ b/src/api/Synapse.Api.Server/Synapse.Api.Server.csproj @@ -7,7 +7,7 @@ en True 1.0.0 - alpha5 + alpha5.1 $(VersionPrefix) $(VersionPrefix) The Synapse Authors diff --git a/src/cli/Synapse.Cli/Synapse.Cli.csproj b/src/cli/Synapse.Cli/Synapse.Cli.csproj index a49ae597f..fafc6e667 100644 --- a/src/cli/Synapse.Cli/Synapse.Cli.csproj +++ b/src/cli/Synapse.Cli/Synapse.Cli.csproj @@ -8,7 +8,7 @@ en True 1.0.0 - alpha5 + alpha5.1 $(VersionPrefix) $(VersionPrefix) The Synapse Authors diff --git a/src/core/Synapse.Core.Infrastructure.Containers.Docker/Synapse.Core.Infrastructure.Containers.Docker.csproj b/src/core/Synapse.Core.Infrastructure.Containers.Docker/Synapse.Core.Infrastructure.Containers.Docker.csproj index a527a1ec3..b86f9e08c 100644 --- a/src/core/Synapse.Core.Infrastructure.Containers.Docker/Synapse.Core.Infrastructure.Containers.Docker.csproj +++ b/src/core/Synapse.Core.Infrastructure.Containers.Docker/Synapse.Core.Infrastructure.Containers.Docker.csproj @@ -7,7 +7,7 @@ en True 1.0.0 - alpha5 + alpha5.1 $(VersionPrefix) $(VersionPrefix) The Synapse Authors diff --git a/src/core/Synapse.Core.Infrastructure.Containers.Kubernetes/Synapse.Core.Infrastructure.Containers.Kubernetes.csproj b/src/core/Synapse.Core.Infrastructure.Containers.Kubernetes/Synapse.Core.Infrastructure.Containers.Kubernetes.csproj index 58747907c..980efb526 100644 --- a/src/core/Synapse.Core.Infrastructure.Containers.Kubernetes/Synapse.Core.Infrastructure.Containers.Kubernetes.csproj +++ b/src/core/Synapse.Core.Infrastructure.Containers.Kubernetes/Synapse.Core.Infrastructure.Containers.Kubernetes.csproj @@ -7,7 +7,7 @@ en True 1.0.0 - alpha5 + alpha5.1 $(VersionPrefix) $(VersionPrefix) The Synapse Authors diff --git a/src/core/Synapse.Core.Infrastructure/Synapse.Core.Infrastructure.csproj b/src/core/Synapse.Core.Infrastructure/Synapse.Core.Infrastructure.csproj index 5d4d4e5b7..b5048f7e0 100644 --- a/src/core/Synapse.Core.Infrastructure/Synapse.Core.Infrastructure.csproj +++ b/src/core/Synapse.Core.Infrastructure/Synapse.Core.Infrastructure.csproj @@ -7,7 +7,7 @@ en True 1.0.0 - alpha5 + alpha5.1 $(VersionPrefix) $(VersionPrefix) The Synapse Authors diff --git a/src/core/Synapse.Core/Synapse.Core.csproj b/src/core/Synapse.Core/Synapse.Core.csproj index 537f04d4c..fb8f03500 100644 --- a/src/core/Synapse.Core/Synapse.Core.csproj +++ b/src/core/Synapse.Core/Synapse.Core.csproj @@ -7,7 +7,7 @@ en True 1.0.0 - alpha5 + alpha5.1 $(VersionPrefix) $(VersionPrefix) The Synapse Authors diff --git a/src/correlator/Synapse.Correlator/Synapse.Correlator.csproj b/src/correlator/Synapse.Correlator/Synapse.Correlator.csproj index 5c2f612f7..b3eaf33d9 100644 --- a/src/correlator/Synapse.Correlator/Synapse.Correlator.csproj +++ b/src/correlator/Synapse.Correlator/Synapse.Correlator.csproj @@ -8,7 +8,7 @@ en True 1.0.0 - alpha5 + alpha5.1 $(VersionPrefix) $(VersionPrefix) The Synapse Authors diff --git a/src/dashboard/Synapse.Dashboard/Components/WorkflowInstancesList/WorkflowInstancesList.razor b/src/dashboard/Synapse.Dashboard/Components/WorkflowInstancesList/WorkflowInstancesList.razor index 361353a1e..13597314d 100644 --- a/src/dashboard/Synapse.Dashboard/Components/WorkflowInstancesList/WorkflowInstancesList.razor +++ b/src/dashboard/Synapse.Dashboard/Components/WorkflowInstancesList/WorkflowInstancesList.razor @@ -58,7 +58,10 @@ @@ -68,9 +71,9 @@ @foreach (var column in knownColumns) { - if ((Columns.Count() == 0 && column != "Delete") || Columns.Contains(column)) + if ((Columns.Count() == 0 && !DirectActions.Contains(column)) || Columns.Contains(column)) { - @(column != "Action" && column != "Delete" ? column : "") + @(column != "Actions" && !DirectActions.Contains(column) ? column : "") } } @@ -85,7 +88,7 @@ @foreach (var column in knownColumns) { - if ((Columns.Count() == 0 && column != "Delete") || Columns.Contains(column)) + if ((Columns.Count() == 0 && !DirectActions.Contains(column)) || Columns.Contains(column)) { @switch(column) @@ -144,16 +147,44 @@ break; + case "Resume": + @if (instance.Status?.Phase == WorkflowInstanceStatusPhase.Running) + { + + } + @if (instance.Status?.Phase == WorkflowInstanceStatusPhase.Suspended) + { + + } + break; + case "Cancel": + @if (instance.IsOperative) + { + + } + break; case "Replay": - + break; case "Delete": - + break; default: break; @@ -208,9 +239,21 @@ [Parameter] public EventCallback OnToggleSelected { get; set; } [Parameter] public EventCallback OnDelete { get; set; } [Parameter] public EventCallback OnReplay { get; set; } + [Parameter] public EventCallback OnSuspend { get; set; } + [Parameter] public EventCallback OnResume { get; set; } + [Parameter] public EventCallback OnCancel { get; set; } [Parameter] public EventCallback OnDeleteSelected { get; set; } + [Parameter] public EventCallback OnSuspendSelected { get; set; } + [Parameter] public EventCallback OnResumeSelected { get; set; } + [Parameter] public EventCallback OnCancelSelected { get; set; } - IEnumerable knownColumns = [ + public static IEnumerable DirectActions = [ + "Resume", + "Cancel", + "Replay", + "Delete" + ]; + static IEnumerable knownColumns = [ "Name", "Namespace", "Definition", @@ -221,8 +264,7 @@ "Duration", "Operator", "Actions", - "Replay", - "Delete" + ..DirectActions ]; ElementReference checkboxAll = default!; @@ -324,11 +366,14 @@ /// string GetColumnAlignment(string column) { - return column == "Name" || column == "Namespace" - ? "start" - : column == "Action" - ? "end" - : "center"; + return ( + column == "Name" || column == "Namespace" + ? "start" + : column == "Action" + ? "end" + : "center" + ) + + (DirectActions.Contains(column) ? " p-0": ""); } /// @@ -385,7 +430,7 @@ /// /// Handles the click on the show button /// - /// + /// The instance to show /// protected async Task OnShowClickedAsync(WorkflowInstance instance) { @@ -398,7 +443,7 @@ /// /// Handles the click on the delete button /// - /// + /// The instance to delete /// protected async Task OnDeleteClickedAsync(WorkflowInstance instance) { @@ -411,7 +456,7 @@ /// /// Handles the click on the replay button /// - /// + /// The instance to replay /// protected async Task OnReplayClickedAsync(WorkflowInstance instance) { @@ -420,4 +465,94 @@ await this.OnReplay.InvokeAsync(instance); } } + + /// + /// Handles the click on the suspend button + /// + /// The instance to suspend + /// + protected async Task OnSuspendClickedAsync(WorkflowInstance instance) + { + if (this.OnSuspend.HasDelegate) + { + await this.OnSuspend.InvokeAsync(instance); + } + } + + /// + /// Handles the click on the resume button + /// + /// The instance to resume + /// + protected async Task OnResumeClickedAsync(WorkflowInstance instance) + { + if (this.OnResume.HasDelegate) + { + await this.OnResume.InvokeAsync(instance); + } + } + + /// + /// Handles the click on the cancel button + /// + /// The instance to cancel + /// + protected async Task OnCancelClickedAsync(WorkflowInstance instance) + { + if (this.OnCancel.HasDelegate) + { + await this.OnCancel.InvokeAsync(instance); + } + } + + protected async Task OnSuspendSelectedClickedAsync() + { + var selected = selectedInstanceNames.ToList(); + var nonRuningInstances = (WorkflowInstances??[]).Where(instance => selected.Contains(instance.GetName()) && instance.Status?.Phase != WorkflowInstanceStatusPhase.Running); + foreach(var instance in nonRuningInstances) + { + if (this.OnToggleSelected.HasDelegate) + { + await OnToggleSelected.InvokeAsync(instance.GetName()); + } + } + if (this.OnSuspendSelected.HasDelegate) + { + await OnSuspendSelected.InvokeAsync(); + } + } + + protected async Task OnResumeSelectedClickedAsync() + { + var selected = selectedInstanceNames.ToList(); + var nonSuspendedInstances = (WorkflowInstances ?? []).Where(instance => selected.Contains(instance.GetName()) && instance.Status?.Phase != WorkflowInstanceStatusPhase.Suspended); + foreach (var instance in nonSuspendedInstances) + { + if (this.OnToggleSelected.HasDelegate) + { + await OnToggleSelected.InvokeAsync(instance.GetName()); + } + } + if (this.OnResumeSelected.HasDelegate) + { + await OnResumeSelected.InvokeAsync(); + } + } + + protected async Task OnCancelSelectedClickedAsync() + { + var selected = selectedInstanceNames.ToList(); + var nonOperativeInstances = (WorkflowInstances ?? []).Where(instance => selected.Contains(instance.GetName()) && !instance.IsOperative); + foreach (var instance in nonOperativeInstances) + { + if (this.OnToggleSelected.HasDelegate) + { + await OnToggleSelected.InvokeAsync(instance.GetName()); + } + } + if (this.OnCancelSelected.HasDelegate) + { + await OnCancelSelected.InvokeAsync(); + } + } } \ No newline at end of file diff --git a/src/dashboard/Synapse.Dashboard/Extensions/StatusExtensions.cs b/src/dashboard/Synapse.Dashboard/Extensions/StatusExtensions.cs index 5ac8ccb70..3100b332e 100644 --- a/src/dashboard/Synapse.Dashboard/Extensions/StatusExtensions.cs +++ b/src/dashboard/Synapse.Dashboard/Extensions/StatusExtensions.cs @@ -43,6 +43,7 @@ public static class StatusExtensions //CorrelationContextStatus.Completed => "success", WorkflowInstanceStatusPhase.Waiting => "cinereous", TaskInstanceStatus.Suspended => "icterine", + //WorkflowInstanceStatusPhase.Suspended => "icterine", TaskInstanceStatus.Skipped => "cinereous", WorkflowInstanceStatusPhase.Pending => "mute", //TaskInstanceStatus.Pending => "mute", diff --git a/src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/Store.cs b/src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/Store.cs index 8bff87e2a..46e7b08ec 100644 --- a/src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/Store.cs +++ b/src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/Store.cs @@ -12,6 +12,7 @@ // limitations under the License. using Neuroglia.Blazor.Dagre.Models; +using Neuroglia.Data.Infrastructure.ResourceOriented; using ServerlessWorkflow.Sdk.Models; using Synapse.Api.Client.Services; using Synapse.Dashboard.Components.DocumentDetailsStateManagement; @@ -318,9 +319,12 @@ public async Task OnCopyToClipboard() } } + /// /// Displays the modal used to provide the new workflow input /// + /// The definition to start a new instance of + /// A default input payload, if any /// A awaitable task public async Task OnShowCreateInstanceAsync(WorkflowDefinition workflowDefinition, EquatableDictionary? input = null) { @@ -336,6 +340,90 @@ public async Task OnShowCreateInstanceAsync(WorkflowDefinition workflowDefinitio } } + /// + /// Suspends the provided instance + /// + /// The instance to suspend + /// A awaitable task + public async Task SuspendInstanceAsync(WorkflowInstance workflowInstance) + { + await this.ApiClient.WorkflowInstances.SuspendAsync(workflowInstance.GetName(), workflowInstance.GetNamespace()!).ConfigureAwait(false); + } + + /// + /// Resumes the provided instance + /// + /// The instance to resume + /// A awaitable task + public async Task ResumeInstanceAsync(WorkflowInstance workflowInstance) + { + await this.ApiClient.WorkflowInstances.ResumeAsync(workflowInstance.GetName(), workflowInstance.GetNamespace()!).ConfigureAwait(false); + } + + /// + /// Cancels the provided instance + /// + /// The instance to resume + /// A awaitable task + public async Task CancelInstanceAsync(WorkflowInstance workflowInstance) + { + await this.ApiClient.WorkflowInstances.CancelAsync(workflowInstance.GetName(), workflowInstance.GetNamespace()!).ConfigureAwait(false); + } + + /// + /// Suspends selected instances + /// + /// A awaitable task + public async Task OnSuspendSelectedInstancesAsync() + { + var selectedResourcesNames = this.Get(state => state.SelectedResourceNames); + var resources = (this.Get(state => state.Resources) ?? []).Where(resource => selectedResourcesNames.Contains(resource.GetName())); + foreach (var resource in resources) + { + await this.SuspendInstanceAsync(resource); + } + this.Reduce(state => state with + { + SelectedResourceNames = [] + }); + } + + /// + /// Resumes selected instances + /// + /// A awaitable task + public async Task OnResumeSelectedInstancesAsync() + { + var selectedResourcesNames = this.Get(state => state.SelectedResourceNames); + var resources = (this.Get(state => state.Resources) ?? []).Where(resource => selectedResourcesNames.Contains(resource.GetName())); + foreach (var resource in resources) + { + await this.ResumeInstanceAsync(resource); + } + this.Reduce(state => state with + { + SelectedResourceNames = [] + }); + } + + /// + /// Cancels selected instances + /// + /// A awaitable task + public async Task OnCancelSelectedInstancesAsync() + { + var selectedResourcesNames = this.Get(state => state.SelectedResourceNames); + var resources = (this.Get(state => state.Resources) ?? []).Where(resource => selectedResourcesNames.Contains(resource.GetName())); + foreach (var resource in resources) + { + await this.CancelInstanceAsync(resource); + } + this.Reduce(state => state with + { + SelectedResourceNames = [] + }); + } + /// /// Creates a new instance of the workflow /// diff --git a/src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/View.razor b/src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/View.razor index 90d274868..401e2a733 100644 --- a/src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/View.razor +++ b/src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/View.razor @@ -36,9 +36,15 @@ Columns="@columns" OnSearchInput="Store.SetSearchTerm" OnShowDetails="OnShowInstanceDetails" + OnSuspend="async (instance) => await Store.SuspendInstanceAsync(instance)" + OnResume="async (instance) => await Store.ResumeInstanceAsync(instance)" + OnCancel="async (instance) => await Store.CancelInstanceAsync(instance)" + OnReplay="async (instance) => await Store.OnShowCreateInstanceAsync(workflowDefinition, instance.Spec?.Input)" OnDelete="OnDeleteWorkflowInstanceAsync" - OnReplay="async instance => await Store.OnShowCreateInstanceAsync(workflowDefinition, instance.Spec?.Input)" OnToggleSelected="Store.ToggleResourceSelection" + OnSuspendSelected="async () => await Store.OnSuspendSelectedInstancesAsync()" + OnResumeSelected="async () => await Store.OnResumeSelectedInstancesAsync()" + OnCancelSelected="async () => await Store.OnCancelSelectedInstancesAsync()" OnDeleteSelected="OnDeleteSelectedResourcesAsync" />