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:
parent
1c7a96034c
commit
feb7da4d28
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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>()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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": []
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue