Create trigger payload validation, refacto HttpEndpoint validator and create Cron validator (#6641)
This commit is contained in:
parent
9cbf75115f
commit
c9bdcd5a84
|
|
@ -3,6 +3,7 @@ using Elsa.Extensions;
|
|||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Http.Bookmarks;
|
||||
using Elsa.Http.ContentWriters;
|
||||
using Elsa.Http.DownloadableContentHandlers;
|
||||
using Elsa.Http.FileCaches;
|
||||
|
|
@ -14,6 +15,7 @@ using Elsa.Http.Resilience;
|
|||
using Elsa.Http.Selectors;
|
||||
using Elsa.Http.Services;
|
||||
using Elsa.Http.Tasks;
|
||||
using Elsa.Http.TriggerPayloadValidators;
|
||||
using Elsa.Http.UIHints;
|
||||
using Elsa.Resilience.Extensions;
|
||||
using Elsa.Resilience.Features;
|
||||
|
|
@ -172,7 +174,6 @@ public class HttpFeature(IModule module) : FeatureBase(module)
|
|||
.AddHttpContextAccessor()
|
||||
|
||||
// Handlers.
|
||||
.AddNotificationHandler<ValidateWorkflowRequestHandler>()
|
||||
.AddNotificationHandler<UpdateRouteTable>()
|
||||
|
||||
// Content parsers.
|
||||
|
|
@ -220,6 +221,9 @@ public class HttpFeature(IModule module) : FeatureBase(module)
|
|||
.AddScoped<IDownloadableContentHandler, StringDownloadableContentHandler>()
|
||||
.AddScoped<IDownloadableContentHandler, HttpFileDownloadableContentHandler>()
|
||||
|
||||
//Trigger payload validators.
|
||||
.AddTriggerPaylodValidator<HttpEndpointTriggerPayloadValidator, HttpEndpointBookmarkPayload>()
|
||||
|
||||
// File caches.
|
||||
.AddScoped(FileCache)
|
||||
.AddScoped<ZipManager>()
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Http.Bookmarks;
|
||||
using Elsa.Mediator.Contracts;
|
||||
using Elsa.Workflows.Helpers;
|
||||
using Elsa.Workflows.Management.Notifications;
|
||||
using Elsa.Workflows.Runtime;
|
||||
using Elsa.Workflows.Runtime.Filters;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Http.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="ValidateWorkflowRequest"/> handler that validates a workflow path and return any errors.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class ValidateWorkflowRequestHandler : INotificationHandler<WorkflowDefinitionValidating>
|
||||
{
|
||||
private readonly ITriggerStore _triggerStore;
|
||||
private readonly ITriggerIndexer _triggerIndexer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public ValidateWorkflowRequestHandler(ITriggerStore triggerStore, ITriggerIndexer triggerIndexer)
|
||||
{
|
||||
_triggerStore = triggerStore;
|
||||
_triggerIndexer = triggerIndexer;
|
||||
}
|
||||
|
||||
public async Task HandleAsync(WorkflowDefinitionValidating notification, CancellationToken cancellationToken)
|
||||
{
|
||||
var workflow = notification.Workflow;
|
||||
var httpEndpointTriggers = (await _triggerIndexer.GetTriggersAsync(workflow, cancellationToken)).Where(x => x.Payload is HttpEndpointBookmarkPayload).ToList();
|
||||
var filter = new TriggerFilter
|
||||
{
|
||||
Name = ActivityTypeNameHelper.GenerateTypeName(typeof(HttpEndpoint))
|
||||
};
|
||||
var publishedWorkflowsTriggers = (await _triggerStore.FindManyAsync(filter, cancellationToken)).ToList();
|
||||
var validationErrors = notification.ValidationErrors;
|
||||
|
||||
foreach (var httpEndpointTrigger in httpEndpointTriggers)
|
||||
{
|
||||
var triggerPayload = httpEndpointTrigger.GetPayload<HttpEndpointBookmarkPayload>();
|
||||
|
||||
var otherWorkflowsWithSamePath = publishedWorkflowsTriggers
|
||||
.Where(x =>
|
||||
x.WorkflowDefinitionId != workflow.Identity.DefinitionId &&
|
||||
x.Payload is HttpEndpointBookmarkPayload payload &&
|
||||
payload.Path == triggerPayload.Path &&
|
||||
payload.Method == triggerPayload.Method)
|
||||
.ToList();
|
||||
|
||||
if (!otherWorkflowsWithSamePath.Any())
|
||||
continue;
|
||||
|
||||
var message = $"The {triggerPayload.Path} path and {triggerPayload.Method} method are already in use by another workflow!";
|
||||
validationErrors.Add(new(message, httpEndpointTrigger.ActivityId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using Elsa.Http.Bookmarks;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Helpers;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
using Elsa.Workflows.Runtime;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Entities;
|
||||
using Elsa.Workflows.Runtime.Filters;
|
||||
|
||||
namespace Elsa.Http.TriggerPayloadValidators;
|
||||
|
||||
public class HttpEndpointTriggerPayloadValidator(ITriggerStore triggerStore) : ITriggerPayloadValidator<HttpEndpointBookmarkPayload>
|
||||
{
|
||||
public async Task ValidateAsync(
|
||||
HttpEndpointBookmarkPayload payload,
|
||||
Workflow workflow,
|
||||
StoredTrigger trigger,
|
||||
ICollection<WorkflowValidationError> validationErrors,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var filter = new TriggerFilter
|
||||
{
|
||||
Name = ActivityTypeNameHelper.GenerateTypeName(typeof(HttpEndpoint))
|
||||
};
|
||||
var publishedWorkflowsTriggers = (await triggerStore.FindManyAsync(filter, cancellationToken)).ToList();
|
||||
|
||||
var otherWorkflowsWithSamePath = publishedWorkflowsTriggers
|
||||
.Where(x =>
|
||||
x.WorkflowDefinitionId != workflow.Identity.DefinitionId &&
|
||||
x.Payload is HttpEndpointBookmarkPayload anotherHttpEndpointPayload &&
|
||||
anotherHttpEndpointPayload.Path == payload.Path &&
|
||||
anotherHttpEndpointPayload.Method == payload.Method)
|
||||
.ToList();
|
||||
|
||||
if (otherWorkflowsWithSamePath.Count == 0)
|
||||
return;
|
||||
|
||||
validationErrors.Add(new($"The {payload.Path} path and {payload.Method} method are already in use by another workflow!",
|
||||
trigger.ActivityId));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
namespace Elsa.Scheduling.Bookmarks;
|
||||
|
||||
internal record CronTriggerPayload(string CronExpression);
|
||||
public record CronTriggerPayload(string CronExpression);
|
||||
|
|
@ -4,9 +4,11 @@ using Elsa.Extensions;
|
|||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Scheduling.Bookmarks;
|
||||
using Elsa.Scheduling.Handlers;
|
||||
using Elsa.Scheduling.HostedServices;
|
||||
using Elsa.Scheduling.Services;
|
||||
using Elsa.Scheduling.TriggerPayloadValidators;
|
||||
using Elsa.Workflows.Management.Features;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
|
|
@ -49,7 +51,10 @@ public class SchedulingFeature : FeatureBase
|
|||
.AddSingleton(CronParser)
|
||||
.AddScoped(WorkflowScheduler)
|
||||
.AddBackgroundTask<CreateSchedulesBackgroundTask>()
|
||||
.AddHandlersFrom<ScheduleWorkflows>();
|
||||
.AddHandlersFrom<ScheduleWorkflows>()
|
||||
|
||||
//Trigger payload validators.
|
||||
.AddTriggerPaylodValidator<CronTriggerPayloadValidator, CronTriggerPayload>();
|
||||
|
||||
Module.Configure<WorkflowManagementFeature>(management => management.AddActivitiesFrom<SchedulingFeature>());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
using Cronos;
|
||||
using Elsa.Scheduling.Bookmarks;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Entities;
|
||||
|
||||
namespace Elsa.Scheduling.TriggerPayloadValidators;
|
||||
|
||||
public class CronTriggerPayloadValidator(ICronParser cronParser) : ITriggerPayloadValidator<CronTriggerPayload>
|
||||
{
|
||||
public Task ValidateAsync(
|
||||
CronTriggerPayload payload,
|
||||
Workflow workflow,
|
||||
StoredTrigger trigger,
|
||||
ICollection<WorkflowValidationError> validationErrors,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
cronParser.GetNextOccurrence(payload.CronExpression);
|
||||
}
|
||||
catch (CronFormatException ex)
|
||||
{
|
||||
validationErrors.Add(new("Error when parsing cron expression: " + ex.Message,
|
||||
trigger.ActivityId));
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
using Elsa.Workflows.Runtime.Entities;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Validator that validate a given trigger payload.
|
||||
/// </summary>
|
||||
public interface ITriggerPayloadValidator<TPayload>
|
||||
{
|
||||
/// <summary>
|
||||
/// Validate a trigger payload. If trigger is not valid, error will be add in this list.
|
||||
/// </summary>
|
||||
Task ValidateAsync(
|
||||
TPayload payload,
|
||||
Workflow workflow,
|
||||
StoredTrigger trigger,
|
||||
ICollection<WorkflowValidationError> validationErrors,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using Elsa.Workflows.Runtime;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Providers;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
|
|
@ -16,4 +17,16 @@ public static class DependencyInjectionExtensions
|
|||
/// <typeparam name="T">The type of the workflow definition provider.</typeparam>
|
||||
/// <returns>The service collection.</returns>
|
||||
public static IServiceCollection AddWorkflowDefinitionProvider<T>(this IServiceCollection services) where T : class, IWorkflowsProvider => services.AddScoped<IWorkflowsProvider, T>();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a <see cref="ITriggerPayloadValidator{TPayload}"/> with the service container.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection</param>
|
||||
/// <typeparam name="TValidator">Validator of the validator</typeparam>
|
||||
/// <typeparam name="TPayload">Payload type</typeparam>
|
||||
public static IServiceCollection AddTriggerPaylodValidator<TValidator, TPayload>(this IServiceCollection services)
|
||||
where TValidator : class, ITriggerPayloadValidator<TPayload>
|
||||
{
|
||||
return services.AddScoped<ITriggerPayloadValidator<TPayload>, TValidator>();
|
||||
}
|
||||
}
|
||||
|
|
@ -345,6 +345,7 @@ public class WorkflowRuntimeFeature(IModule module) : FeatureBase(module)
|
|||
.AddNotificationHandler<RefreshActivityRegistry>()
|
||||
.AddNotificationHandler<SignalBookmarkQueueWorker>()
|
||||
.AddNotificationHandler<EvaluateParentLogPersistenceModes>()
|
||||
.AddNotificationHandler<ValidateWorkflowRequestHandler>()
|
||||
|
||||
// Workflow activation strategies.
|
||||
.AddScoped<IWorkflowActivationStrategy, SingletonStrategy>()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Mediator.Contracts;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
using Elsa.Workflows.Management.Notifications;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Entities;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="WorkflowDefinitionValidating"/> handler that validates a workflow path and return any errors.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class ValidateWorkflowRequestHandler(
|
||||
ITriggerIndexer triggerIndexer,
|
||||
IServiceProvider serviceProvider) : INotificationHandler<WorkflowDefinitionValidating>
|
||||
{
|
||||
public async Task HandleAsync(WorkflowDefinitionValidating notification, CancellationToken cancellationToken)
|
||||
{
|
||||
Workflow workflow = notification.Workflow;
|
||||
ICollection<WorkflowValidationError> validationErrors = notification.ValidationErrors;
|
||||
foreach (StoredTrigger trigger in await triggerIndexer.GetTriggersAsync(workflow, cancellationToken))
|
||||
{
|
||||
if (trigger.Payload is null)
|
||||
{
|
||||
validationErrors.Add(new($"Trigger should have a payload",
|
||||
trigger.ActivityId));
|
||||
}
|
||||
else
|
||||
{
|
||||
MethodInfo method = typeof(ValidateWorkflowRequestHandler)
|
||||
.GetMethod(nameof(ValidateInternalAsync), BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.MakeGenericMethod(trigger.Payload.GetType());
|
||||
|
||||
object? result = method.Invoke(this, [trigger.Payload, workflow, trigger, validationErrors, cancellationToken]);
|
||||
if (result is Task task)
|
||||
{
|
||||
await task;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ValidateInternalAsync<TPayload>(TPayload instance,
|
||||
Workflow workflow,
|
||||
StoredTrigger trigger,
|
||||
ICollection<WorkflowValidationError> validationErrors,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ITriggerPayloadValidator<TPayload>? payloadValidator = serviceProvider.GetService<ITriggerPayloadValidator<TPayload>>();
|
||||
|
||||
if (payloadValidator != null)
|
||||
await payloadValidator.ValidateAsync(instance, workflow, trigger, validationErrors, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
using Elsa.Testing.Shared;
|
||||
using Elsa.Workflows.Management;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Elsa.Workflows.IntegrationTests.Scenarios.ImportAndPublish;
|
||||
|
||||
public class ImportAndPublishCronTests
|
||||
{
|
||||
private readonly CapturingTextWriter _capturingTextWriter = new();
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public ImportAndPublishCronTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_services = new TestApplicationBuilder(testOutputHelper)
|
||||
.WithCapturingTextWriter(_capturingTextWriter)
|
||||
.Build();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Cron workflow imported from file should publish successfully.")]
|
||||
public async Task ImportAndPublish_ShouldSucceed_WithGoodCron()
|
||||
{
|
||||
// Populate registries.
|
||||
await _services.PopulateRegistriesAsync();
|
||||
|
||||
// Import workflow.
|
||||
var workflowDefinition = await _services.ImportWorkflowDefinitionAsync($"Scenarios/ImportAndPublish/Workflows/cron-every-hour.json");
|
||||
|
||||
// Publish.
|
||||
IWorkflowDefinitionPublisher workflowDefinitionPublisher = _services.GetRequiredService<IWorkflowDefinitionPublisher>();
|
||||
var result = await workflowDefinitionPublisher.PublishAsync(workflowDefinition);
|
||||
|
||||
// Assert.
|
||||
Assert.True(result.Succeeded);
|
||||
Assert.Empty(result.ValidationErrors);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Cron workflow imported from file should not publish successfully with bad cron expression.")]
|
||||
public async Task ImportAndPublish_ShouldFailed_WithBadCronExpression()
|
||||
{
|
||||
// Populate registries.
|
||||
await _services.PopulateRegistriesAsync();
|
||||
|
||||
// Import workflow.
|
||||
var workflowDefinition = await _services.ImportWorkflowDefinitionAsync($"Scenarios/ImportAndPublish/Workflows/bad-cron-expression.json");
|
||||
|
||||
// Publish.
|
||||
IWorkflowDefinitionPublisher workflowDefinitionPublisher = _services.GetRequiredService<IWorkflowDefinitionPublisher>();
|
||||
var result = await workflowDefinitionPublisher.PublishAsync(workflowDefinition);
|
||||
|
||||
// Assert.
|
||||
Assert.False(result.Succeeded);
|
||||
Assert.Single(result.ValidationErrors);
|
||||
Assert.Equal("Error when parsing cron expression: The given cron expression has an invalid format. Seconds: Value must be a number between 0 and 59 (all inclusive).", result.ValidationErrors.Single().Message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Testing.Shared;
|
||||
using Elsa.Workflows.Management;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Elsa.Workflows.IntegrationTests.Scenarios.ImportAndPublish;
|
||||
|
||||
public class ImportAndPublishHttpEndpointsTests
|
||||
{
|
||||
private readonly CapturingTextWriter _capturingTextWriter = new();
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public ImportAndPublishHttpEndpointsTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_services = new TestApplicationBuilder(testOutputHelper)
|
||||
.WithCapturingTextWriter(_capturingTextWriter)
|
||||
.ConfigureElsa(configure => configure.UseHttp())
|
||||
.Build();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Http endpoint workflow imported from file should publish successfully.")]
|
||||
public async Task ImportAndPublish_ShouldSucceed_WithGoodHttpEndpoint()
|
||||
{
|
||||
// Populate registries.
|
||||
await _services.PopulateRegistriesAsync();
|
||||
|
||||
// Import workflow.
|
||||
var workflowDefinition = await _services.ImportWorkflowDefinitionAsync($"Scenarios/ImportAndPublish/Workflows/http-workflow.json");
|
||||
|
||||
// Publish.
|
||||
IWorkflowDefinitionPublisher workflowDefinitionPublisher = _services.GetRequiredService<IWorkflowDefinitionPublisher>();
|
||||
var result = await workflowDefinitionPublisher.PublishAsync(workflowDefinition);
|
||||
|
||||
// Assert.
|
||||
Assert.True(result.Succeeded);
|
||||
Assert.Empty(result.ValidationErrors);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Http endpoint workflow imported from file should not publish successfully with same path and method.")]
|
||||
public async Task ImportAndPublish_ShouldFailed_WithTwoHttpEndpointSamePathMethod()
|
||||
{
|
||||
// Populate registries.
|
||||
await _services.PopulateRegistriesAsync();
|
||||
|
||||
// Import first workflow.
|
||||
var workflowDefinition = await _services.ImportWorkflowDefinitionAsync($"Scenarios/ImportAndPublish/Workflows/http-workflow.json");
|
||||
|
||||
// Publish first workflow.
|
||||
IWorkflowDefinitionPublisher workflowDefinitionPublisher = _services.GetRequiredService<IWorkflowDefinitionPublisher>();
|
||||
var result = await workflowDefinitionPublisher.PublishAsync(workflowDefinition);
|
||||
|
||||
// Assert first workflow.
|
||||
Assert.True(result.Succeeded);
|
||||
Assert.Empty(result.ValidationErrors);
|
||||
|
||||
// Import second workflow.
|
||||
workflowDefinition = await _services.ImportWorkflowDefinitionAsync($"Scenarios/ImportAndPublish/Workflows/http-workflow.json");
|
||||
|
||||
// Publish second workflow.
|
||||
result = await workflowDefinitionPublisher.PublishAsync(workflowDefinition);
|
||||
|
||||
// Assert second workflow.
|
||||
Assert.False(result.Succeeded);
|
||||
Assert.Single(result.ValidationErrors);
|
||||
Assert.Equal("The /test path and get method are already in use by another workflow!", result.ValidationErrors.Single().Message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
using Elsa.Testing.Shared;
|
||||
using Elsa.Workflows.Management;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Elsa.Workflows.IntegrationTests.Scenarios.ImportAndPublish;
|
||||
|
||||
public class ImportAndPublishTimerTests
|
||||
{
|
||||
private readonly CapturingTextWriter _capturingTextWriter = new();
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public ImportAndPublishTimerTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_services = new TestApplicationBuilder(testOutputHelper)
|
||||
.WithCapturingTextWriter(_capturingTextWriter)
|
||||
.Build();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Timer workflow imported from file should publish successfully.")]
|
||||
public async Task ImportAndPublish_ShouldSucceed_WithGoodTimerWithoutValidator()
|
||||
{
|
||||
// Populate registries.
|
||||
await _services.PopulateRegistriesAsync();
|
||||
|
||||
// Import workflow.
|
||||
var workflowDefinition = await _services.ImportWorkflowDefinitionAsync($"Scenarios/ImportAndPublish/Workflows/timer-workflow.json");
|
||||
|
||||
// Publish.
|
||||
IWorkflowDefinitionPublisher workflowDefinitionPublisher = _services.GetRequiredService<IWorkflowDefinitionPublisher>();
|
||||
var result = await workflowDefinitionPublisher.PublishAsync(workflowDefinition);
|
||||
|
||||
// Assert.
|
||||
Assert.True(result.Succeeded);
|
||||
Assert.Empty(result.ValidationErrors);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
{
|
||||
"$schema": "https://elsaworkflows.io/schemas/workflow-definition/v3.0.0/schema.json",
|
||||
"id": "868f0fa68d846638",
|
||||
"definitionId": "311cedd544cfe691",
|
||||
"name": "CronWorkflow",
|
||||
"createdAt": "2025-05-12T12:16:06.603274+00:00",
|
||||
"version": 1,
|
||||
"toolVersion": "3.3.0.0",
|
||||
"variables": [
|
||||
],
|
||||
"inputs": [
|
||||
],
|
||||
"outputs": [
|
||||
],
|
||||
"outcomes": [
|
||||
],
|
||||
"customProperties": {
|
||||
},
|
||||
"isReadonly": false,
|
||||
"isSystem": false,
|
||||
"isLatest": true,
|
||||
"isPublished": false,
|
||||
"options": {
|
||||
"autoUpdateConsumingWorkflows": false
|
||||
},
|
||||
"root": {
|
||||
"id": "57363d0e76c3e9d2",
|
||||
"nodeId": "Workflow1:57363d0e76c3e9d2",
|
||||
"name": "Flowchart1",
|
||||
"type": "Elsa.Flowchart",
|
||||
"version": 1,
|
||||
"customProperties": {
|
||||
"notFoundConnections": [
|
||||
],
|
||||
"canStartWorkflow": false,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"metadata": {
|
||||
},
|
||||
"activities": [
|
||||
{
|
||||
"text": {
|
||||
"typeName": "String",
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"value": "Hello every hour"
|
||||
}
|
||||
},
|
||||
"id": "1bf2412c9603f5d3",
|
||||
"nodeId": "Workflow1:57363d0e76c3e9d2:1bf2412c9603f5d3",
|
||||
"name": "WriteLine1",
|
||||
"type": "Elsa.WriteLine",
|
||||
"version": 1,
|
||||
"customProperties": {
|
||||
"canStartWorkflow": false,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"metadata": {
|
||||
"designer": {
|
||||
"position": {
|
||||
"x": -39.899993896484375,
|
||||
"y": -88.600006103515625
|
||||
},
|
||||
"size": {
|
||||
"width": 138.02500915527344,
|
||||
"height": 49.600002288818359
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cronExpression": {
|
||||
"typeName": "String",
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"value": "BadCronExpression"
|
||||
}
|
||||
},
|
||||
"id": "13658e4b3bfafaaf",
|
||||
"nodeId": "Workflow1:57363d0e76c3e9d2:13658e4b3bfafaaf",
|
||||
"name": "Cron1",
|
||||
"type": "Elsa.Cron",
|
||||
"version": 1,
|
||||
"customProperties": {
|
||||
"canStartWorkflow": true,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"metadata": {
|
||||
"designer": {
|
||||
"position": {
|
||||
"x": -206.89999389648438,
|
||||
"y": -88.600006103515625
|
||||
},
|
||||
"size": {
|
||||
"width": 100.59999847412109,
|
||||
"height": 49.600002288818359
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"source": {
|
||||
"activity": "13658e4b3bfafaaf",
|
||||
"port": "Done"
|
||||
},
|
||||
"target": {
|
||||
"activity": "1bf2412c9603f5d3",
|
||||
"port": "In"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
{
|
||||
"$schema": "https://elsaworkflows.io/schemas/workflow-definition/v3.0.0/schema.json",
|
||||
"id": "868f0fa68d846638",
|
||||
"definitionId": "311cedd544cfe691",
|
||||
"name": "CronWorkflow",
|
||||
"createdAt": "2025-05-12T12:16:06.603274+00:00",
|
||||
"version": 1,
|
||||
"toolVersion": "3.3.0.0",
|
||||
"variables": [
|
||||
],
|
||||
"inputs": [
|
||||
],
|
||||
"outputs": [
|
||||
],
|
||||
"outcomes": [
|
||||
],
|
||||
"customProperties": {
|
||||
},
|
||||
"isReadonly": false,
|
||||
"isSystem": false,
|
||||
"isLatest": true,
|
||||
"isPublished": false,
|
||||
"options": {
|
||||
"autoUpdateConsumingWorkflows": false
|
||||
},
|
||||
"root": {
|
||||
"id": "57363d0e76c3e9d2",
|
||||
"nodeId": "Workflow1:57363d0e76c3e9d2",
|
||||
"name": "Flowchart1",
|
||||
"type": "Elsa.Flowchart",
|
||||
"version": 1,
|
||||
"customProperties": {
|
||||
"notFoundConnections": [
|
||||
],
|
||||
"canStartWorkflow": false,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"metadata": {
|
||||
},
|
||||
"activities": [
|
||||
{
|
||||
"text": {
|
||||
"typeName": "String",
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"value": "Hello every hour"
|
||||
}
|
||||
},
|
||||
"id": "1bf2412c9603f5d3",
|
||||
"nodeId": "Workflow1:57363d0e76c3e9d2:1bf2412c9603f5d3",
|
||||
"name": "WriteLine1",
|
||||
"type": "Elsa.WriteLine",
|
||||
"version": 1,
|
||||
"customProperties": {
|
||||
"canStartWorkflow": false,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"metadata": {
|
||||
"designer": {
|
||||
"position": {
|
||||
"x": -39.899993896484375,
|
||||
"y": -88.600006103515625
|
||||
},
|
||||
"size": {
|
||||
"width": 138.02500915527344,
|
||||
"height": 49.600002288818359
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cronExpression": {
|
||||
"typeName": "String",
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"value": "0 0 * * * *"
|
||||
}
|
||||
},
|
||||
"id": "13658e4b3bfafaaf",
|
||||
"nodeId": "Workflow1:57363d0e76c3e9d2:13658e4b3bfafaaf",
|
||||
"name": "Cron1",
|
||||
"type": "Elsa.Cron",
|
||||
"version": 1,
|
||||
"customProperties": {
|
||||
"canStartWorkflow": true,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"metadata": {
|
||||
"designer": {
|
||||
"position": {
|
||||
"x": -206.89999389648438,
|
||||
"y": -88.600006103515625
|
||||
},
|
||||
"size": {
|
||||
"width": 100.59999847412109,
|
||||
"height": 49.600002288818359
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"source": {
|
||||
"activity": "13658e4b3bfafaaf",
|
||||
"port": "Done"
|
||||
},
|
||||
"target": {
|
||||
"activity": "1bf2412c9603f5d3",
|
||||
"port": "In"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
{
|
||||
"$schema": "https://elsaworkflows.io/schemas/workflow-definition/v3.0.0/schema.json",
|
||||
"name": "HttpWorkflow",
|
||||
"createdAt": "2025-05-12T12:28:56.845918+00:00",
|
||||
"version": 1,
|
||||
"toolVersion": "3.3.0.0",
|
||||
"variables": [
|
||||
],
|
||||
"inputs": [
|
||||
],
|
||||
"outputs": [
|
||||
],
|
||||
"outcomes": [
|
||||
],
|
||||
"customProperties": {
|
||||
},
|
||||
"isReadonly": false,
|
||||
"isSystem": false,
|
||||
"isLatest": true,
|
||||
"isPublished": false,
|
||||
"options": {
|
||||
"autoUpdateConsumingWorkflows": false
|
||||
},
|
||||
"root": {
|
||||
"id": "8163a86bbcff6777",
|
||||
"nodeId": "Workflow1:8163a86bbcff6777",
|
||||
"name": "Flowchart1",
|
||||
"type": "Elsa.Flowchart",
|
||||
"version": 1,
|
||||
"customProperties": {
|
||||
"notFoundConnections": [
|
||||
],
|
||||
"canStartWorkflow": false,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"metadata": {
|
||||
},
|
||||
"activities": [
|
||||
{
|
||||
"path": {
|
||||
"typeName": "String",
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"value": "test"
|
||||
}
|
||||
},
|
||||
"supportedMethods": {
|
||||
"typeName": "List<String>",
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"value": "[\"GET\"]"
|
||||
}
|
||||
},
|
||||
"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": "b036c71bbd31983d",
|
||||
"nodeId": "Workflow1:8163a86bbcff6777:b036c71bbd31983d",
|
||||
"name": "HttpEndpoint1",
|
||||
"type": "Elsa.HttpEndpoint",
|
||||
"version": 1,
|
||||
"customProperties": {
|
||||
"canStartWorkflow": true,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"metadata": {
|
||||
"designer": {
|
||||
"position": {
|
||||
"x": -180,
|
||||
"y": -80
|
||||
},
|
||||
"size": {
|
||||
"width": 173.5625,
|
||||
"height": 49.600002288818359
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"text": {
|
||||
"typeName": "String",
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"value": "Hello from http"
|
||||
}
|
||||
},
|
||||
"id": "b91befb34b3e44ea",
|
||||
"nodeId": "Workflow1:8163a86bbcff6777:b91befb34b3e44ea",
|
||||
"name": "WriteLine1",
|
||||
"type": "Elsa.WriteLine",
|
||||
"version": 1,
|
||||
"customProperties": {
|
||||
"canStartWorkflow": false,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"metadata": {
|
||||
"designer": {
|
||||
"position": {
|
||||
"x": 133.0999755859375,
|
||||
"y": -80
|
||||
},
|
||||
"size": {
|
||||
"width": 138.02500915527344,
|
||||
"height": 49.600002288818359
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"source": {
|
||||
"activity": "b036c71bbd31983d",
|
||||
"port": "Done"
|
||||
},
|
||||
"target": {
|
||||
"activity": "b91befb34b3e44ea",
|
||||
"port": "In"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
{
|
||||
"$schema": "https://elsaworkflows.io/schemas/workflow-definition/v3.0.0/schema.json",
|
||||
"id": "6e217c3e1fbce6e",
|
||||
"definitionId": "d1d828033d9c2dcd",
|
||||
"name": "TimerWorkflow",
|
||||
"createdAt": "2025-05-12T12:28:56.845918+00:00",
|
||||
"version": 1,
|
||||
"toolVersion": "3.3.0.0",
|
||||
"variables": [
|
||||
],
|
||||
"inputs": [
|
||||
],
|
||||
"outputs": [
|
||||
],
|
||||
"outcomes": [
|
||||
],
|
||||
"customProperties": {
|
||||
},
|
||||
"isReadonly": false,
|
||||
"isSystem": false,
|
||||
"isLatest": true,
|
||||
"isPublished": false,
|
||||
"options": {
|
||||
"autoUpdateConsumingWorkflows": false
|
||||
},
|
||||
"root": {
|
||||
"id": "8163a86bbcff6777",
|
||||
"nodeId": "Workflow1:8163a86bbcff6777",
|
||||
"name": "Flowchart1",
|
||||
"type": "Elsa.Flowchart",
|
||||
"version": 1,
|
||||
"customProperties": {
|
||||
"notFoundConnections": [
|
||||
],
|
||||
"canStartWorkflow": false,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"metadata": {
|
||||
},
|
||||
"activities": [
|
||||
{
|
||||
"text": {
|
||||
"typeName": "String",
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"value": "Hello from timer"
|
||||
}
|
||||
},
|
||||
"id": "b91befb34b3e44ea",
|
||||
"nodeId": "Workflow1:8163a86bbcff6777:b91befb34b3e44ea",
|
||||
"name": "WriteLine1",
|
||||
"type": "Elsa.WriteLine",
|
||||
"version": 1,
|
||||
"customProperties": {
|
||||
"canStartWorkflow": false,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"metadata": {
|
||||
"designer": {
|
||||
"position": {
|
||||
"x": 133.0999755859375,
|
||||
"y": -80
|
||||
},
|
||||
"size": {
|
||||
"width": 138.02500915527344,
|
||||
"height": 49.600002288818359
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"interval": {
|
||||
"typeName": "TimeSpan",
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"value": "00:01:00"
|
||||
}
|
||||
},
|
||||
"id": "89ab56281a99239",
|
||||
"nodeId": "Workflow1:8163a86bbcff6777:89ab56281a99239",
|
||||
"name": "Timer1",
|
||||
"type": "Elsa.Timer",
|
||||
"version": 1,
|
||||
"customProperties": {
|
||||
"canStartWorkflow": true,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"metadata": {
|
||||
"designer": {
|
||||
"position": {
|
||||
"x": -81.899993896484375,
|
||||
"y": -80
|
||||
},
|
||||
"size": {
|
||||
"width": 107.63750457763672,
|
||||
"height": 49.600002288818359
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"source": {
|
||||
"activity": "89ab56281a99239",
|
||||
"port": "Done"
|
||||
},
|
||||
"target": {
|
||||
"activity": "b91befb34b3e44ea",
|
||||
"port": "In"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue