Implemented workflows reload endpoint (#5732)

* Implemented workflows reload endpoint

* Added componenent tests for reload

* Adjusted cache handling

* Made the refresh endpoint return refreshed and not found definitions

* Update test/component/Elsa.Workflows.ComponentTests/Scenarios/WorkflowDefinitionReload/RemoveReloadWorkflowTests.cs

Fix typo

Co-authored-by: raymonddenhaan <155616759+raymonddenhaan@users.noreply.github.com>

---------

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
Co-authored-by: raymonddenhaan <155616759+raymonddenhaan@users.noreply.github.com>
This commit is contained in:
MariusVuscanNx 2024-07-12 09:35:46 +03:00 committed by GitHub
parent 1c7a96034c
commit feb7da4d28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 336 additions and 41 deletions

View file

@ -15,17 +15,18 @@ namespace Elsa.Http.Handlers;
/// A handler that invalidates the HTTP workflows cache when a workflow definition is published, retracted, or deleted or when triggers are indexed.
/// </summary>
[UsedImplicitly]
public class InvalidateHttpWorkflowsCache(IHttpWorkflowsCacheManager httpWorkflowsCacheManager,
ITriggerStore triggerStore,
IHttpWorkflowsCacheManager cacheManager) :
public class InvalidateHttpWorkflowsCache(
IHttpWorkflowsCacheManager httpWorkflowsCacheManager,
ITriggerStore triggerStore) :
INotificationHandler<WorkflowDefinitionPublished>,
INotificationHandler<WorkflowDefinitionRetracted>,
INotificationHandler<WorkflowDefinitionVersionsUpdated>,
INotificationHandler<WorkflowDefinitionDeleted>,
INotificationHandler<WorkflowDefinitionDeleted>,
INotificationHandler<WorkflowDefinitionsDeleted>,
INotificationHandler<WorkflowDefinitionVersionDeleted>,
INotificationHandler<WorkflowDefinitionVersionsDeleted>,
INotificationHandler<WorkflowTriggersIndexed>
INotificationHandler<WorkflowTriggersIndexed>,
INotificationHandler<WorkflowDefinitionsReloaded>
{
/// <inheritdoc />
public Task HandleAsync(WorkflowDefinitionPublished notification, CancellationToken cancellationToken)
@ -91,6 +92,15 @@ public class InvalidateHttpWorkflowsCache(IHttpWorkflowsCacheManager httpWorkflo
await InvalidateCacheAsync(notification.IndexedWorkflowTriggers.Workflow.Identity.DefinitionId);
}
/// <inheritdoc />
public async Task HandleAsync(WorkflowDefinitionsReloaded notification, CancellationToken cancellationToken)
{
foreach (var workflowDefinitionId in notification.WorkflowDefinitionIds)
{
await InvalidateCacheAsync(workflowDefinitionId);
}
}
private async Task InvalidateCacheAsync(string workflowDefinitionId)
{
await httpWorkflowsCacheManager.EvictWorkflowAsync(workflowDefinitionId);
@ -103,7 +113,7 @@ public class InvalidateHttpWorkflowsCache(IHttpWorkflowsCacheManager httpWorkflo
WorkflowDefinitionVersionId = workflowDefinitionVersionId
};
var triggers = await triggerStore.FindManyAsync(filter, cancellationToken);
await InvalidateTriggerCacheAsync(triggers, cancellationToken);
}
@ -113,7 +123,7 @@ public class InvalidateHttpWorkflowsCache(IHttpWorkflowsCacheManager httpWorkflo
{
if (trigger?.Payload is HttpEndpointBookmarkPayload httpPayload)
{
var hash = cacheManager.ComputeBookmarkHash(httpPayload.Path, httpPayload.Method);
var hash = httpWorkflowsCacheManager.ComputeBookmarkHash(httpPayload.Path, httpPayload.Method);
await httpWorkflowsCacheManager.EvictTriggerAsync(hash, cancellationToken);
}
}

View file

@ -18,7 +18,8 @@ public class WorkflowDefinitionEventsConsumer(IWorkflowDefinitionActivityRegistr
global::MassTransit.IConsumer<WorkflowDefinitionVersionDeleted>,
global::MassTransit.IConsumer<WorkflowDefinitionVersionsDeleted>,
global::MassTransit.IConsumer<WorkflowDefinitionVersionsUpdated>,
global::MassTransit.IConsumer<WorkflowDefinitionsRefreshed>
global::MassTransit.IConsumer<WorkflowDefinitionsRefreshed>,
global::MassTransit.IConsumer<WorkflowDefinitionsReloaded>
{
/// <inheritdoc />
public Task Consume(ConsumeContext<WorkflowDefinitionDeleted> context)
@ -82,9 +83,19 @@ public class WorkflowDefinitionEventsConsumer(IWorkflowDefinitionActivityRegistr
{
var message = context.Message;
var notification = new Elsa.Workflows.Runtime.Notifications.WorkflowDefinitionsRefreshed(message.WorkflowDefinitionIds);
AmbientConsumerScope.IsConsumerExecutionContext = true;
AmbientConsumerScope.IsWorkflowDefinitionEventsConsumer = true;
await notificationSender.SendAsync(notification, context.CancellationToken);
AmbientConsumerScope.IsConsumerExecutionContext = false;
AmbientConsumerScope.IsWorkflowDefinitionEventsConsumer = false;
}
/// <inheritdoc />
public async Task Consume(ConsumeContext<WorkflowDefinitionsReloaded> context)
{
var message = context.Message;
var notification = new Elsa.Workflows.Runtime.Notifications.WorkflowDefinitionsReloaded(message.WorkflowDefinitionIds);
AmbientConsumerScope.IsWorkflowDefinitionEventsConsumer = true;
await notificationSender.SendAsync(notification, context.CancellationToken);
AmbientConsumerScope.IsWorkflowDefinitionEventsConsumer = false;
}
private Task UpdateDefinition(string id, bool usableAsActivity)

View file

@ -1,4 +1,3 @@
using Elsa.MassTransit.Contracts;
using Elsa.MassTransit.Services;
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Management.Notifications;
@ -18,7 +17,8 @@ public class DistributedWorkflowDefinitionNotificationsHandler(IBus bus) :
INotificationHandler<WorkflowDefinitionVersionDeleted>,
INotificationHandler<WorkflowDefinitionVersionsDeleted>,
INotificationHandler<WorkflowDefinitionVersionsUpdated>,
INotificationHandler<WorkflowDefinitionsRefreshed>
INotificationHandler<WorkflowDefinitionsRefreshed>,
INotificationHandler<WorkflowDefinitionsReloaded>
{
/// <inheritdoc />
public Task HandleAsync(WorkflowDefinitionPublished notification, CancellationToken cancellationToken)
@ -73,11 +73,23 @@ public class DistributedWorkflowDefinitionNotificationsHandler(IBus bus) :
public Task HandleAsync(WorkflowDefinitionsRefreshed notification, CancellationToken cancellationToken)
{
// Prevent re-entrance.
if (AmbientConsumerScope.IsConsumerExecutionContext)
if (AmbientConsumerScope.IsWorkflowDefinitionEventsConsumer)
return Task.CompletedTask;
var definitionIds = notification.WorkflowDefinitionIds;
var message = new Distributed.WorkflowDefinitionsRefreshed(definitionIds);
return bus.Publish(message, cancellationToken);
}
/// <inheritdoc />
public Task HandleAsync(WorkflowDefinitionsReloaded notification, CancellationToken cancellationToken)
{
// Prevent re-entrance.
if (AmbientConsumerScope.IsWorkflowDefinitionEventsConsumer)
return Task.CompletedTask;
var definitionIds = notification.WorkflowDefinitionIds;
var message = new Distributed.WorkflowDefinitionsReloaded(definitionIds);
return bus.Publish(message, cancellationToken);
}
}

View file

@ -0,0 +1,8 @@
namespace Elsa.MassTransit.Messages;
/// Represents a message that indicates that the specified workflow definitions have been reloaded.
public class WorkflowDefinitionsReloaded(ICollection<string> workflowDefinitionIds)
{
/// The workflow definition IDs that have been reloaded.
public ICollection<string> WorkflowDefinitionIds { get; set; } = workflowDefinitionIds;
}

View file

@ -6,7 +6,7 @@ public static class AmbientConsumerScope
private static readonly AsyncLocal<bool> IsRaisedFromConsumerState = new();
/// Gets or sets a value that indicates if the current code is initiated from a consumer.
public static bool IsConsumerExecutionContext
public static bool IsWorkflowDefinitionEventsConsumer
{
get => IsRaisedFromConsumerState.Value;
set => IsRaisedFromConsumerState.Value = value;

View file

@ -1,6 +1,7 @@
using Elsa.Abstractions;
using Elsa.Workflows.Runtime.Contracts;
using Elsa.Workflows.Runtime.Requests;
using Elsa.Workflows.Runtime.Responses;
using JetBrains.Annotations;
namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Refresh;
@ -18,13 +19,13 @@ internal class Refresh(IWorkflowDefinitionsRefresher workflowDefinitionsRefreshe
public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
{
await RefreshWorkflowDefinitionsAsync(request.DefinitionIds, cancellationToken);
await SendOkAsync(cancellationToken);
var result = await RefreshWorkflowDefinitionsAsync(request.DefinitionIds, cancellationToken);
await SendOkAsync(new Response(result.Refreshed, result.NotFound), cancellationToken);
}
private async Task RefreshWorkflowDefinitionsAsync(ICollection<string>? definitionIds, CancellationToken cancellationToken)
private async Task<RefreshWorkflowDefinitionsResponse> RefreshWorkflowDefinitionsAsync(ICollection<string>? definitionIds, CancellationToken cancellationToken)
{
var request = new RefreshWorkflowDefinitionsRequest(definitionIds, BatchSize);
await workflowDefinitionsRefresher.RefreshWorkflowDefinitionsAsync(request, cancellationToken);
return await workflowDefinitionsRefresher.RefreshWorkflowDefinitionsAsync(request, cancellationToken);
}
}

View file

@ -1,6 +1,14 @@
using System.Text.Json.Serialization;
namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Refresh;
public class Request
internal class Request
{
public ICollection<string>? DefinitionIds { get; set; }
}
internal class Response(ICollection<string> refreshed, ICollection<string> notFound)
{
[JsonPropertyName("refreshed")] public ICollection<string> Refreshed { get; } = refreshed;
[JsonPropertyName("notFound")] public ICollection<string> NotFound { get; } = notFound;
}

View file

@ -0,0 +1,28 @@
using Elsa.Abstractions;
using Elsa.Workflows.Runtime.Contracts;
using JetBrains.Annotations;
namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Reload;
[PublicAPI]
internal class Reload(IWorkflowDefinitionsReloader workflowDefinitionsReloader) : ElsaEndpointWithoutRequest
{
private const int BatchSize = 10;
public override void Configure()
{
Post("/actions/workflow-definitions/reload");
ConfigurePermissions("actions:workflow-definitions:reload");
}
public override async Task HandleAsync(CancellationToken cancellationToken)
{
await ReloadWorkflowDefinitionsAsync(cancellationToken);
await SendOkAsync(cancellationToken);
}
private async Task ReloadWorkflowDefinitionsAsync(CancellationToken cancellationToken)
{
await workflowDefinitionsReloader.ReloadWorkflowDefinitionsAsync(cancellationToken);
}
}

View file

@ -12,14 +12,14 @@ public interface IWorkflowDefinitionStorePopulator
/// Populates the <see cref="IWorkflowDefinitionStore"/> with workflow definitions provided from <see cref="IWorkflowProvider"/> implementations.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
Task PopulateStoreAsync(CancellationToken cancellationToken = default);
Task<ICollection<string>> PopulateStoreAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Populates the <see cref="IWorkflowDefinitionStore"/> with workflow definitions provided from <see cref="IWorkflowProvider"/> implementations.
/// </summary>
/// <param name="indexTriggers">Whether to index triggers.</param>
/// <param name="cancellationToken">The cancellation token.</param>
Task PopulateStoreAsync(bool indexTriggers, CancellationToken cancellationToken = default);
Task<ICollection<string>> PopulateStoreAsync(bool indexTriggers, CancellationToken cancellationToken = default);
/// <summary>
/// Adds a workflow definition to the store.

View file

@ -0,0 +1,8 @@
namespace Elsa.Workflows.Runtime.Contracts;
/// Reloads all workflows by re-invoking the populator.
public interface IWorkflowDefinitionsReloader
{
/// Reloads all workflows by re-invoking the populator.
Task ReloadWorkflowDefinitionsAsync(CancellationToken cancellationToken = default);
}

View file

@ -221,6 +221,7 @@ public class WorkflowRuntimeFeature : FeatureBase
.AddScoped<IWorkflowDefinitionStorePopulator, DefaultWorkflowDefinitionStorePopulator>()
.AddScoped<IRegistriesPopulator, DefaultRegistriesPopulator>()
.AddScoped<IWorkflowDefinitionsRefresher, WorkflowDefinitionsRefresher>()
.AddScoped<IWorkflowDefinitionsReloader, WorkflowDefinitionsReloader>()
.AddScoped<IWorkflowRegistry, DefaultWorkflowRegistry>()
.AddScoped<ITaskReporter, TaskReporter>()
.AddScoped<SynchronousTaskDispatcher>()

View file

@ -15,11 +15,19 @@ namespace Elsa.Workflows.Runtime.Handlers;
/// The cache is invalidated by calling the <c>TriggerTokenAsync</c> method of the <c>ICacheManager</c> passed to the class constructor.
/// </remarks>
[UsedImplicitly]
public class InvalidateTriggersCache(ICacheManager cacheManager) : INotificationHandler<WorkflowDefinitionsRefreshed>
public class InvalidateTriggersCache(ICacheManager cacheManager) :
INotificationHandler<WorkflowDefinitionsRefreshed>,
INotificationHandler<WorkflowDefinitionsReloaded>
{
/// <inheritdoc />
public Task HandleAsync(WorkflowDefinitionsRefreshed notification, CancellationToken cancellationToken)
{
return cacheManager.TriggerTokenAsync(CachingTriggerStore.CacheInvalidationTokenKey, cancellationToken).AsTask();
}
/// <inheritdoc />
public async Task HandleAsync(WorkflowDefinitionsReloaded notification, CancellationToken cancellationToken)
{
await cacheManager.TriggerTokenAsync(CachingTriggerStore.CacheInvalidationTokenKey, cancellationToken);
}
}

View file

@ -0,0 +1,26 @@
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Management.Contracts;
using Elsa.Workflows.Runtime.Notifications;
using JetBrains.Annotations;
namespace Elsa.Workflows.Runtime.Handlers;
/// <summary>
/// A notification handler that invalidates workflows cache when workflow definitions are reloaded.
/// </summary>
/// <remarks>
/// The class implements the <c>INotificationHandler</c> interface and is responsible for handling <c>WorkflowDefinitionsReloaded</c> notifications.
/// When a <c>WorkflowDefinitionsReloaded</c> notification is received, the <c>HandleAsync</c> method is called to invalidate the http definition cache.
/// </remarks>
[UsedImplicitly]
public class InvalidateWorkflowsCache(IWorkflowDefinitionCacheManager workflowDefinitionCacheManager) : INotificationHandler<WorkflowDefinitionsReloaded>
{
/// <inheritdoc />
public async Task HandleAsync(WorkflowDefinitionsReloaded notification, CancellationToken cancellationToken)
{
foreach (var workflowDefinitionId in notification.WorkflowDefinitionIds)
{
await workflowDefinitionCacheManager.EvictWorkflowDefinitionAsync(workflowDefinitionId, cancellationToken);
}
}
}

View file

@ -0,0 +1,6 @@
using Elsa.Mediator.Contracts;
namespace Elsa.Workflows.Runtime.Notifications;
/// Published when workflow definitions have been reloaded.
public record WorkflowDefinitionsReloaded(ICollection<string> WorkflowDefinitionIds) : INotification;

View file

@ -3,4 +3,4 @@ using Elsa.Workflows.Management.Entities;
namespace Elsa.Workflows.Runtime.Responses;
/// Represents a response to a request to refresh workflow definitions.
public record RefreshWorkflowDefinitionsResponse(ICollection<WorkflowDefinition> WorkflowDefinitions);
public record RefreshWorkflowDefinitionsResponse(ICollection<string> Refreshed, ICollection<string> NotFound);

View file

@ -49,21 +49,26 @@ public class DefaultWorkflowDefinitionStorePopulator : IWorkflowDefinitionStoreP
}
/// <inheritdoc />
public Task PopulateStoreAsync(CancellationToken cancellationToken = default)
public Task<ICollection<string>> PopulateStoreAsync(CancellationToken cancellationToken = default)
{
return PopulateStoreAsync(true, cancellationToken);
}
/// <inheritdoc />
public async Task PopulateStoreAsync(bool indexTriggers, CancellationToken cancellationToken = default)
public async Task<ICollection<string>> PopulateStoreAsync(bool indexTriggers, CancellationToken cancellationToken = default)
{
var providers = _workflowDefinitionProviders();
var workflowDefinitionIds = new List<string>();
foreach (var provider in providers)
{
var results = await provider.GetWorkflowsAsync(cancellationToken).AsTask().ToList();
workflowDefinitionIds.AddRange(results.Select(w => w.Workflow.Id));
foreach (var result in results) await AddAsync(result, indexTriggers, cancellationToken);
}
return workflowDefinitionIds;
}
/// <inheritdoc />
@ -131,8 +136,8 @@ public class DefaultWorkflowDefinitionStorePopulator : IWorkflowDefinitionStoreP
if (existingDefinitionVersion != null)
{
workflowDefinitionsToSave.Add(existingDefinitionVersion);
if(existingDefinitionVersion.Id != workflow.Identity.Id)
if (existingDefinitionVersion.Id != workflow.Identity.Id)
{
// It's possible that the imported workflow definition has a different ID than the existing one in the store.
// In a future update, we might store this discrepancy in a "troubleshooting" table and provide tooling for managing these, and other, discrepancies.

View file

@ -39,12 +39,15 @@ public class WorkflowDefinitionsRefresher(IWorkflowDefinitionStore store, ITrigg
await IndexWorkflowTriggersAsync(definitions.Items, cancellationToken);
processedWorkflowDefinitions.AddRange(definitions.Items);
currentPage++;
if (definitions.Items.Count < batchSize)
break;
}
var processedWorkflowDefinitionIds = processedWorkflowDefinitions.Select(x => x.Id).ToList();
var processedWorkflowDefinitionIds = processedWorkflowDefinitions.Select(x => x.DefinitionId).ToList();
var notification = new WorkflowDefinitionsRefreshed(processedWorkflowDefinitionIds);
await notificationSender.SendAsync(notification, cancellationToken);
return new RefreshWorkflowDefinitionsResponse(processedWorkflowDefinitions);
return new RefreshWorkflowDefinitionsResponse(processedWorkflowDefinitionIds, request.DefinitionIds?.Except(processedWorkflowDefinitionIds)?.ToList() ?? []);
}
private async Task IndexWorkflowTriggersAsync(IEnumerable<WorkflowDefinition> definitions, CancellationToken cancellationToken)

View file

@ -0,0 +1,17 @@
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Runtime.Contracts;
using Elsa.Workflows.Runtime.Notifications;
namespace Elsa.Workflows.Runtime.Services;
/// <inheritdoc />
public class WorkflowDefinitionsReloader(IWorkflowDefinitionStorePopulator workflowDefinitionStorePopulator, INotificationSender notificationSender) : IWorkflowDefinitionsReloader
{
/// <inheritdoc />
public async Task ReloadWorkflowDefinitionsAsync(CancellationToken cancellationToken)
{
var definitionIds = await workflowDefinitionStorePopulator.PopulateStoreAsync(true, cancellationToken);
var notification = new WorkflowDefinitionsReloaded(definitionIds);
await notificationSender.SendAsync(notification, cancellationToken);
}
}

View file

@ -21,6 +21,9 @@
</ItemGroup>
<ItemGroup>
<None Update="Scenarios\WorkflowDefinitionReload\http-workflow.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="xunit.runner.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
@ -75,6 +78,9 @@
<None Update="Scenarios\WorkflowDefinitionRefresh\dynamic-endpoint-http-workflow.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Scenarios\WorkflowDefinitionReload\http-workflow.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View file

@ -1,4 +1,5 @@
using Elsa.Workflows.ComponentTests.Helpers.Services;
using System.Net;
using Elsa.Workflows.ComponentTests.Helpers.Services;
using Elsa.Workflows.Runtime.Contracts;
using Microsoft.Extensions.DependencyInjection;
@ -15,22 +16,19 @@ public class DynamicEndpointTests : AppComponentTest
}
[Fact]
public async Task HelloWorldWorkflow_ShouldRespondWithHelloWorld()
public async Task ChangingEndpointValueThenRefresh_WorkflowShouldRespondToTheNewValue()
{
var client = WorkflowServer.CreateHttpWorkflowClient();
var firstResponse = await client.GetStringAsync("first-value");
var firstResponse = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "first-value"));
StaticValueHolder.Value = "second-value";
await _workflowDefinitionsRefresher.RefreshWorkflowDefinitionsAsync(new Runtime.Requests.RefreshWorkflowDefinitionsRequest
{
BatchSize = 10,
DefinitionIds = ["f69f061159adc3ae"]
}, CancellationToken.None);
var _ = await _workflowDefinitionsRefresher.RefreshWorkflowDefinitionsAsync(
new Runtime.Requests.RefreshWorkflowDefinitionsRequest() { DefinitionIds = ["f69f061159adc3ae"] }, CancellationToken.None);
var secondResponse = await client.GetStringAsync("second-value");
var secondResponse = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "second-value"));
Assert.Equal("", firstResponse);
Assert.Equal("", secondResponse);
Assert.Equal(HttpStatusCode.OK, firstResponse.StatusCode);
Assert.Equal(HttpStatusCode.OK, secondResponse.StatusCode);
}
}

View file

@ -0,0 +1,35 @@
using System.Net;
using Elsa.Workflows.Management.Contracts;
using Elsa.Workflows.Runtime.Contracts;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.WorkflowDefinitionReload;
public class RemoveReloadWorkflowTests : AppComponentTest
{
private readonly IWorkflowDefinitionManager _workflowDefinitionManager;
private readonly IWorkflowDefinitionsReloader _workflowDefinitionsReloader;
public RemoveReloadWorkflowTests(App app) : base(app)
{
_workflowDefinitionManager = Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionManager>();
_workflowDefinitionsReloader = Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionsReloader>();
}
[Fact]
public async Task RemovingTheWorkflowThenReload_WorkflowShouldBeReachableAgain()
{
var client = WorkflowServer.CreateHttpWorkflowClient();
var result = await _workflowDefinitionManager.DeleteByDefinitionIdAsync("f68b09bc-2013-4617-b82f-d76b6819a624", CancellationToken.None);
var firstResponse = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "reload-test"));
await _workflowDefinitionsReloader.ReloadWorkflowDefinitionsAsync(CancellationToken.None);
var secondResponse = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "reload-test"));
Assert.Equal(HttpStatusCode.NotFound, firstResponse.StatusCode);
Assert.Equal(HttpStatusCode.OK, secondResponse.StatusCode);
}
}

View file

@ -0,0 +1,104 @@
{
"id": "c2e19917-d03e-41ed-9d19-ef5c1ddc9871",
"definitionId": "f68b09bc-2013-4617-b82f-d76b6819a624",
"name": "Workflow 1",
"createdAt": "2024-07-04T13:41:33.1905134+00:00",
"version": 1,
"toolVersion": "3.3.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "f8019bae-c506-4467-812b-e403e7aad8ff",
"nodeId": "Workflow1:f8019bae-c506-4467-812b-e403e7aad8ff",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"path": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "reload-test"
}
},
"supportedMethods": {
"typeName": "String[]",
"expression": {
"type": "Literal",
"value": "[\u0022GET\u0022]"
}
},
"authorize": {
"typeName": "Boolean",
"expression": {
"type": "Literal",
"value": false
}
},
"policy": {
"typeName": "String",
"expression": {
"type": "Literal"
}
},
"requestTimeout": null,
"requestSizeLimit": null,
"fileSizeLimit": null,
"allowedFileExtensions": null,
"blockedFileExtensions": null,
"allowedMimeTypes": null,
"exposeRequestTooLargeOutcome": false,
"exposeFileTooLargeOutcome": false,
"exposeInvalidFileExtensionOutcome": false,
"exposeInvalidFileMimeTypeOutcome": false,
"parsedContent": null,
"files": null,
"routeData": null,
"queryStringData": null,
"headers": null,
"result": null,
"id": "cc196a5e-04f7-4794-b25c-1a0987632d8a",
"nodeId": "Workflow1:f8019bae-c506-4467-812b-e403e7aad8ff:cc196a5e-04f7-4794-b25c-1a0987632d8a",
"name": "HttpEndpoint1",
"type": "Elsa.HttpEndpoint",
"version": 1,
"customProperties": {
"canStartWorkflow": true,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -260,
"y": 460
},
"size": {
"width": 176.390625,
"height": 50
}
}
}
}
],
"connections": []
}
}