From 149f91e53815d991ae1fd1a6ab446006dee4be85 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 18 Jul 2024 07:51:46 +0200 Subject: [PATCH] Update API endpoint for Polling Observer (#5787) * Rename and refactor journal update endpoint Replaced `/workflow-instances/{id}/journal/has-updates` endpoint with `/workflow-instances/{id}/updated-at` to simplify API responses. Deleted `HasUpdates` related classes and introduced `GetUpdatedAtResponse` for consistency and clarity. Updated client contracts accordingly. * Remove HasUpdates endpoint and refactor workflow observer Deleted the HasUpdates endpoint and refactored related code to use an updated timestamp approach instead. Improved nullable handling in WorkflowInstanceDesigner and ensured proper observer disposal to avoid memory leaks. Updated workflow observer factory and observer implementations to support observer names and enhanced logging. * Rename updated workflow instance endpoint and handle execution state Renamed the endpoint from "/updated-at" to "/execution-state" to better reflect its purpose. Updated related response models and documentation to capture workflow execution state details such as status, sub-status, and last updated timestamp. * Enable SignalR for real-time workflows Add a flag to use SignalR and activate real-time workflows when enabled. Refactor code to wrap SignalR setup in conditional checks based on the new flag. This enhances the application's interactivity through real-time capabilities. * Remove obsolete endpoints and rename execution state paths Deleted the outdated Api1 and DynamicWorkflows endpoints under Elsa.Server.Web. Also, renamed paths related to execution state models and endpoint to remove "Journal" from the namespace for better clarity and organization. --- .../Endpoints/Api1/Get/Endpoint.cs | 33 ----------------- .../DynamicWorkflows/Post/Endpoint.cs | 37 ------------------- src/bundles/Elsa.Server.Web/Program.cs | 18 +++++---- .../Contracts/IWorkflowInstancesApi.cs | 12 +++--- .../Models/HasJournalUpdateRequest.cs | 11 ------ .../WorkflowInstanceExecutionStateResponse.cs | 6 +++ .../ExecutionState/Endpoint.cs | 37 +++++++++++++++++++ .../ExecutionState/Models.cs | 13 +++++++ .../Journal/HasUpdates/Endpoint.cs | 32 ---------------- .../Journal/HasUpdates/Models.cs | 13 ------- 10 files changed, 73 insertions(+), 139 deletions(-) delete mode 100644 src/bundles/Elsa.Server.Web/Endpoints/Api1/Get/Endpoint.cs delete mode 100644 src/bundles/Elsa.Server.Web/Endpoints/DynamicWorkflows/Post/Endpoint.cs delete mode 100644 src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/HasJournalUpdateRequest.cs create mode 100644 src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Responses/WorkflowInstanceExecutionStateResponse.cs create mode 100644 src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/ExecutionState/Endpoint.cs create mode 100644 src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/ExecutionState/Models.cs delete mode 100644 src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/HasUpdates/Endpoint.cs delete mode 100644 src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/HasUpdates/Models.cs diff --git a/src/bundles/Elsa.Server.Web/Endpoints/Api1/Get/Endpoint.cs b/src/bundles/Elsa.Server.Web/Endpoints/Api1/Get/Endpoint.cs deleted file mode 100644 index 5d727bb33..000000000 --- a/src/bundles/Elsa.Server.Web/Endpoints/Api1/Get/Endpoint.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Elsa.Abstractions; -using Elsa.Workflows.Activities; -using Elsa.Workflows.Models; -using Elsa.Workflows.Runtime.Contracts; -using Elsa.Workflows.Runtime.Parameters; -using JetBrains.Annotations; - -namespace Elsa.Server.Web.Endpoints.Api1.Get; - -/// -/// Returns a message. -/// -[UsedImplicitly] -public class Get : ElsaEndpointWithoutRequest -{ - /// - public override void Configure() - { - Get("/api-1"); - AllowAnonymous(); - } - - /// - public override async Task HandleAsync(CancellationToken ct) - { - await Task.Delay(1000, ct); - var response = new - { - Message = "OK" - }; - await SendOkAsync(response, ct); - } -} \ No newline at end of file diff --git a/src/bundles/Elsa.Server.Web/Endpoints/DynamicWorkflows/Post/Endpoint.cs b/src/bundles/Elsa.Server.Web/Endpoints/DynamicWorkflows/Post/Endpoint.cs deleted file mode 100644 index f72fb576e..000000000 --- a/src/bundles/Elsa.Server.Web/Endpoints/DynamicWorkflows/Post/Endpoint.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Elsa.Abstractions; -using Elsa.Workflows.Activities; -using Elsa.Workflows.Models; -using Elsa.Workflows.Runtime.Contracts; -using Elsa.Workflows.Runtime.Options; -using Elsa.Workflows.Runtime.Parameters; - -namespace Elsa.Server.Web.Endpoints.DynamicWorkflows.Post; - -public class Post(IWorkflowRegistry workflowRegistry, IWorkflowRuntime workflowRuntime) : ElsaEndpointWithoutRequest -{ - public override void Configure() - { - Post("/dynamic-workflows"); - AllowAnonymous(); - } - - public override async Task HandleAsync(CancellationToken ct) - { - var workflow = new Workflow - { - Identity = new WorkflowIdentity("DynamicWorkflow1", 1, "DynamicWorkflow1:v1"), - Root = new Sequence - { - Activities = - { - new WriteLine("Step 1"), - new WriteLine("Step 2"), - new WriteLine("Step 3") - } - } - }; - - await workflowRegistry.RegisterAsync(workflow, ct); - await workflowRuntime.StartWorkflowAsync("DynamicWorkflow1", new StartWorkflowRuntimeParams()); - } -} \ No newline at end of file diff --git a/src/bundles/Elsa.Server.Web/Program.cs b/src/bundles/Elsa.Server.Web/Program.cs index 9c41549da..c4f259015 100644 --- a/src/bundles/Elsa.Server.Web/Program.cs +++ b/src/bundles/Elsa.Server.Web/Program.cs @@ -48,6 +48,7 @@ const bool runEFCoreMigrations = true; const bool useMemoryStores = false; const bool useCaching = true; const bool useReadOnlyMode = false; +const bool useSignalR = true; const DistributedCachingTransport distributedCachingTransport = DistributedCachingTransport.MassTransit; const MassTransitBroker useMassTransitBroker = MassTransitBroker.Memory; @@ -250,7 +251,6 @@ services { api.AddFastEndpointsAssembly(); }) - .UseRealTimeWorkflows() .UseCSharp(options => { options.AppendScript("string Greet(string name) => $\"Hello {name}!\";"); @@ -322,6 +322,11 @@ services elsa.UseQuartz(quartz => { quartz.UseSqlite(sqliteConnectionString); }); } + if (useSignalR) + { + elsa.UseRealTimeWorkflows(); + } + if (useMassTransit) { elsa.UseMassTransit(massTransit => @@ -418,19 +423,18 @@ if (app.Environment.IsDevelopment()) } // SignalR. -app.UseWorkflowsSignalRHubs(); +if (useSignalR) +{ + app.UseWorkflowsSignalRHubs(); +} // Run. app.Run(); -/// /// The main entry point for the application made public for end to end testing. -/// [UsedImplicitly] public partial class Program { - /// /// Set by the test runner to configure the module for testing. - /// public static Action? ConfigureForTest { get; set; } -} +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Contracts/IWorkflowInstancesApi.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Contracts/IWorkflowInstancesApi.cs index 4a50a368c..a225e57ad 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Contracts/IWorkflowInstancesApi.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Contracts/IWorkflowInstancesApi.cs @@ -1,6 +1,7 @@ using Elsa.Api.Client.Resources.WorkflowDefinitions.Models; using Elsa.Api.Client.Resources.WorkflowInstances.Models; using Elsa.Api.Client.Resources.WorkflowInstances.Requests; +using Elsa.Api.Client.Resources.WorkflowInstances.Responses; using Elsa.Api.Client.Shared.Models; using Refit; @@ -47,14 +48,13 @@ public interface IWorkflowInstancesApi Task> GetFilteredJournalAsync(string workflowInstanceId, GetFilteredJournalRequest? filter, int? skip = default, int? take = default, CancellationToken cancellationToken = default); /// - /// Checks if there are updates in the journal for a specific workflow instance. + /// Returns the execution state of the specified workflow instance. /// - /// The ID of the workflow instance for which to check for updates. - /// The request containing the ID and time from which to check for updates. + /// The ID of the workflow instance for which to return its execution state. /// The cancellation token. - /// Returns whether updates are available for the journal. - [Get("/workflow-instances/{workflowInstanceId}/journal/has-updates")] - Task HasJournalUpdates(string workflowInstanceId, [Query]HasJournalUpdateRequest request, CancellationToken cancellationToken = default); + /// Returns a response containing the execution state. + [Get("/workflow-instances/{workflowInstanceId}/execution-state")] + Task GetExecutionStateAsync(string workflowInstanceId, CancellationToken cancellationToken = default); /// /// Deletes a workflow instance. diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/HasJournalUpdateRequest.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/HasJournalUpdateRequest.cs deleted file mode 100644 index 6efb34477..000000000 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/HasJournalUpdateRequest.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Elsa.Api.Client.Resources.WorkflowInstances.Models; - -/// A request to update a journal for a workflow instance. -public class HasJournalUpdateRequest -{ - /// The unique identifier of a workflow instance. - public string WorkflowInstanceId { get; set; } = default!; - - /// The start date for checking for updates in the workflow instance journal. - public DateTimeOffset UpdatesSince { get; set; } -} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Responses/WorkflowInstanceExecutionStateResponse.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Responses/WorkflowInstanceExecutionStateResponse.cs new file mode 100644 index 000000000..e3262e1f8 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Responses/WorkflowInstanceExecutionStateResponse.cs @@ -0,0 +1,6 @@ +using Elsa.Api.Client.Resources.WorkflowInstances.Enums; + +namespace Elsa.Api.Client.Resources.WorkflowInstances.Responses; + +/// Represents the response containing the last updated timestamp of a workflow instance. +public record WorkflowInstanceExecutionStateResponse(WorkflowStatus Status, WorkflowSubStatus WorkflowSubStatus, DateTimeOffset UpdatedAt); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/ExecutionState/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/ExecutionState/Endpoint.cs new file mode 100644 index 000000000..96ccc853d --- /dev/null +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/ExecutionState/Endpoint.cs @@ -0,0 +1,37 @@ +using Elsa.Abstractions; +using Elsa.Extensions; +using Elsa.Workflows.Management.Contracts; +using JetBrains.Annotations; + +namespace Elsa.Workflows.Api.Endpoints.WorkflowInstances.ExecutionState; + +/// Returns the execution state of the specified workflow instance. +[PublicAPI] +internal class ExecutionState(IWorkflowInstanceStore store) : ElsaEndpoint +{ + /// + public override void Configure() + { + Get("/workflow-instances/{id}/execution-state"); + ConfigurePermissions("read:workflow-instances"); + } + + /// + public override async Task HandleAsync(Request request, CancellationToken cancellationToken) + { + var workflowInstance = await store.FindAsync(request.WorkflowInstanceId, cancellationToken); + + if (workflowInstance == null) + { + await SendNotFoundAsync(cancellationToken); + return; + } + + var response = new Response( + workflowInstance.Status, + workflowInstance.SubStatus, + workflowInstance.UpdatedAt); + + await SendOkAsync(response, cancellationToken); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/ExecutionState/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/ExecutionState/Models.cs new file mode 100644 index 000000000..4a0605239 --- /dev/null +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/ExecutionState/Models.cs @@ -0,0 +1,13 @@ +using FastEndpoints; + +namespace Elsa.Workflows.Api.Endpoints.WorkflowInstances.ExecutionState; + +/// The request to check the execution state of the workflow instance. +public class Request +{ + /// The unique identifier of a workflow instance. + [BindFrom("id")] public string WorkflowInstanceId { get; set; } = default!; +} + +/// Represents the response containing the last updated timestamp of a workflow instance. +public record Response(WorkflowStatus Status, WorkflowSubStatus SubStatus, DateTimeOffset UpdatedAt); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/HasUpdates/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/HasUpdates/Endpoint.cs deleted file mode 100644 index a535e25c9..000000000 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/HasUpdates/Endpoint.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Elsa.Abstractions; -using Elsa.Common.Entities; -using Elsa.Common.Models; -using Elsa.Workflows.Runtime.Contracts; -using Elsa.Workflows.Runtime.Filters; -using Elsa.Workflows.Runtime.OrderDefinitions; -using JetBrains.Annotations; - -namespace Elsa.Workflows.Api.Endpoints.WorkflowInstances.Journal.HasUpdates; - -/// Endpoint that checks if there are updates for a workflow instance. -[PublicAPI] -internal class HasUpdates(IWorkflowExecutionLogStore store) : ElsaEndpoint -{ - /// - public override void Configure() - { - Get("/workflow-instances/{id}/journal/has-updates"); - ConfigurePermissions("read:workflow-instances"); - } - - /// - public override async Task ExecuteAsync(Request request, CancellationToken cancellationToken) - { - var pageArgs = PageArgs.From(1, 1, 0, 1); - var filter = new WorkflowExecutionLogRecordFilter { WorkflowInstanceId = request.WorkflowInstanceId }; - var order = new WorkflowExecutionLogRecordOrder(x => x.Sequence, OrderDirection.Descending); - var pageOfRecords = await store.FindManyAsync(filter, pageArgs, order, cancellationToken); - - return pageOfRecords.Items.Any(item => item.Timestamp >= request.UpdatesSince); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/HasUpdates/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/HasUpdates/Models.cs deleted file mode 100644 index eefafc6d0..000000000 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/HasUpdates/Models.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FastEndpoints; - -namespace Elsa.Workflows.Api.Endpoints.WorkflowInstances.Journal.HasUpdates; - -/// The request to check if there are updates for a workflow instance journal. -public class Request -{ - /// The unique identifier of a workflow instance. - [BindFrom("id")] public string WorkflowInstanceId { get; set; } = default!; - - /// The start date for checking for updates in the workflow instance journal. - public DateTimeOffset UpdatesSince { get; set; } -} \ No newline at end of file