From 689147b9ca9c31c2a3cc66c36378d212833def4b Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 4 Jan 2022 09:42:12 +0100 Subject: [PATCH] Add sources from experimental repo --- .gitignore | 72 + Elsa.ProtoActor.sln | 222 ++ Elsa.sln | 202 ++ Elsa.sln.DotSettings | 9 + LICENSE | 21 + NuGet.Config | 8 + README.md | 1 + docker-compose.yml | 48 + .../Elsa.Activities.Http.csproj | 18 + .../Elsa.Activities.Http.csproj.DotSettings | 4 + .../ApplicationBuilderExtensions.cs | 9 + .../Extensions/ServiceCollectionExtensions.cs | 13 + .../Elsa.Activities.Http/HttpTrigger.cs | 51 + .../Middleware/HttpTriggerMiddleware.cs | 57 + .../Models/HttpRequestModel.cs | 8 + .../Elsa.Activities.Http/WriteHttpResponse.cs | 26 + .../Elsa.Activities.Timers/AssemblyInfo.cs | 2 + .../Elsa.Activities.Timers.csproj | 13 + .../Elsa.Activities.Timers/TimerTrigger.cs | 10 + .../ApiResults/DispatchWorkflowResult.cs | 26 + src/api/Elsa.Api/ApiResults/EmptyResult.cs | 9 + .../ApiResults/ExecuteWorkflowResult.cs | 27 + .../ApiResults/ProcessStimulusResult.cs | 38 + src/api/Elsa.Api/Elsa.Api.csproj | 22 + .../Endpoints/ActivityDescriptors/List.cs | 28 + .../Endpoints/ActivityDescriptors/Resource.cs | 32 + src/api/Elsa.Api/Endpoints/Events/Resource.cs | 32 + src/api/Elsa.Api/Endpoints/Events/Trigger.cs | 17 + .../Endpoints/TriggerDescriptors/List.cs | 28 + .../Endpoints/TriggerDescriptors/Resource.cs | 32 + .../Endpoints/WorkflowInstances/Get.cs | 19 + .../Endpoints/WorkflowInstances/List.cs | 36 + .../Endpoints/WorkflowInstances/Resource.cs | 28 + .../Elsa.Api/Endpoints/Workflows/Dispatch.cs | 17 + .../Elsa.Api/Endpoints/Workflows/Execute.cs | 17 + src/api/Elsa.Api/Endpoints/Workflows/Get.cs | 26 + .../Elsa.Api/Endpoints/Workflows/GetMany.cs | 29 + src/api/Elsa.Api/Endpoints/Workflows/List.cs | 30 + src/api/Elsa.Api/Endpoints/Workflows/Post.cs | 70 + .../Elsa.Api/Endpoints/Workflows/Resource.cs | 36 + .../ApplicationBuilderExtensions.cs | 9 + .../EndpointRouteBuilderExtensions.cs | 18 + ...JsonSerializationErrorHandlerMiddleware.cs | 43 + src/api/Elsa.Api/ResourceBase.cs | 23 + .../Elsa.Core/Activities/Console/ReadLine.cs | 21 + .../Elsa.Core/Activities/Console/WriteLine.cs | 37 + .../Elsa.Core/Activities/ControlFlow/For.cs | 70 + .../Activities/ControlFlow/ForEach.cs | 44 + .../Elsa.Core/Activities/ControlFlow/Fork.cs | 67 + .../Elsa.Core/Activities/ControlFlow/If.cs | 26 + .../Elsa.Core/Activities/ControlFlow/Join.cs | 85 + .../Activities/ControlFlow/ParallelForEach.cs | 44 + .../Elsa.Core/Activities/ControlFlow/While.cs | 27 + .../Elsa.Core/Activities/Primitives/Event.cs | 40 + .../Activities/Primitives/UpdateVariable.cs | 16 + .../Activities/Workflows/Connection.cs | 5 + .../Activities/Workflows/Container.cs | 28 + .../Activities/Workflows/Flowchart.cs | 38 + .../Activities/Workflows/Sequence.cs | 43 + .../OutboundActivityNodeResolver.cs | 37 + src/core/Elsa.Core/AssemblyInfo.cs | 1 + .../Elsa.Core/Attributes/ActivityAttribute.cs | 12 + .../Elsa.Core/Attributes/InboundAttribute.cs | 6 + .../Elsa.Core/Attributes/InputAttribute.cs | 72 + .../Elsa.Core/Attributes/NodeAttribute.cs | 6 + .../Elsa.Core/Attributes/OutboundAttribute.cs | 8 + .../Elsa.Core/Attributes/OutputAttribute.cs | 15 + .../Elsa.Core/Attributes/StateAttribute.cs | 6 + .../Builders/WorkflowDefinitionBuilder.cs | 55 + src/core/Elsa.Core/Contracts/Delegates.cs | 6 + src/core/Elsa.Core/Contracts/IActivity.cs | 8 + .../Contracts/IActivityExecutionBuilder.cs | 10 + .../Contracts/IActivityExecutionMiddleware.cs | 8 + .../Contracts/IActivityExecutionPipeline.cs | 11 + .../Elsa.Core/Contracts/IActivityInvoker.cs | 14 + .../Contracts/IActivityNodeResolver.cs | 8 + .../Elsa.Core/Contracts/IActivityScheduler.cs | 11 + .../Contracts/IActivitySchedulerFactory.cs | 6 + .../Elsa.Core/Contracts/IActivityWalker.cs | 11 + src/core/Elsa.Core/Contracts/IContainer.cs | 8 + src/core/Elsa.Core/Contracts/IExpression.cs | 5 + .../Contracts/IExpressionEvaluator.cs | 15 + .../Elsa.Core/Contracts/IExpressionHandler.cs | 8 + .../Contracts/IExpressionHandlerRegistry.cs | 7 + src/core/Elsa.Core/Contracts/IHasher.cs | 9 + .../Elsa.Core/Contracts/IIdentityGenerator.cs | 6 + .../Contracts/IIdentityGraphService.cs | 10 + src/core/Elsa.Core/Contracts/INode.cs | 8 + src/core/Elsa.Core/Contracts/IReference.cs | 5 + .../Contracts/IScheduledNodeExecuted.cs | 8 + .../Contracts/IScopeCompletedHandler.cs | 8 + src/core/Elsa.Core/Contracts/ISystemClock.cs | 6 + src/core/Elsa.Core/Contracts/ITrigger.cs | 9 + .../Contracts/IWorkflowDefinitionBuilder.cs | 16 + .../Elsa.Core/Contracts/IWorkflowEngine.cs | 20 + .../Contracts/IWorkflowExecutionBuilder.cs | 11 + .../Contracts/IWorkflowExecutionMiddleware.cs | 8 + .../Contracts/IWorkflowExecutionPipeline.cs | 11 + .../Contracts/IWorkflowStateSerializer.cs | 10 + src/core/Elsa.Core/Elsa.Core.csproj | 22 + .../Elsa.Core/Elsa.Core.csproj.DotSettings | 3 + .../Exceptions/TypeConversionException.cs | 19 + .../Expressions/DelegateExpression.cs | 32 + .../Elsa.Core/Expressions/ElsaExpression.cs | 25 + .../Elsa.Core/Expressions/JsonExpression.cs | 33 + .../Expressions/LiteralExpression.cs | 28 + .../Expressions/VariableExpression.cs | 36 + .../Extensions/ActivityDriverExtensions.cs | 19 + .../Extensions/ActivityExtensions.cs | 59 + .../Elsa.Core/Extensions/NodeExtensions.cs | 34 + .../Extensions/ServiceCollectionExtensions.cs | 22 + src/core/Elsa.Core/Helpers/ObjectConverter.cs | 58 + src/core/Elsa.Core/Helpers/TypeNameHelper.cs | 44 + src/core/Elsa.Core/IsExternalInit.cs | 17 + src/core/Elsa.Core/Models/Activity.cs | 33 + .../Models/ActivityExecutionContext.cs | 107 + src/core/Elsa.Core/Models/ActivityNode.cs | 45 + src/core/Elsa.Core/Models/ActivityWorkItem.cs | 15 + src/core/Elsa.Core/Models/Argument.cs | 13 + src/core/Elsa.Core/Models/Bookmark.cs | 11 + src/core/Elsa.Core/Models/Composite.cs | 17 + .../Elsa.Core/Models/DelegateReference.cs | 34 + .../Models/DispatchWorkflowResult.cs | 3 + src/core/Elsa.Core/Models/DynamicActivity.cs | 14 + .../Models/ElsaExpressionReference.cs | 15 + .../Elsa.Core/Models/ExecuteWorkflowResult.cs | 5 + .../Models/ExpressionExecutionContext.cs | 35 + src/core/Elsa.Core/Models/Input.cs | 59 + src/core/Elsa.Core/Models/JsonObject.cs | 17 + src/core/Elsa.Core/Models/Literal.cs | 31 + src/core/Elsa.Core/Models/NodeIdentity.cs | 3 + src/core/Elsa.Core/Models/Output.cs | 15 + src/core/Elsa.Core/Models/Register.cs | 33 + src/core/Elsa.Core/Models/RegisterLocation.cs | 18 + .../Models/RegisterLocationReference.cs | 21 + src/core/Elsa.Core/Models/Trigger.cs | 28 + .../Models/TriggerIndexingContext.cs | 17 + src/core/Elsa.Core/Models/TriggerType.cs | 3 + src/core/Elsa.Core/Models/TypeDescriptor.cs | 3 + src/core/Elsa.Core/Models/TypeKind.cs | 11 + src/core/Elsa.Core/Models/Variable.cs | 30 + src/core/Elsa.Core/Models/Workflow.cs | 21 + .../Models/WorkflowExecutionContext.cs | 133 + .../Models/WorkflowExecutionLogEntry.cs | 3 + src/core/Elsa.Core/Models/WorkflowIdentity.cs | 6 + .../Models/WorkflowIndexingContext.cs | 25 + src/core/Elsa.Core/Models/WorkflowMetadata.cs | 5 + .../Elsa.Core/Models/WorkflowPublication.cs | 8 + .../Options/WorkflowEngineOptions.cs | 29 + .../ActivityExecutionMiddlewareExtensions.cs | 20 + .../ActivityExecutionPipeline.cs | 31 + .../ActivityExecutionPipelineBuilder.cs | 31 + .../ActivityMiddlewareDelegate.cs | 5 + .../Components/ActivityInvokerMiddleware.cs | 114 + .../Components/LoggingMiddleware.cs | 35 + .../Elsa.Core/Pipelines/MiddlewareHelpers.cs | 29 + .../Components/ActivitySchedulerMiddleware.cs | 33 + .../WorkflowExecutionMiddlewareExtensions.cs | 20 + .../WorkflowExecutionPipeline.cs | 28 + .../WorkflowExecutionPipelineBuilder.cs | 41 + .../WorkflowMiddlewareDelegate.cs | 5 + .../Elsa.Core/Services/ActivityInvoker.cs | 47 + .../Elsa.Core/Services/ActivityScheduler.cs | 14 + .../Services/ActivitySchedulerFactory.cs | 8 + src/core/Elsa.Core/Services/ActivityWalker.cs | 54 + .../Elsa.Core/Services/ExpressionEvaluator.cs | 27 + .../Services/ExpressionHandlerRegistry.cs | 31 + src/core/Elsa.Core/Services/Hasher.cs | 33 + .../Elsa.Core/Services/IdentityGenerator.cs | 8 + .../Services/IdentityGraphService.cs | 110 + src/core/Elsa.Core/Services/SystemClock.cs | 8 + src/core/Elsa.Core/Services/WorkflowEngine.cs | 120 + .../Services/WorkflowStateSerializer.cs | 162 + .../State/ActivityExecutionContextState.cs | 15 + .../State/CompletionCallbackState.cs | 3 + src/core/Elsa.Core/State/RegisterState.cs | 13 + src/core/Elsa.Core/State/WorkflowState.cs | 10 + .../Contracts/IActivityDescriber.cs | 8 + .../Contracts/IActivityFactory.cs | 9 + .../IActivityPropertyDefaultValueProvider.cs | 8 + .../Contracts/IActivityProvider.cs | 8 + .../Contracts/IActivityRegistry.cs | 15 + .../Contracts/IActivityRegistryPopulator.cs | 6 + .../Contracts/IExpressionSyntaxProvider.cs | 8 + .../Contracts/IExpressionSyntaxRegistry.cs | 12 + .../IExpressionSyntaxRegistryPopulator.cs | 6 + .../IPropertyDefaultValueResolver.cs | 8 + .../Contracts/IPropertyOptionsResolver.cs | 8 + .../Contracts/ITriggerDescriber.cs | 8 + .../Contracts/ITriggerFactory.cs | 9 + .../Contracts/ITriggerProvider.cs | 8 + .../Contracts/ITriggerRegistry.cs | 15 + .../Contracts/ITriggerRegistryPopulator.cs | 6 + .../Contracts/IWellKnownTypeRegistry.cs | 13 + .../Contracts/IWorkflowPublisher.cs | 17 + .../Contracts/WellKnownTypeRegistry.cs | 26 + .../Elsa.Management/Elsa.Management.csproj | 20 + .../Extensions/ServiceCollectionExtensions.cs | 41 + .../Extensions/TypeExtensions.cs | 8 + .../Models/ActivityDescriptor.cs | 15 + .../Elsa.Management/Models/ActivityTraits.cs | 8 + .../Models/ExpressionSyntaxDescriptor.cs | 26 + .../Elsa.Management/Models/InputDescriptor.cs | 46 + .../Elsa.Management/Models/InputUIHints.cs | 42 + .../Elsa.Management/Models/NodeDescriptor.cs | 11 + .../Models/OutputDescriptor.cs | 18 + src/core/Elsa.Management/Models/Port.cs | 7 + .../Models/PropertyDescriptor.cs | 11 + .../Elsa.Management/Models/SelectListItem.cs | 3 + .../Models/TriggerDescriptor.cs | 12 + .../Notifications/WorkflowPublished.cs | 5 + .../Notifications/WorkflowPublishing.cs | 6 + .../Notifications/WorkflowRetracted.cs | 6 + .../Notifications/WorkflowRetracting.cs | 6 + .../Elsa.Management/Options/ApiOptions.cs | 14 + .../DefaultExpressionSyntaxProvider.cs | 77 + .../Providers/TypedActivityProvider.cs | 35 + .../Providers/TypedTriggerProvider.cs | 35 + .../Converters/ActivityJsonConverter.cs | 52 + .../ActivityJsonConverterFactory.cs | 24 + .../Converters/ConnectionJsonConverter.cs | 47 + .../Converters/FlowchartJsonConverter.cs | 71 + .../Converters/InputJsonConverter.cs | 67 + .../Converters/InputJsonConverterFactory.cs | 19 + .../Converters/TriggerJsonConverter.cs | 52 + .../Converters/TriggerJsonConverterFactory.cs | 24 + .../Converters/TypeJsonConverter.cs | 53 + .../Extensions/JsonSerializerExtensions.cs | 12 + .../WorkflowSerializerOptionsProvider.cs | 27 + .../Services/ActivityDescriber.cs | 139 + .../Services/ActivityFactory.cs | 15 + .../Services/ActivityRegistry.cs | 58 + .../Services/ActivityRegistryPopulator.cs | 27 + .../Services/ExpressionSyntaxRegistry.cs | 20 + .../ExpressionSyntaxRegistryPopulator.cs | 24 + .../IActivityPropertyOptionsProvider.cs | 8 + .../Services/PropertyDefaultValueResolver.cs | 33 + .../Services/PropertyOptionsResolver.cs | 54 + .../Services/TriggerDescriber.cs | 83 + .../Services/TriggerFactory.cs | 15 + .../Services/TriggerRegistry.cs | 58 + .../Services/TriggerRegistryPopulator.cs | 27 + .../Services/WorkflowPublisher.cs | 169 + src/core/Elsa.Mediator/Contracts/ICommand.cs | 9 + .../Contracts/ICommandHandler.cs | 14 + .../Elsa.Mediator/Contracts/ICommandSender.cs | 7 + src/core/Elsa.Mediator/Contracts/IMediator.cs | 5 + .../Elsa.Mediator/Contracts/INotification.cs | 6 + .../Contracts/INotificationHandler.cs | 10 + .../Elsa.Mediator/Contracts/IPublisher.cs | 6 + src/core/Elsa.Mediator/Contracts/IRequest.cs | 9 + .../Contracts/IRequestHandler.cs | 10 + .../Elsa.Mediator/Contracts/IRequestSender.cs | 6 + src/core/Elsa.Mediator/Contracts/Unit.cs | 6 + src/core/Elsa.Mediator/Elsa.Mediator.csproj | 13 + .../Extensions/ServiceCollectionExtensions.cs | 84 + .../Middleware/Command/CommandContext.cs | 18 + .../Command/CommandMiddlewareDelegate.cs | 3 + .../Middleware/Command/CommandPipeline.cs | 24 + .../Command/CommandPipelineBuilder.cs | 41 + .../CommandPipelineBuilderExtensions.cs | 10 + .../CommandHandlerInvokerMiddleware.cs | 46 + .../Components/CommandLoggingMiddleware.cs | 15 + .../Command/Contracts/ICommandMiddleware.cs | 6 + .../Command/Contracts/ICommandPipeline.cs | 8 + .../Contracts/ICommandPipelineBuilder.cs | 9 + .../Command/MiddlewareExtensions.cs | 20 + .../Middleware/MiddlewareHelpers.cs | 29 + .../NotificationHandlerInvokerMiddleware.cs | 36 + .../NotificationLoggingMiddleware.cs | 15 + .../Contracts/INotificationMiddleware.cs | 6 + .../Contracts/INotificationPipeline.cs | 8 + .../Contracts/INotificationPipelineBuilder.cs | 9 + .../Notification/MiddlewareExtensions.cs | 20 + .../Notification/NotificationContext.cs | 15 + .../NotificationMiddlewareDelegate.cs | 3 + .../Notification/NotificationPipeline.cs | 24 + .../NotificationPipelineBuilder.cs | 41 + .../NotificationPipelineBuilderExtensions.cs | 10 + .../RequestHandlerInvokerMiddleware.cs | 46 + .../Components/RequestLoggingMiddleware.cs | 15 + .../Request/Contracts/IRequestMiddleware.cs | 6 + .../Request/Contracts/IRequestPipeline.cs | 8 + .../Contracts/IRequestPipelineBuilder.cs | 9 + .../Request/MiddlewareExtensions.cs | 20 + .../Middleware/Request/RequestContext.cs | 18 + .../Request/RequestMiddlewareDelegate.cs | 3 + .../Middleware/Request/RequestPipeline.cs | 24 + .../Request/RequestPipelineBuilder.cs | 41 + .../RequestPipelineBuilderExtensions.cs | 10 + .../Elsa.Mediator/Services/DefaultMediator.cs | 57 + .../Elsa.Serialization.csproj | 13 + src/core/Elsa/Elsa.csproj | 23 + .../Extensions/ServiceCollectionExtensions.cs | 78 + .../Extensions/ServiceProviderExtensions.cs | 22 + .../elsa-workflows-designer/.editorconfig | 15 + .../elsa-workflows-designer/.gitignore | 26 + .../elsa-workflows-designer/.prettierrc.json | 13 + src/designer/elsa-workflows-designer/LICENSE | 21 + .../elsa-workflows-designer/package-lock.json | 1181 +++++++ .../elsa-workflows-designer/package.json | 52 + .../elsa-workflows-designer/readme.md | 75 + .../src/components.d.ts | 527 +++ .../container-activity-component.tsx | 9 + .../activities/flowchart/activity-walker.ts | 80 + .../activities/flowchart/flowchart.scss | 25 + .../activities/flowchart/flowchart.tsx | 324 ++ .../activities/flowchart/graph-factory.ts | 215 ++ .../components/activities/flowchart/models.ts | 13 + .../activities/flowchart/node-factory.ts | 53 + .../components/activities/flowchart/ports.tsx | 68 + .../components/activities/flowchart/shapes.ts | 138 + .../activity-properties-editor.tsx | 160 + .../components/designer/canvas/canvas.scss | 25 + .../src/components/designer/canvas/canvas.tsx | 48 + .../designer/form-panel/form-panel.tsx | 114 + .../src/components/designer/panel/models.ts | 10 + .../src/components/designer/panel/panel.scss | 19 + .../src/components/designer/panel/panel.tsx | 55 + .../slide-over-panel/slide-over-panel.tsx | 194 ++ .../src/components/designer/state.tsx | 18 + .../designer/toolbox/toolbox-activities.tsx | 139 + .../designer/toolbox/toolbox-triggers.tsx | 139 + .../components/designer/toolbox/toolbox.tsx | 54 + .../trigger-container/trigger-container.tsx | 158 + .../trigger-properties-editor.tsx | 164 + .../workflow-editor/workflow-editor.scss | 72 + .../workflow-editor/workflow-editor.tsx | 274 ++ .../workflow-properties-editor.tsx | 93 + .../components/icons/activities/flowchart.ts | 8 + .../icons/activities/http-trigger.ts | 7 + .../src/components/icons/activities/if.ts | 4 + .../src/components/icons/activities/index.tsx | 5 + .../components/icons/activities/read-line.ts | 8 + .../components/icons/activities/write-line.ts | 6 + .../components/icons/tooling/bulk-actions.tsx | 6 + .../src/components/icons/tooling/delete.tsx | 11 + .../src/components/icons/tooling/edit.tsx | 8 + .../src/components/icons/tooling/index.ts | 9 + .../src/components/icons/tooling/order-by.tsx | 6 + .../icons/tooling/page-size-icon.tsx | 12 + .../src/components/icons/tooling/publish.tsx | 6 + .../components/icons/tooling/un-publish.tsx | 6 + .../icons/tooling/workflow-status-icon.tsx | 7 + .../src/components/icons/tooling/workflow.tsx | 10 + .../src/components/inputs/check-list.tsx | 100 + .../src/components/inputs/dropdown.tsx | 50 + .../src/components/inputs/single-line.tsx | 27 + .../workflow-browser/workflow-browser.tsx | 152 + .../workflow-instance-browser/filter.tsx | 124 + .../workflow-instance-browser/search.tsx | 49 + .../modals/workflow-instance-browser/utils.ts | 24 + .../workflow-instance-browser.tsx | 285 ++ .../shared/context-menu/context-menu.tsx | 77 + .../components/shared/context-menu/models.tsx | 6 + .../dropdown-button/dropdown-button.tsx | 97 + .../shared/dropdown-button/models.tsx | 13 + .../components/shared/forms/form-entry.tsx | 23 + .../src/components/shared/forms/hint.tsx | 7 + .../src/components/shared/forms/info-list.tsx | 31 + .../shared/modal-dialog/modal-dialog.tsx | 128 + .../src/components/shared/pager/controls.tsx | 72 + .../src/components/shared/pager/pager.tsx | 62 + .../src/components/shell/studio/studio.tsx | 108 + .../workflow-publish-button.tsx | 181 ++ .../workflow-toolbar-menu.tsx | 78 + .../workflow-toolbar/workflow-toolbar.tsx | 36 + .../elsa-workflows-designer/src/data/store.ts | 8 + .../activity/default-activity-driver.tsx | 52 + .../drivers/input/default-input-driver.tsx | 40 + .../drivers/trigger/default-trigger-driver.ts | 36 + .../src/global/tailwind.css | 118 + .../src/index-automatic-interaction.html | 18 + .../src/index-manual-interaction.html | 35 + .../elsa-workflows-designer/src/index.html | 22 + .../elsa-workflows-designer/src/index.ts | 5 + .../elsa-workflows-designer/src/models/api.ts | 123 + .../src/models/core.ts | 94 + .../src/models/event-args.ts | 14 + .../src/models/events.ts | 6 + .../src/models/expressions.ts | 17 + .../src/models/index.ts | 7 + .../src/models/shared.ts | 2 + .../elsa-workflows-designer/src/models/ui.ts | 94 + .../src/services/activity-driver-registry.ts | 23 + .../src/services/activity-driver.ts | 16 + .../src/services/activity-icon-registry.ts | 31 + .../src/services/custom-event-bus.d.ts | 51 + .../src/services/custom-event-bus.js | 193 ++ .../src/services/elsa-api-client-provider.ts | 233 ++ .../src/services/event-bus.ts | 11 + .../src/services/index.ts | 5 + .../src/services/input-control-registry.tsx | 29 + .../src/services/input-driver-registry.ts | 18 + .../src/services/node-input-driver.ts | 16 + .../src/services/server-settings.ts | 13 + .../src/services/trigger-driver-registry.ts | 22 + .../src/services/trigger-driver.ts | 16 + .../src/utils/index.ts | 2 + .../src/utils/select-list-items.ts | 31 + .../src/utils/utils.ts | 54 + .../elsa-workflows-designer/stencil.config.ts | 37 + .../tailwind.config.js | 27 + .../elsa-workflows-designer/tsconfig.json | 27 + src/dsl/Elsa.Dsl/Abstractions/ElsaLexer.cs | 386 +++ .../Elsa.Dsl/Abstractions/ElsaLexer.interp | 166 + .../Elsa.Dsl/Abstractions/ElsaLexer.tokens | 91 + src/dsl/Elsa.Dsl/Abstractions/ElsaParser.cs | 2862 +++++++++++++++++ .../Elsa.Dsl/Abstractions/ElsaParser.interp | 131 + .../Elsa.Dsl/Abstractions/ElsaParser.tokens | 91 + .../Abstractions/ElsaParserBaseListener.cs | 683 ++++ .../Abstractions/ElsaParserBaseVisitor.cs | 545 ++++ .../Abstractions/ElsaParserListener.cs | 569 ++++ .../Abstractions/ElsaParserVisitor.cs | 350 ++ .../Abstractions/IFunctionActivityRegistry.cs | 10 + src/dsl/Elsa.Dsl/Abstractions/ITypeSystem.cs | 9 + src/dsl/Elsa.Dsl/AssemblyInfo.cs | 2 + src/dsl/Elsa.Dsl/Contracts/IDslEngine.cs | 8 + src/dsl/Elsa.Dsl/Dsl/ElsaLexer.g4 | 55 + src/dsl/Elsa.Dsl/Dsl/ElsaParser.g4 | 125 + src/dsl/Elsa.Dsl/Dsl/Samples/For.elsa | 9 + src/dsl/Elsa.Dsl/Dsl/Samples/Full.elsa | 40 + src/dsl/Elsa.Dsl/Dsl/Samples/Greeting.elsa | 7 + .../Elsa.Dsl/Dsl/Samples/HelloGoodbye.elsa | 4 + src/dsl/Elsa.Dsl/Dsl/Samples/HelloWorld.elsa | 1 + src/dsl/Elsa.Dsl/Dsl/Samples/HttpTrigger.elsa | 14 + src/dsl/Elsa.Dsl/Dsl/Samples/If.elsa | 14 + src/dsl/Elsa.Dsl/Dsl/Samples/Scratchpad.elsa | 10 + src/dsl/Elsa.Dsl/Dsl/gen/ElsaLexer.interp | 163 + src/dsl/Elsa.Dsl/Dsl/gen/ElsaLexer.java | 235 ++ src/dsl/Elsa.Dsl/Dsl/gen/ElsaLexer.tokens | 89 + src/dsl/Elsa.Dsl/Elsa.Dsl.csproj | 17 + src/dsl/Elsa.Dsl/Elsa.Dsl.csproj.DotSettings | 2 + src/dsl/Elsa.Dsl/Extensions/ListConverter.cs | 21 + .../Extensions/ServiceCollectionExtensions.cs | 16 + .../Extensions/TypeSystemExtensions.cs | 43 + .../VisitArgs.cs | 24 + .../VisitBlockStatement.cs | 40 + .../VisitBrackets.cs | 29 + .../VisitExternalExpression.cs | 24 + .../VisitFile.cs | 37 + .../VisitFunction.cs | 35 + .../VisitIfStatement.cs | 36 + .../VisitLocalVariable.cs | 27 + .../VisitNewObject.cs | 54 + .../VisitObject.cs | 68 + .../VisitProperty.cs | 57 + .../VisitStringValue.cs | 20 + .../VisitTrigger.cs | 18 + .../VisitWorkflowVariable.cs | 51 + .../WorkflowDefinitionBuilderInterpreter.cs | 37 + src/dsl/Elsa.Dsl/Models/DefinedVariable.cs | 10 + .../WorkflowDefinitionInterpreterSettings.cs | 5 + src/dsl/Elsa.Dsl/Services/DslEngine.cs | 37 + .../Services/FunctionActivityRegistry.cs | 130 + src/dsl/Elsa.Dsl/Services/TypeSystem.cs | 23 + .../Commands/DeleteWorkflowBookmarks.cs | 14 + .../Commands/DeleteWorkflowDefinition.cs | 8 + .../Commands/DeleteWorkflowInstances.cs | 17 + .../Commands/ReplaceWorkflowTriggers.cs | 7 + .../Commands/SaveWorkflow.cs | 9 + .../Commands/SaveWorkflowBookmarks.cs | 15 + .../Commands/SaveWorkflowInstance.cs | 9 + .../Contracts/IWorkflowBookmarkStore.cs | 16 + .../Contracts/IWorkflowInstanceStore.cs | 13 + .../Contracts/IWorkflowTriggerStore.cs | 15 + .../Elsa.Persistence.Abstractions.csproj | 19 + .../Entities/Entity.cs | 6 + .../Entities/OrderBy.cs | 8 + .../Entities/OrderDirection.cs | 7 + .../Entities/WorkflowBookmark.cs | 16 + .../Entities/WorkflowDefinition.cs | 23 + .../Entities/WorkflowInstance.cs | 20 + .../Entities/WorkflowStatus.cs | 12 + .../Entities/WorkflowTrigger.cs | 8 + .../Extensions/ServiceCollectionExtensions.cs | 12 + .../WorkflowDefinitionExtensions.cs | 70 + .../Extensions/WorkflowExtensions.cs | 39 + .../Mappers/WorkflowDefinitionMapper.cs | 46 + .../PersistWorkflowInstanceMiddleware.cs | 118 + .../Models/Cursor.cs | 5 + .../Models/CursorDirection.cs | 7 + .../Models/PagedList.cs | 5 + .../Models/PagerParameters.cs | 3 + .../Models/VersionOptions.cs | 93 + .../Models/WorkflowInstanceSummary.cs | 34 + .../Models/WorkflowSummary.cs | 17 + .../FindLatestAndPublishedWorkflows.cs | 7 + .../Requests/FindManyWorkflowDefinitions.cs | 7 + .../Requests/FindWorkflowBookmarks.cs | 23 + .../Requests/FindWorkflowDefinition.cs | 7 + .../Requests/FindWorkflowInstance.cs | 6 + .../Requests/FindWorkflowTriggers.cs | 17 + .../Requests/ListWorkflowInstanceSummaries.cs | 17 + .../Requests/ListWorkflowSummaries.cs | 6 + .../Requests/ListWorkflows.cs | 11 + .../VersionOptionsTypeConverter.cs | 13 + .../DbContextOptionsBuilderExtensions.cs | 11 + ...sistence.EntityFrameworkCore.Sqlite.csproj | 22 + .../20211228122857_Initial.Designer.cs | 245 ++ .../Migrations/20211228122857_Initial.cs | 225 ++ .../Migrations/ElsaDbContextModelSnapshot.cs | 243 ++ .../Readme.md | 23 + .../SqliteElsaContextFactory.cs | 20 + .../WorkflowBookmarkConfiguration.cs | 22 + .../WorkflowDefinitionConfiguration.cs | 22 + .../WorkflowInstanceConfiguration.cs | 25 + .../WorkflowTriggerConfiguration.cs | 16 + .../Contracts/IEntitySerializer.cs | 10 + .../Contracts/IStore.cs | 17 + ...lsa.Persistence.EntityFrameworkCore.csproj | 21 + .../ElsaDbContext.cs | 40 + .../Extensions/DatabaseFacadeExtensions.cs | 10 + .../Extensions/QueryableBulkExtensions.cs | 25 + .../Extensions/ServiceCollectionExtensions.cs | 53 + .../DeleteWorkflowBookmarksHandler.cs | 13 + .../DeleteWorkflowDefinitionHandler.cs | 13 + .../DeleteWorkflowInstancesHandler.cs | 23 + .../ReplaceWorkflowTriggersHandler.cs | 20 + .../Commands/SaveWorkflowBookmarksHandler.cs | 19 + .../Commands/SaveWorkflowDefinitionHandler.cs | 27 + .../Commands/SaveWorkflowInstanceHandler.cs | 19 + ...tAndPublishedWorkflowDefinitionsHandler.cs | 26 + .../FindManyWorkflowDefinitionsHandler.cs | 28 + .../Requests/FindWorkflowBookmarksHandler.cs | 23 + .../Handlers/Requests/FindWorkflowHandler.cs | 31 + .../Requests/FindWorkflowInstanceHandler.cs | 14 + .../Requests/FindWorkflowTriggersHandler.cs | 15 + .../ListWorkflowInstanceSummariesHandler.cs | 56 + .../Requests/ListWorkflowSummariesHandler.cs | 36 + .../Handlers/Requests/ListWorkflowsHandler.cs | 34 + .../WorkflowDefinitionSerializer.cs | 51 + .../WorkflowInstanceSerializer.cs | 48 + .../HostedServices/RunMigrations.cs | 23 + .../Services/EFCoreStore.cs | 123 + .../Elsa.Persistence.InMemory.csproj | 12 + .../Extensions/ServiceCollectionExtensions.cs | 25 + .../DeleteWorkflowBookmarksHandler.cs | 25 + .../DeleteWorkflowDefinitionHandler.cs | 25 + .../DeleteWorkflowInstancesHandler.cs | 32 + .../ReplaceWorkflowTriggersHandler.cs | 26 + .../Commands/SaveWorkflowBookmarksHandler.cs | 25 + .../Handlers/Commands/SaveWorkflowHandler.cs | 29 + .../Commands/SaveWorkflowInstanceHandler.cs | 25 + ...tAndPublishedWorkflowDefinitionsHandler.cs | 31 + .../FindManyWorkflowDefinitionsHandler.cs | 34 + .../Requests/FindWorkflowBookmarksHandler.cs | 34 + .../Requests/FindWorkflowDefinitionHandler.cs | 30 + .../Requests/FindWorkflowInstanceHandler.cs | 21 + .../Requests/FindWorkflowTriggersHandler.cs | 22 + .../ListWorkflowInstanceSummariesHandler.cs | 64 + .../Requests/ListWorkflowSummariesHandler.cs | 40 + .../Handlers/Requests/ListWorkflowsHandler.cs | 37 + .../Services/InMemoryStore.cs | 50 + .../Actors/WorkflowDefinitionActor.cs | 96 + .../Actors/WorkflowInstanceActor.cs | 60 + .../Actors/WorkflowOperatorActor.cs | 114 + .../Elsa.Runtime.ProtoActor.csproj | 48 + .../Extensions/ServiceCollectionExtensions.cs | 82 + .../Elsa.Runtime.ProtoActor/GrainKinds.cs | 7 + .../HostedServices/WorkflowServerHost.cs | 22 + .../Services/ProtoActorWorkflowInvoker.cs | 128 + .../Elsa.Runtime.ProtoActor/messages.proto | 54 + .../Abstractions/StimulusHandler.cs | 17 + .../WorkflowInstructionInterpreter.cs | 26 + .../DispatchWorkflowInstructionResult.cs | 3 + .../ExecuteWorkflowInstructionResult.cs | 5 + .../Elsa.Runtime/Contracts/IStimulus.cs | 5 + .../Contracts/IStimulusHandler.cs | 7 + .../Contracts/IStimulusInterpreter.cs | 6 + .../Elsa.Runtime/Contracts/ITriggerIndexer.cs | 16 + .../Elsa.Runtime/Contracts/IWorkflow.cs | 8 + .../Contracts/IWorkflowInstruction.cs | 5 + .../Contracts/IWorkflowInstructionExecutor.cs | 9 + .../IWorkflowInstructionInterpreter.cs | 8 + .../IWorkflowInstructionScheduler.cs | 7 + .../Contracts/IWorkflowInvoker.cs | 12 + .../Contracts/IWorkflowProvider.cs | 13 + .../Contracts/IWorkflowRegistry.cs | 10 + .../Elsa.Runtime/Contracts/IWorkflowServer.cs | 15 + src/runtime/Elsa.Runtime/Elsa.Runtime.csproj | 22 + .../Extensions/EnumerableExtensions.cs | 15 + .../Extensions/ServiceCollectionExtensions.cs | 64 + .../Handlers/IndexWorkflowTriggersHandler.cs | 13 + .../IndexWorkflowTriggersHostedService.cs | 27 + .../RegisterDescriptorsHostedService.cs | 26 + ...xpressionSyntaxDescriptorsHostedService.cs | 12 + .../Instructions/ResumeWorkflowInstruction.cs | 6 + .../TriggerWorkflowInstruction.cs | 6 + .../ResumeWorkflowInstructionInterpreter.cs | 62 + .../TriggerWorkflowInstructionInterpreter.cs | 71 + src/runtime/Elsa.Runtime/IsExternalInit.cs | 16 + .../DispatchWorkflowDefinitionRequest.cs | 3 + .../Models/DispatchWorkflowInstanceRequest.cs | 5 + .../ExecuteWorkflowDefinitionRequest.cs | 3 + .../Models/ExecuteWorkflowInstanceRequest.cs | 5 + src/runtime/Elsa.Runtime/Models/Stimuli.cs | 8 + .../Options/WorkflowRuntimeOptions.cs | 11 + .../Services/StimulusInterpreter.cs | 20 + .../Elsa.Runtime/Services/TriggerIndexer.cs | 107 + .../Services/WorkflowEngineBuilder.cs | 15 + .../Services/WorkflowInstructionExecutor.cs | 41 + .../Elsa.Runtime/Services/WorkflowRegistry.cs | 37 + .../Elsa.Runtime/Services/WorkflowServer.cs | 75 + .../ResumeWorkflowsStimulusHandler.cs | 19 + .../TriggerWorkflowsStimulusHandler.cs | 19 + .../Elsa.Runtime/Stimuli/StandardStimulus.cs | 5 + .../ConfigurationWorkflowProvider.cs | 49 + .../DatabaseWorkflowProvider.cs | 41 + .../Activities/MyGreeterComposite.cs | 35 + .../Activities/WriteLines.cs | 42 + .../Elsa.Samples.Web1.csproj | 16 + .../aspnet/Elsa.Samples.Web1/Program.cs | 94 + .../Properties/launchSettings.json | 19 + .../Workflows/CompositeActivitiesWorkflow.cs | 29 + .../Workflows/ForkedHttpWorkflow.cs | 61 + .../Workflows/HelloWorldWorkflow.cs | 13 + .../Workflows/HttpWorkflow.cs | 56 + .../aspnet/Elsa.Samples.Web1/appsettings.json | 11 + .../Elsa.Samples.Web2.csproj | 17 + .../aspnet/Elsa.Samples.Web2/Program.cs | 46 + .../Properties/launchSettings.json | 21 + .../Workflows/HelloWorldWorkflow.cs | 13 + .../aspnet/Elsa.Samples.Web2/appsettings.json | 9 + .../Elsa.Samples.Console1.csproj | 21 + .../console/Elsa.Samples.Console1/Program.cs | 98 + .../BlockingParallelForEachWorkflow.cs | 41 + .../Workflows/BlockingWorkflow.cs | 18 + .../Workflows/ConditionalWorkflow.cs | 33 + .../Workflows/CustomizedActivityWorkflow.cs | 16 + .../Workflows/FlowchartWorkflow.cs | 38 + .../Workflows/ForEachWorkflow.cs | 32 + .../Workflows/ForWorkflow.cs | 36 + .../Workflows/ForkedWorkflow.cs | 31 + .../Workflows/GreetingWorkflow.cs | 25 + .../Workflows/HelloGoodbyeWorkflow.cs | 14 + .../Workflows/HelloWorldWorkflow.cs | 9 + .../Workflows/ParallelForEachWorkflow.cs | 32 + .../Workflows/VariablesWorkflow.cs | 29 + .../Workflows/WhileWorkflow.cs | 52 + .../Elsa.Samples.Console2.csproj | 28 + .../console/Elsa.Samples.Console2/Program.cs | 57 + .../Elsa.Samples.Console2/Sample1.elsa | 3 + .../Elsa.Scripting.JavaScript.csproj | 16 + .../JavaScriptExpression.cs | 27 + .../JavaScriptExpressionReference.cs | 15 + .../ServiceCollectionExtensions.cs | 12 + 647 files changed, 29101 insertions(+) create mode 100644 .gitignore create mode 100644 Elsa.ProtoActor.sln create mode 100644 Elsa.sln create mode 100644 Elsa.sln.DotSettings create mode 100644 LICENSE create mode 100644 NuGet.Config create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 src/activities/Elsa.Activities.Http/Elsa.Activities.Http.csproj create mode 100644 src/activities/Elsa.Activities.Http/Elsa.Activities.Http.csproj.DotSettings create mode 100644 src/activities/Elsa.Activities.Http/Extensions/ApplicationBuilderExtensions.cs create mode 100644 src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/activities/Elsa.Activities.Http/HttpTrigger.cs create mode 100644 src/activities/Elsa.Activities.Http/Middleware/HttpTriggerMiddleware.cs create mode 100644 src/activities/Elsa.Activities.Http/Models/HttpRequestModel.cs create mode 100644 src/activities/Elsa.Activities.Http/WriteHttpResponse.cs create mode 100644 src/activities/Elsa.Activities.Timers/AssemblyInfo.cs create mode 100644 src/activities/Elsa.Activities.Timers/Elsa.Activities.Timers.csproj create mode 100644 src/activities/Elsa.Activities.Timers/TimerTrigger.cs create mode 100644 src/api/Elsa.Api/ApiResults/DispatchWorkflowResult.cs create mode 100644 src/api/Elsa.Api/ApiResults/EmptyResult.cs create mode 100644 src/api/Elsa.Api/ApiResults/ExecuteWorkflowResult.cs create mode 100644 src/api/Elsa.Api/ApiResults/ProcessStimulusResult.cs create mode 100644 src/api/Elsa.Api/Elsa.Api.csproj create mode 100644 src/api/Elsa.Api/Endpoints/ActivityDescriptors/List.cs create mode 100644 src/api/Elsa.Api/Endpoints/ActivityDescriptors/Resource.cs create mode 100644 src/api/Elsa.Api/Endpoints/Events/Resource.cs create mode 100644 src/api/Elsa.Api/Endpoints/Events/Trigger.cs create mode 100644 src/api/Elsa.Api/Endpoints/TriggerDescriptors/List.cs create mode 100644 src/api/Elsa.Api/Endpoints/TriggerDescriptors/Resource.cs create mode 100644 src/api/Elsa.Api/Endpoints/WorkflowInstances/Get.cs create mode 100644 src/api/Elsa.Api/Endpoints/WorkflowInstances/List.cs create mode 100644 src/api/Elsa.Api/Endpoints/WorkflowInstances/Resource.cs create mode 100644 src/api/Elsa.Api/Endpoints/Workflows/Dispatch.cs create mode 100644 src/api/Elsa.Api/Endpoints/Workflows/Execute.cs create mode 100644 src/api/Elsa.Api/Endpoints/Workflows/Get.cs create mode 100644 src/api/Elsa.Api/Endpoints/Workflows/GetMany.cs create mode 100644 src/api/Elsa.Api/Endpoints/Workflows/List.cs create mode 100644 src/api/Elsa.Api/Endpoints/Workflows/Post.cs create mode 100644 src/api/Elsa.Api/Endpoints/Workflows/Resource.cs create mode 100644 src/api/Elsa.Api/Extensions/ApplicationBuilderExtensions.cs create mode 100644 src/api/Elsa.Api/Extensions/EndpointRouteBuilderExtensions.cs create mode 100644 src/api/Elsa.Api/Middleware/JsonSerializationErrorHandlerMiddleware.cs create mode 100644 src/api/Elsa.Api/ResourceBase.cs create mode 100644 src/core/Elsa.Core/Activities/Console/ReadLine.cs create mode 100644 src/core/Elsa.Core/Activities/Console/WriteLine.cs create mode 100644 src/core/Elsa.Core/Activities/ControlFlow/For.cs create mode 100644 src/core/Elsa.Core/Activities/ControlFlow/ForEach.cs create mode 100644 src/core/Elsa.Core/Activities/ControlFlow/Fork.cs create mode 100644 src/core/Elsa.Core/Activities/ControlFlow/If.cs create mode 100644 src/core/Elsa.Core/Activities/ControlFlow/Join.cs create mode 100644 src/core/Elsa.Core/Activities/ControlFlow/ParallelForEach.cs create mode 100644 src/core/Elsa.Core/Activities/ControlFlow/While.cs create mode 100644 src/core/Elsa.Core/Activities/Primitives/Event.cs create mode 100644 src/core/Elsa.Core/Activities/Primitives/UpdateVariable.cs create mode 100644 src/core/Elsa.Core/Activities/Workflows/Connection.cs create mode 100644 src/core/Elsa.Core/Activities/Workflows/Container.cs create mode 100644 src/core/Elsa.Core/Activities/Workflows/Flowchart.cs create mode 100644 src/core/Elsa.Core/Activities/Workflows/Sequence.cs create mode 100644 src/core/Elsa.Core/ActivityNodeResolvers/OutboundActivityNodeResolver.cs create mode 100644 src/core/Elsa.Core/AssemblyInfo.cs create mode 100644 src/core/Elsa.Core/Attributes/ActivityAttribute.cs create mode 100644 src/core/Elsa.Core/Attributes/InboundAttribute.cs create mode 100644 src/core/Elsa.Core/Attributes/InputAttribute.cs create mode 100644 src/core/Elsa.Core/Attributes/NodeAttribute.cs create mode 100644 src/core/Elsa.Core/Attributes/OutboundAttribute.cs create mode 100644 src/core/Elsa.Core/Attributes/OutputAttribute.cs create mode 100644 src/core/Elsa.Core/Attributes/StateAttribute.cs create mode 100644 src/core/Elsa.Core/Builders/WorkflowDefinitionBuilder.cs create mode 100644 src/core/Elsa.Core/Contracts/Delegates.cs create mode 100644 src/core/Elsa.Core/Contracts/IActivity.cs create mode 100644 src/core/Elsa.Core/Contracts/IActivityExecutionBuilder.cs create mode 100644 src/core/Elsa.Core/Contracts/IActivityExecutionMiddleware.cs create mode 100644 src/core/Elsa.Core/Contracts/IActivityExecutionPipeline.cs create mode 100644 src/core/Elsa.Core/Contracts/IActivityInvoker.cs create mode 100644 src/core/Elsa.Core/Contracts/IActivityNodeResolver.cs create mode 100644 src/core/Elsa.Core/Contracts/IActivityScheduler.cs create mode 100644 src/core/Elsa.Core/Contracts/IActivitySchedulerFactory.cs create mode 100644 src/core/Elsa.Core/Contracts/IActivityWalker.cs create mode 100644 src/core/Elsa.Core/Contracts/IContainer.cs create mode 100644 src/core/Elsa.Core/Contracts/IExpression.cs create mode 100644 src/core/Elsa.Core/Contracts/IExpressionEvaluator.cs create mode 100644 src/core/Elsa.Core/Contracts/IExpressionHandler.cs create mode 100644 src/core/Elsa.Core/Contracts/IExpressionHandlerRegistry.cs create mode 100644 src/core/Elsa.Core/Contracts/IHasher.cs create mode 100644 src/core/Elsa.Core/Contracts/IIdentityGenerator.cs create mode 100644 src/core/Elsa.Core/Contracts/IIdentityGraphService.cs create mode 100644 src/core/Elsa.Core/Contracts/INode.cs create mode 100644 src/core/Elsa.Core/Contracts/IReference.cs create mode 100644 src/core/Elsa.Core/Contracts/IScheduledNodeExecuted.cs create mode 100644 src/core/Elsa.Core/Contracts/IScopeCompletedHandler.cs create mode 100644 src/core/Elsa.Core/Contracts/ISystemClock.cs create mode 100644 src/core/Elsa.Core/Contracts/ITrigger.cs create mode 100644 src/core/Elsa.Core/Contracts/IWorkflowDefinitionBuilder.cs create mode 100644 src/core/Elsa.Core/Contracts/IWorkflowEngine.cs create mode 100644 src/core/Elsa.Core/Contracts/IWorkflowExecutionBuilder.cs create mode 100644 src/core/Elsa.Core/Contracts/IWorkflowExecutionMiddleware.cs create mode 100644 src/core/Elsa.Core/Contracts/IWorkflowExecutionPipeline.cs create mode 100644 src/core/Elsa.Core/Contracts/IWorkflowStateSerializer.cs create mode 100644 src/core/Elsa.Core/Elsa.Core.csproj create mode 100644 src/core/Elsa.Core/Elsa.Core.csproj.DotSettings create mode 100644 src/core/Elsa.Core/Exceptions/TypeConversionException.cs create mode 100644 src/core/Elsa.Core/Expressions/DelegateExpression.cs create mode 100644 src/core/Elsa.Core/Expressions/ElsaExpression.cs create mode 100644 src/core/Elsa.Core/Expressions/JsonExpression.cs create mode 100644 src/core/Elsa.Core/Expressions/LiteralExpression.cs create mode 100644 src/core/Elsa.Core/Expressions/VariableExpression.cs create mode 100644 src/core/Elsa.Core/Extensions/ActivityDriverExtensions.cs create mode 100644 src/core/Elsa.Core/Extensions/ActivityExtensions.cs create mode 100644 src/core/Elsa.Core/Extensions/NodeExtensions.cs create mode 100644 src/core/Elsa.Core/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/core/Elsa.Core/Helpers/ObjectConverter.cs create mode 100644 src/core/Elsa.Core/Helpers/TypeNameHelper.cs create mode 100644 src/core/Elsa.Core/IsExternalInit.cs create mode 100644 src/core/Elsa.Core/Models/Activity.cs create mode 100644 src/core/Elsa.Core/Models/ActivityExecutionContext.cs create mode 100644 src/core/Elsa.Core/Models/ActivityNode.cs create mode 100644 src/core/Elsa.Core/Models/ActivityWorkItem.cs create mode 100644 src/core/Elsa.Core/Models/Argument.cs create mode 100644 src/core/Elsa.Core/Models/Bookmark.cs create mode 100644 src/core/Elsa.Core/Models/Composite.cs create mode 100644 src/core/Elsa.Core/Models/DelegateReference.cs create mode 100644 src/core/Elsa.Core/Models/DispatchWorkflowResult.cs create mode 100644 src/core/Elsa.Core/Models/DynamicActivity.cs create mode 100644 src/core/Elsa.Core/Models/ElsaExpressionReference.cs create mode 100644 src/core/Elsa.Core/Models/ExecuteWorkflowResult.cs create mode 100644 src/core/Elsa.Core/Models/ExpressionExecutionContext.cs create mode 100644 src/core/Elsa.Core/Models/Input.cs create mode 100644 src/core/Elsa.Core/Models/JsonObject.cs create mode 100644 src/core/Elsa.Core/Models/Literal.cs create mode 100644 src/core/Elsa.Core/Models/NodeIdentity.cs create mode 100644 src/core/Elsa.Core/Models/Output.cs create mode 100644 src/core/Elsa.Core/Models/Register.cs create mode 100644 src/core/Elsa.Core/Models/RegisterLocation.cs create mode 100644 src/core/Elsa.Core/Models/RegisterLocationReference.cs create mode 100644 src/core/Elsa.Core/Models/Trigger.cs create mode 100644 src/core/Elsa.Core/Models/TriggerIndexingContext.cs create mode 100644 src/core/Elsa.Core/Models/TriggerType.cs create mode 100644 src/core/Elsa.Core/Models/TypeDescriptor.cs create mode 100644 src/core/Elsa.Core/Models/TypeKind.cs create mode 100644 src/core/Elsa.Core/Models/Variable.cs create mode 100644 src/core/Elsa.Core/Models/Workflow.cs create mode 100644 src/core/Elsa.Core/Models/WorkflowExecutionContext.cs create mode 100644 src/core/Elsa.Core/Models/WorkflowExecutionLogEntry.cs create mode 100644 src/core/Elsa.Core/Models/WorkflowIdentity.cs create mode 100644 src/core/Elsa.Core/Models/WorkflowIndexingContext.cs create mode 100644 src/core/Elsa.Core/Models/WorkflowMetadata.cs create mode 100644 src/core/Elsa.Core/Models/WorkflowPublication.cs create mode 100644 src/core/Elsa.Core/Options/WorkflowEngineOptions.cs create mode 100644 src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityExecutionMiddlewareExtensions.cs create mode 100644 src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityExecutionPipeline.cs create mode 100644 src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityExecutionPipelineBuilder.cs create mode 100644 src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityMiddlewareDelegate.cs create mode 100644 src/core/Elsa.Core/Pipelines/ActivityExecution/Components/ActivityInvokerMiddleware.cs create mode 100644 src/core/Elsa.Core/Pipelines/ActivityExecution/Components/LoggingMiddleware.cs create mode 100644 src/core/Elsa.Core/Pipelines/MiddlewareHelpers.cs create mode 100644 src/core/Elsa.Core/Pipelines/WorkflowExecution/Components/ActivitySchedulerMiddleware.cs create mode 100644 src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowExecutionMiddlewareExtensions.cs create mode 100644 src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowExecutionPipeline.cs create mode 100644 src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowExecutionPipelineBuilder.cs create mode 100644 src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowMiddlewareDelegate.cs create mode 100644 src/core/Elsa.Core/Services/ActivityInvoker.cs create mode 100644 src/core/Elsa.Core/Services/ActivityScheduler.cs create mode 100644 src/core/Elsa.Core/Services/ActivitySchedulerFactory.cs create mode 100644 src/core/Elsa.Core/Services/ActivityWalker.cs create mode 100644 src/core/Elsa.Core/Services/ExpressionEvaluator.cs create mode 100644 src/core/Elsa.Core/Services/ExpressionHandlerRegistry.cs create mode 100644 src/core/Elsa.Core/Services/Hasher.cs create mode 100644 src/core/Elsa.Core/Services/IdentityGenerator.cs create mode 100644 src/core/Elsa.Core/Services/IdentityGraphService.cs create mode 100644 src/core/Elsa.Core/Services/SystemClock.cs create mode 100644 src/core/Elsa.Core/Services/WorkflowEngine.cs create mode 100644 src/core/Elsa.Core/Services/WorkflowStateSerializer.cs create mode 100644 src/core/Elsa.Core/State/ActivityExecutionContextState.cs create mode 100644 src/core/Elsa.Core/State/CompletionCallbackState.cs create mode 100644 src/core/Elsa.Core/State/RegisterState.cs create mode 100644 src/core/Elsa.Core/State/WorkflowState.cs create mode 100644 src/core/Elsa.Management/Contracts/IActivityDescriber.cs create mode 100644 src/core/Elsa.Management/Contracts/IActivityFactory.cs create mode 100644 src/core/Elsa.Management/Contracts/IActivityPropertyDefaultValueProvider.cs create mode 100644 src/core/Elsa.Management/Contracts/IActivityProvider.cs create mode 100644 src/core/Elsa.Management/Contracts/IActivityRegistry.cs create mode 100644 src/core/Elsa.Management/Contracts/IActivityRegistryPopulator.cs create mode 100644 src/core/Elsa.Management/Contracts/IExpressionSyntaxProvider.cs create mode 100644 src/core/Elsa.Management/Contracts/IExpressionSyntaxRegistry.cs create mode 100644 src/core/Elsa.Management/Contracts/IExpressionSyntaxRegistryPopulator.cs create mode 100644 src/core/Elsa.Management/Contracts/IPropertyDefaultValueResolver.cs create mode 100644 src/core/Elsa.Management/Contracts/IPropertyOptionsResolver.cs create mode 100644 src/core/Elsa.Management/Contracts/ITriggerDescriber.cs create mode 100644 src/core/Elsa.Management/Contracts/ITriggerFactory.cs create mode 100644 src/core/Elsa.Management/Contracts/ITriggerProvider.cs create mode 100644 src/core/Elsa.Management/Contracts/ITriggerRegistry.cs create mode 100644 src/core/Elsa.Management/Contracts/ITriggerRegistryPopulator.cs create mode 100644 src/core/Elsa.Management/Contracts/IWellKnownTypeRegistry.cs create mode 100644 src/core/Elsa.Management/Contracts/IWorkflowPublisher.cs create mode 100644 src/core/Elsa.Management/Contracts/WellKnownTypeRegistry.cs create mode 100644 src/core/Elsa.Management/Elsa.Management.csproj create mode 100644 src/core/Elsa.Management/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/core/Elsa.Management/Extensions/TypeExtensions.cs create mode 100644 src/core/Elsa.Management/Models/ActivityDescriptor.cs create mode 100644 src/core/Elsa.Management/Models/ActivityTraits.cs create mode 100644 src/core/Elsa.Management/Models/ExpressionSyntaxDescriptor.cs create mode 100644 src/core/Elsa.Management/Models/InputDescriptor.cs create mode 100644 src/core/Elsa.Management/Models/InputUIHints.cs create mode 100644 src/core/Elsa.Management/Models/NodeDescriptor.cs create mode 100644 src/core/Elsa.Management/Models/OutputDescriptor.cs create mode 100644 src/core/Elsa.Management/Models/Port.cs create mode 100644 src/core/Elsa.Management/Models/PropertyDescriptor.cs create mode 100644 src/core/Elsa.Management/Models/SelectListItem.cs create mode 100644 src/core/Elsa.Management/Models/TriggerDescriptor.cs create mode 100644 src/core/Elsa.Management/Notifications/WorkflowPublished.cs create mode 100644 src/core/Elsa.Management/Notifications/WorkflowPublishing.cs create mode 100644 src/core/Elsa.Management/Notifications/WorkflowRetracted.cs create mode 100644 src/core/Elsa.Management/Notifications/WorkflowRetracting.cs create mode 100644 src/core/Elsa.Management/Options/ApiOptions.cs create mode 100644 src/core/Elsa.Management/Providers/DefaultExpressionSyntaxProvider.cs create mode 100644 src/core/Elsa.Management/Providers/TypedActivityProvider.cs create mode 100644 src/core/Elsa.Management/Providers/TypedTriggerProvider.cs create mode 100644 src/core/Elsa.Management/Serialization/Converters/ActivityJsonConverter.cs create mode 100644 src/core/Elsa.Management/Serialization/Converters/ActivityJsonConverterFactory.cs create mode 100644 src/core/Elsa.Management/Serialization/Converters/ConnectionJsonConverter.cs create mode 100644 src/core/Elsa.Management/Serialization/Converters/FlowchartJsonConverter.cs create mode 100644 src/core/Elsa.Management/Serialization/Converters/InputJsonConverter.cs create mode 100644 src/core/Elsa.Management/Serialization/Converters/InputJsonConverterFactory.cs create mode 100644 src/core/Elsa.Management/Serialization/Converters/TriggerJsonConverter.cs create mode 100644 src/core/Elsa.Management/Serialization/Converters/TriggerJsonConverterFactory.cs create mode 100644 src/core/Elsa.Management/Serialization/Converters/TypeJsonConverter.cs create mode 100644 src/core/Elsa.Management/Serialization/Extensions/JsonSerializerExtensions.cs create mode 100644 src/core/Elsa.Management/Serialization/WorkflowSerializerOptionsProvider.cs create mode 100644 src/core/Elsa.Management/Services/ActivityDescriber.cs create mode 100644 src/core/Elsa.Management/Services/ActivityFactory.cs create mode 100644 src/core/Elsa.Management/Services/ActivityRegistry.cs create mode 100644 src/core/Elsa.Management/Services/ActivityRegistryPopulator.cs create mode 100644 src/core/Elsa.Management/Services/ExpressionSyntaxRegistry.cs create mode 100644 src/core/Elsa.Management/Services/ExpressionSyntaxRegistryPopulator.cs create mode 100644 src/core/Elsa.Management/Services/IActivityPropertyOptionsProvider.cs create mode 100644 src/core/Elsa.Management/Services/PropertyDefaultValueResolver.cs create mode 100644 src/core/Elsa.Management/Services/PropertyOptionsResolver.cs create mode 100644 src/core/Elsa.Management/Services/TriggerDescriber.cs create mode 100644 src/core/Elsa.Management/Services/TriggerFactory.cs create mode 100644 src/core/Elsa.Management/Services/TriggerRegistry.cs create mode 100644 src/core/Elsa.Management/Services/TriggerRegistryPopulator.cs create mode 100644 src/core/Elsa.Management/Services/WorkflowPublisher.cs create mode 100644 src/core/Elsa.Mediator/Contracts/ICommand.cs create mode 100644 src/core/Elsa.Mediator/Contracts/ICommandHandler.cs create mode 100644 src/core/Elsa.Mediator/Contracts/ICommandSender.cs create mode 100644 src/core/Elsa.Mediator/Contracts/IMediator.cs create mode 100644 src/core/Elsa.Mediator/Contracts/INotification.cs create mode 100644 src/core/Elsa.Mediator/Contracts/INotificationHandler.cs create mode 100644 src/core/Elsa.Mediator/Contracts/IPublisher.cs create mode 100644 src/core/Elsa.Mediator/Contracts/IRequest.cs create mode 100644 src/core/Elsa.Mediator/Contracts/IRequestHandler.cs create mode 100644 src/core/Elsa.Mediator/Contracts/IRequestSender.cs create mode 100644 src/core/Elsa.Mediator/Contracts/Unit.cs create mode 100644 src/core/Elsa.Mediator/Elsa.Mediator.csproj create mode 100644 src/core/Elsa.Mediator/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Command/CommandContext.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Command/CommandMiddlewareDelegate.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Command/CommandPipeline.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Command/CommandPipelineBuilder.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Command/CommandPipelineBuilderExtensions.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Command/Components/CommandHandlerInvokerMiddleware.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Command/Components/CommandLoggingMiddleware.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Command/Contracts/ICommandMiddleware.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Command/Contracts/ICommandPipeline.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Command/Contracts/ICommandPipelineBuilder.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Command/MiddlewareExtensions.cs create mode 100644 src/core/Elsa.Mediator/Middleware/MiddlewareHelpers.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Notification/Components/NotificationHandlerInvokerMiddleware.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Notification/Components/NotificationLoggingMiddleware.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Notification/Contracts/INotificationMiddleware.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Notification/Contracts/INotificationPipeline.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Notification/Contracts/INotificationPipelineBuilder.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Notification/MiddlewareExtensions.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Notification/NotificationContext.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Notification/NotificationMiddlewareDelegate.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Notification/NotificationPipeline.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Notification/NotificationPipelineBuilder.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Notification/NotificationPipelineBuilderExtensions.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Request/Components/RequestHandlerInvokerMiddleware.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Request/Components/RequestLoggingMiddleware.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Request/Contracts/IRequestMiddleware.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Request/Contracts/IRequestPipeline.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Request/Contracts/IRequestPipelineBuilder.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Request/MiddlewareExtensions.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Request/RequestContext.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Request/RequestMiddlewareDelegate.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Request/RequestPipeline.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Request/RequestPipelineBuilder.cs create mode 100644 src/core/Elsa.Mediator/Middleware/Request/RequestPipelineBuilderExtensions.cs create mode 100644 src/core/Elsa.Mediator/Services/DefaultMediator.cs create mode 100644 src/core/Elsa.Serialization/Elsa.Serialization.csproj create mode 100644 src/core/Elsa/Elsa.csproj create mode 100644 src/core/Elsa/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/core/Elsa/Extensions/ServiceProviderExtensions.cs create mode 100644 src/designer/elsa-workflows-designer/.editorconfig create mode 100644 src/designer/elsa-workflows-designer/.gitignore create mode 100644 src/designer/elsa-workflows-designer/.prettierrc.json create mode 100644 src/designer/elsa-workflows-designer/LICENSE create mode 100644 src/designer/elsa-workflows-designer/package-lock.json create mode 100644 src/designer/elsa-workflows-designer/package.json create mode 100644 src/designer/elsa-workflows-designer/readme.md create mode 100644 src/designer/elsa-workflows-designer/src/components.d.ts create mode 100644 src/designer/elsa-workflows-designer/src/components/activities/container-activity-component.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/activities/flowchart/activity-walker.ts create mode 100644 src/designer/elsa-workflows-designer/src/components/activities/flowchart/flowchart.scss create mode 100644 src/designer/elsa-workflows-designer/src/components/activities/flowchart/flowchart.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/activities/flowchart/graph-factory.ts create mode 100644 src/designer/elsa-workflows-designer/src/components/activities/flowchart/models.ts create mode 100644 src/designer/elsa-workflows-designer/src/components/activities/flowchart/node-factory.ts create mode 100644 src/designer/elsa-workflows-designer/src/components/activities/flowchart/ports.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/activities/flowchart/shapes.ts create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/activity-properties-editor/activity-properties-editor.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.scss create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/form-panel/form-panel.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/panel/models.ts create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/panel/panel.scss create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/panel/panel.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/slide-over-panel/slide-over-panel.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/state.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/toolbox/toolbox-activities.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/toolbox/toolbox-triggers.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/toolbox/toolbox.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/trigger-container/trigger-container.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/trigger-properties-editor/trigger-properties-editor.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/workflow-editor/workflow-editor.scss create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/workflow-editor/workflow-editor.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/designer/workflow-properties-editor/workflow-properties-editor.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/activities/flowchart.ts create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/activities/http-trigger.ts create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/activities/if.ts create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/activities/index.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/activities/read-line.ts create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/activities/write-line.ts create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/tooling/bulk-actions.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/tooling/delete.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/tooling/edit.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/tooling/index.ts create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/tooling/order-by.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/tooling/page-size-icon.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/tooling/publish.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/tooling/un-publish.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/tooling/workflow-status-icon.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/tooling/workflow.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/inputs/check-list.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/inputs/dropdown.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/inputs/single-line.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/modals/workflow-browser/workflow-browser.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/modals/workflow-instance-browser/filter.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/modals/workflow-instance-browser/search.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/modals/workflow-instance-browser/utils.ts create mode 100644 src/designer/elsa-workflows-designer/src/components/modals/workflow-instance-browser/workflow-instance-browser.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/shared/context-menu/context-menu.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/shared/context-menu/models.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/shared/dropdown-button/dropdown-button.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/shared/dropdown-button/models.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/shared/forms/form-entry.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/shared/forms/hint.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/shared/forms/info-list.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/shared/modal-dialog/modal-dialog.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/shared/pager/controls.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/shared/pager/pager.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/shell/studio/studio.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/toolbar/workflow-publish-button/workflow-publish-button.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/toolbar/workflow-toolbar-menu/workflow-toolbar-menu.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/toolbar/workflow-toolbar/workflow-toolbar.tsx create mode 100644 src/designer/elsa-workflows-designer/src/data/store.ts create mode 100644 src/designer/elsa-workflows-designer/src/drivers/activity/default-activity-driver.tsx create mode 100644 src/designer/elsa-workflows-designer/src/drivers/input/default-input-driver.tsx create mode 100644 src/designer/elsa-workflows-designer/src/drivers/trigger/default-trigger-driver.ts create mode 100644 src/designer/elsa-workflows-designer/src/global/tailwind.css create mode 100644 src/designer/elsa-workflows-designer/src/index-automatic-interaction.html create mode 100644 src/designer/elsa-workflows-designer/src/index-manual-interaction.html create mode 100644 src/designer/elsa-workflows-designer/src/index.html create mode 100644 src/designer/elsa-workflows-designer/src/index.ts create mode 100644 src/designer/elsa-workflows-designer/src/models/api.ts create mode 100644 src/designer/elsa-workflows-designer/src/models/core.ts create mode 100644 src/designer/elsa-workflows-designer/src/models/event-args.ts create mode 100644 src/designer/elsa-workflows-designer/src/models/events.ts create mode 100644 src/designer/elsa-workflows-designer/src/models/expressions.ts create mode 100644 src/designer/elsa-workflows-designer/src/models/index.ts create mode 100644 src/designer/elsa-workflows-designer/src/models/shared.ts create mode 100644 src/designer/elsa-workflows-designer/src/models/ui.ts create mode 100644 src/designer/elsa-workflows-designer/src/services/activity-driver-registry.ts create mode 100644 src/designer/elsa-workflows-designer/src/services/activity-driver.ts create mode 100644 src/designer/elsa-workflows-designer/src/services/activity-icon-registry.ts create mode 100644 src/designer/elsa-workflows-designer/src/services/custom-event-bus.d.ts create mode 100644 src/designer/elsa-workflows-designer/src/services/custom-event-bus.js create mode 100644 src/designer/elsa-workflows-designer/src/services/elsa-api-client-provider.ts create mode 100644 src/designer/elsa-workflows-designer/src/services/event-bus.ts create mode 100644 src/designer/elsa-workflows-designer/src/services/index.ts create mode 100644 src/designer/elsa-workflows-designer/src/services/input-control-registry.tsx create mode 100644 src/designer/elsa-workflows-designer/src/services/input-driver-registry.ts create mode 100644 src/designer/elsa-workflows-designer/src/services/node-input-driver.ts create mode 100644 src/designer/elsa-workflows-designer/src/services/server-settings.ts create mode 100644 src/designer/elsa-workflows-designer/src/services/trigger-driver-registry.ts create mode 100644 src/designer/elsa-workflows-designer/src/services/trigger-driver.ts create mode 100644 src/designer/elsa-workflows-designer/src/utils/index.ts create mode 100644 src/designer/elsa-workflows-designer/src/utils/select-list-items.ts create mode 100644 src/designer/elsa-workflows-designer/src/utils/utils.ts create mode 100644 src/designer/elsa-workflows-designer/stencil.config.ts create mode 100644 src/designer/elsa-workflows-designer/tailwind.config.js create mode 100644 src/designer/elsa-workflows-designer/tsconfig.json create mode 100644 src/dsl/Elsa.Dsl/Abstractions/ElsaLexer.cs create mode 100644 src/dsl/Elsa.Dsl/Abstractions/ElsaLexer.interp create mode 100644 src/dsl/Elsa.Dsl/Abstractions/ElsaLexer.tokens create mode 100644 src/dsl/Elsa.Dsl/Abstractions/ElsaParser.cs create mode 100644 src/dsl/Elsa.Dsl/Abstractions/ElsaParser.interp create mode 100644 src/dsl/Elsa.Dsl/Abstractions/ElsaParser.tokens create mode 100644 src/dsl/Elsa.Dsl/Abstractions/ElsaParserBaseListener.cs create mode 100644 src/dsl/Elsa.Dsl/Abstractions/ElsaParserBaseVisitor.cs create mode 100644 src/dsl/Elsa.Dsl/Abstractions/ElsaParserListener.cs create mode 100644 src/dsl/Elsa.Dsl/Abstractions/ElsaParserVisitor.cs create mode 100644 src/dsl/Elsa.Dsl/Abstractions/IFunctionActivityRegistry.cs create mode 100644 src/dsl/Elsa.Dsl/Abstractions/ITypeSystem.cs create mode 100644 src/dsl/Elsa.Dsl/AssemblyInfo.cs create mode 100644 src/dsl/Elsa.Dsl/Contracts/IDslEngine.cs create mode 100644 src/dsl/Elsa.Dsl/Dsl/ElsaLexer.g4 create mode 100644 src/dsl/Elsa.Dsl/Dsl/ElsaParser.g4 create mode 100644 src/dsl/Elsa.Dsl/Dsl/Samples/For.elsa create mode 100644 src/dsl/Elsa.Dsl/Dsl/Samples/Full.elsa create mode 100644 src/dsl/Elsa.Dsl/Dsl/Samples/Greeting.elsa create mode 100644 src/dsl/Elsa.Dsl/Dsl/Samples/HelloGoodbye.elsa create mode 100644 src/dsl/Elsa.Dsl/Dsl/Samples/HelloWorld.elsa create mode 100644 src/dsl/Elsa.Dsl/Dsl/Samples/HttpTrigger.elsa create mode 100644 src/dsl/Elsa.Dsl/Dsl/Samples/If.elsa create mode 100644 src/dsl/Elsa.Dsl/Dsl/Samples/Scratchpad.elsa create mode 100644 src/dsl/Elsa.Dsl/Dsl/gen/ElsaLexer.interp create mode 100644 src/dsl/Elsa.Dsl/Dsl/gen/ElsaLexer.java create mode 100644 src/dsl/Elsa.Dsl/Dsl/gen/ElsaLexer.tokens create mode 100644 src/dsl/Elsa.Dsl/Elsa.Dsl.csproj create mode 100644 src/dsl/Elsa.Dsl/Elsa.Dsl.csproj.DotSettings create mode 100644 src/dsl/Elsa.Dsl/Extensions/ListConverter.cs create mode 100644 src/dsl/Elsa.Dsl/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/dsl/Elsa.Dsl/Extensions/TypeSystemExtensions.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitArgs.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitBlockStatement.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitBrackets.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitExternalExpression.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitFile.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitFunction.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitIfStatement.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitLocalVariable.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitNewObject.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitObject.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitProperty.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitStringValue.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitTrigger.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitWorkflowVariable.cs create mode 100644 src/dsl/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/WorkflowDefinitionBuilderInterpreter.cs create mode 100644 src/dsl/Elsa.Dsl/Models/DefinedVariable.cs create mode 100644 src/dsl/Elsa.Dsl/Models/WorkflowDefinitionInterpreterSettings.cs create mode 100644 src/dsl/Elsa.Dsl/Services/DslEngine.cs create mode 100644 src/dsl/Elsa.Dsl/Services/FunctionActivityRegistry.cs create mode 100644 src/dsl/Elsa.Dsl/Services/TypeSystem.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Commands/DeleteWorkflowBookmarks.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Commands/DeleteWorkflowDefinition.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Commands/DeleteWorkflowInstances.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Commands/ReplaceWorkflowTriggers.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Commands/SaveWorkflow.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Commands/SaveWorkflowBookmarks.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Commands/SaveWorkflowInstance.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Contracts/IWorkflowBookmarkStore.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Contracts/IWorkflowInstanceStore.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Contracts/IWorkflowTriggerStore.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Elsa.Persistence.Abstractions.csproj create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Entities/Entity.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Entities/OrderBy.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Entities/OrderDirection.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Entities/WorkflowBookmark.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Entities/WorkflowDefinition.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Entities/WorkflowInstance.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Entities/WorkflowStatus.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Entities/WorkflowTrigger.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Extensions/WorkflowDefinitionExtensions.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Extensions/WorkflowExtensions.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Mappers/WorkflowDefinitionMapper.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Middleware/WorkflowExecution/PersistWorkflowInstanceMiddleware.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Models/Cursor.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Models/CursorDirection.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Models/PagedList.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Models/PagerParameters.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Models/VersionOptions.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Models/WorkflowInstanceSummary.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Models/WorkflowSummary.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Requests/FindLatestAndPublishedWorkflows.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Requests/FindManyWorkflowDefinitions.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Requests/FindWorkflowBookmarks.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Requests/FindWorkflowDefinition.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Requests/FindWorkflowInstance.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Requests/FindWorkflowTriggers.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Requests/ListWorkflowInstanceSummaries.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Requests/ListWorkflowSummaries.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/Requests/ListWorkflows.cs create mode 100644 src/persistence/Elsa.Persistence.Abstractions/TypeConverters/VersionOptionsTypeConverter.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/DbContextOptionsBuilderExtensions.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Elsa.Persistence.EntityFrameworkCore.Sqlite.csproj create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Migrations/20211228122857_Initial.Designer.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Migrations/20211228122857_Initial.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Migrations/ElsaDbContextModelSnapshot.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Readme.md create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/SqliteElsaContextFactory.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Configuration/WorkflowBookmarkConfiguration.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Configuration/WorkflowDefinitionConfiguration.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Configuration/WorkflowInstanceConfiguration.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Configuration/WorkflowTriggerConfiguration.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Contracts/IEntitySerializer.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Contracts/IStore.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Elsa.Persistence.EntityFrameworkCore.csproj create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/ElsaDbContext.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Extensions/DatabaseFacadeExtensions.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Extensions/QueryableBulkExtensions.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Commands/DeleteWorkflowBookmarksHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Commands/DeleteWorkflowDefinitionHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Commands/DeleteWorkflowInstancesHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Commands/ReplaceWorkflowTriggersHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Commands/SaveWorkflowBookmarksHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Commands/SaveWorkflowDefinitionHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Commands/SaveWorkflowInstanceHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Requests/FindLatestAndPublishedWorkflowDefinitionsHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Requests/FindManyWorkflowDefinitionsHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Requests/FindWorkflowBookmarksHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Requests/FindWorkflowHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Requests/FindWorkflowInstanceHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Requests/FindWorkflowTriggersHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Requests/ListWorkflowInstanceSummariesHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Requests/ListWorkflowSummariesHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Requests/ListWorkflowsHandler.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Serialization/WorkflowDefinitionSerializer.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Serialization/WorkflowInstanceSerializer.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/HostedServices/RunMigrations.cs create mode 100644 src/persistence/Elsa.Persistence.EntityFrameworkCore/Services/EFCoreStore.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Elsa.Persistence.InMemory.csproj create mode 100644 src/persistence/Elsa.Persistence.InMemory/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Commands/DeleteWorkflowBookmarksHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Commands/DeleteWorkflowDefinitionHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Commands/DeleteWorkflowInstancesHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Commands/ReplaceWorkflowTriggersHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Commands/SaveWorkflowBookmarksHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Commands/SaveWorkflowHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Commands/SaveWorkflowInstanceHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Requests/FindLatestAndPublishedWorkflowDefinitionsHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Requests/FindManyWorkflowDefinitionsHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Requests/FindWorkflowBookmarksHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Requests/FindWorkflowDefinitionHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Requests/FindWorkflowInstanceHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Requests/FindWorkflowTriggersHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Requests/ListWorkflowInstanceSummariesHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Requests/ListWorkflowSummariesHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Handlers/Requests/ListWorkflowsHandler.cs create mode 100644 src/persistence/Elsa.Persistence.InMemory/Services/InMemoryStore.cs create mode 100644 src/runtime/Elsa.Runtime.ProtoActor/Actors/WorkflowDefinitionActor.cs create mode 100644 src/runtime/Elsa.Runtime.ProtoActor/Actors/WorkflowInstanceActor.cs create mode 100644 src/runtime/Elsa.Runtime.ProtoActor/Actors/WorkflowOperatorActor.cs create mode 100644 src/runtime/Elsa.Runtime.ProtoActor/Elsa.Runtime.ProtoActor.csproj create mode 100644 src/runtime/Elsa.Runtime.ProtoActor/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/runtime/Elsa.Runtime.ProtoActor/GrainKinds.cs create mode 100644 src/runtime/Elsa.Runtime.ProtoActor/HostedServices/WorkflowServerHost.cs create mode 100644 src/runtime/Elsa.Runtime.ProtoActor/Services/ProtoActorWorkflowInvoker.cs create mode 100644 src/runtime/Elsa.Runtime.ProtoActor/messages.proto create mode 100644 src/runtime/Elsa.Runtime/Abstractions/StimulusHandler.cs create mode 100644 src/runtime/Elsa.Runtime/Abstractions/WorkflowInstructionInterpreter.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/DispatchWorkflowInstructionResult.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/ExecuteWorkflowInstructionResult.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/IStimulus.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/IStimulusHandler.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/IStimulusInterpreter.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/ITriggerIndexer.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/IWorkflow.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/IWorkflowInstruction.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/IWorkflowInstructionExecutor.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/IWorkflowInstructionInterpreter.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/IWorkflowInstructionScheduler.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/IWorkflowInvoker.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/IWorkflowProvider.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/IWorkflowRegistry.cs create mode 100644 src/runtime/Elsa.Runtime/Contracts/IWorkflowServer.cs create mode 100644 src/runtime/Elsa.Runtime/Elsa.Runtime.csproj create mode 100644 src/runtime/Elsa.Runtime/Extensions/EnumerableExtensions.cs create mode 100644 src/runtime/Elsa.Runtime/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/runtime/Elsa.Runtime/Handlers/IndexWorkflowTriggersHandler.cs create mode 100644 src/runtime/Elsa.Runtime/HostedServices/IndexWorkflowTriggersHostedService.cs create mode 100644 src/runtime/Elsa.Runtime/HostedServices/RegisterDescriptorsHostedService.cs create mode 100644 src/runtime/Elsa.Runtime/HostedServices/RegisterExpressionSyntaxDescriptorsHostedService.cs create mode 100644 src/runtime/Elsa.Runtime/Instructions/ResumeWorkflowInstruction.cs create mode 100644 src/runtime/Elsa.Runtime/Instructions/TriggerWorkflowInstruction.cs create mode 100644 src/runtime/Elsa.Runtime/Interpreters/ResumeWorkflowInstructionInterpreter.cs create mode 100644 src/runtime/Elsa.Runtime/Interpreters/TriggerWorkflowInstructionInterpreter.cs create mode 100644 src/runtime/Elsa.Runtime/IsExternalInit.cs create mode 100644 src/runtime/Elsa.Runtime/Models/DispatchWorkflowDefinitionRequest.cs create mode 100644 src/runtime/Elsa.Runtime/Models/DispatchWorkflowInstanceRequest.cs create mode 100644 src/runtime/Elsa.Runtime/Models/ExecuteWorkflowDefinitionRequest.cs create mode 100644 src/runtime/Elsa.Runtime/Models/ExecuteWorkflowInstanceRequest.cs create mode 100644 src/runtime/Elsa.Runtime/Models/Stimuli.cs create mode 100644 src/runtime/Elsa.Runtime/Options/WorkflowRuntimeOptions.cs create mode 100644 src/runtime/Elsa.Runtime/Services/StimulusInterpreter.cs create mode 100644 src/runtime/Elsa.Runtime/Services/TriggerIndexer.cs create mode 100644 src/runtime/Elsa.Runtime/Services/WorkflowEngineBuilder.cs create mode 100644 src/runtime/Elsa.Runtime/Services/WorkflowInstructionExecutor.cs create mode 100644 src/runtime/Elsa.Runtime/Services/WorkflowRegistry.cs create mode 100644 src/runtime/Elsa.Runtime/Services/WorkflowServer.cs create mode 100644 src/runtime/Elsa.Runtime/Stimuli/Handlers/ResumeWorkflowsStimulusHandler.cs create mode 100644 src/runtime/Elsa.Runtime/Stimuli/Handlers/TriggerWorkflowsStimulusHandler.cs create mode 100644 src/runtime/Elsa.Runtime/Stimuli/StandardStimulus.cs create mode 100644 src/runtime/Elsa.Runtime/WorkflowProviders/ConfigurationWorkflowProvider.cs create mode 100644 src/runtime/Elsa.Runtime/WorkflowProviders/DatabaseWorkflowProvider.cs create mode 100644 src/samples/aspnet/Elsa.Samples.Web1/Activities/MyGreeterComposite.cs create mode 100644 src/samples/aspnet/Elsa.Samples.Web1/Activities/WriteLines.cs create mode 100644 src/samples/aspnet/Elsa.Samples.Web1/Elsa.Samples.Web1.csproj create mode 100644 src/samples/aspnet/Elsa.Samples.Web1/Program.cs create mode 100644 src/samples/aspnet/Elsa.Samples.Web1/Properties/launchSettings.json create mode 100644 src/samples/aspnet/Elsa.Samples.Web1/Workflows/CompositeActivitiesWorkflow.cs create mode 100644 src/samples/aspnet/Elsa.Samples.Web1/Workflows/ForkedHttpWorkflow.cs create mode 100644 src/samples/aspnet/Elsa.Samples.Web1/Workflows/HelloWorldWorkflow.cs create mode 100644 src/samples/aspnet/Elsa.Samples.Web1/Workflows/HttpWorkflow.cs create mode 100644 src/samples/aspnet/Elsa.Samples.Web1/appsettings.json create mode 100644 src/samples/aspnet/Elsa.Samples.Web2/Elsa.Samples.Web2.csproj create mode 100644 src/samples/aspnet/Elsa.Samples.Web2/Program.cs create mode 100644 src/samples/aspnet/Elsa.Samples.Web2/Properties/launchSettings.json create mode 100644 src/samples/aspnet/Elsa.Samples.Web2/Workflows/HelloWorldWorkflow.cs create mode 100644 src/samples/aspnet/Elsa.Samples.Web2/appsettings.json create mode 100644 src/samples/console/Elsa.Samples.Console1/Elsa.Samples.Console1.csproj create mode 100644 src/samples/console/Elsa.Samples.Console1/Program.cs create mode 100644 src/samples/console/Elsa.Samples.Console1/Workflows/BlockingParallelForEachWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.Console1/Workflows/BlockingWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.Console1/Workflows/ConditionalWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.Console1/Workflows/CustomizedActivityWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.Console1/Workflows/FlowchartWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.Console1/Workflows/ForEachWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.Console1/Workflows/ForWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.Console1/Workflows/ForkedWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.Console1/Workflows/GreetingWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.Console1/Workflows/HelloGoodbyeWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.Console1/Workflows/HelloWorldWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.Console1/Workflows/ParallelForEachWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.Console1/Workflows/VariablesWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.Console1/Workflows/WhileWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.Console2/Elsa.Samples.Console2.csproj create mode 100644 src/samples/console/Elsa.Samples.Console2/Program.cs create mode 100644 src/samples/console/Elsa.Samples.Console2/Sample1.elsa create mode 100644 src/scripting/Elsa.Scripting.JavaScript/Elsa.Scripting.JavaScript.csproj create mode 100644 src/scripting/Elsa.Scripting.JavaScript/JavaScriptExpression.cs create mode 100644 src/scripting/Elsa.Scripting.JavaScript/JavaScriptExpressionReference.cs create mode 100644 src/scripting/Elsa.Scripting.JavaScript/ServiceCollectionExtensions.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..49fa223cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,72 @@ +#Ignore thumbnails created by Windows +Thumbs.db + +#Ignore files built by Visual Studio +*.obj +*.exe +*.pdb +*.user +*.aps +*.pch +*.vspscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.cache +*.ilk +*.log +[Bb]in +[Dd]ebug*/ +*.lib +*.sbr +obj/ +[Rr]elease*/ +_ReSharper*/ +[Tt]est[Rr]esult* +.vs/ + +# VS Code/Omnisharp crash dumps +mono_crash.mem*.blob + + +#Nuget packages folder +packages/ + +#Ignore git-related files +*.orig + +#Rider +.idea + +#wwwroot +wwwroot/ + +#npm +node_modules/ +.babelrc +.stylelintrc.yml +.eslintrc.yml + +#github +.github/ + +#volatile +App_Data/ +*.db +*.db-shm +*.db-wal + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# Elsa Stencil Components +src/designer/elsa-workflows-designer/dist +src/designer/elsa-workflows-designer/www +src/designer/elsa-workflows-designer/loader + +# Created by developers using GNU/Linux +.directory diff --git a/Elsa.ProtoActor.sln b/Elsa.ProtoActor.sln new file mode 100644 index 000000000..0dd53bec3 --- /dev/null +++ b/Elsa.ProtoActor.sln @@ -0,0 +1,222 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{61017E64-6D00-49CB-9E81-5002DC8F7D5F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{155227F0-A33B-40AA-A4B4-06F813EB921B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{C6658DE0-2B2F-47F0-BB61-2CA66D435C09}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "activities", "activities", "{F8604289-6D28-428E-9FA8-1480464964F0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Console1", "src\samples\console\Elsa.Samples.Console1\Elsa.Samples.Console1.csproj", "{B44A9DDA-8091-490A-BC0B-2EAE4A601B09}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Core", "src\core\Elsa.Core\Elsa.Core.csproj", "{6FA9ED7D-2D32-46BC-B47F-E503B2A4C0A9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Extensions.DependencyInjection", "src\core\Elsa.Extensions.DependencyInjection\Elsa.Extensions.DependencyInjection.csproj", "{042022EA-62BC-44BD-A93F-11ABDB39C791}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{7D21EB5E-D6B4-48A5-A9BC-A26387A65F0B}" +ProjectSection(SolutionItems) = preProject + README.md = README.md + docker-compose.yml = docker-compose.yml +EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{0354F050-3992-4DD4-B0EE-5FBA04AC72B6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "architecture", "architecture", "{A34FD350-79C5-492A-8811-6DBD80830F8B}" +ProjectSection(SolutionItems) = preProject + docs\architecture\Layer1.md = docs\architecture\Layer1.md + docs\architecture\Layer2.md = docs\architecture\Layer2.md +EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "console", "console", "{873BFC3E-63C2-4495-A503-5EC05DCD84E4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "aspnet", "aspnet", "{56C2FFB8-EA54-45B5-A095-4A78142EB4B5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Web1", "src\samples\aspnet\Elsa.Samples.Web1\Elsa.Samples.Web1.csproj", "{A2E6E9A4-4C21-4022-A53E-BDF5ED0D0678}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.Http", "src\activities\Elsa.Activities.Http\Elsa.Activities.Http.csproj", "{CF88276B-D1C8-4E0D-8043-0BFA54DA693E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "api", "api", "{298BA226-B766-40AE-933F-EB9AFCDE051C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Api", "src\api\Elsa.Api\Elsa.Api.csproj", "{39C6C4A2-077E-48C2-B295-DFE357EC38B2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "persistence", "persistence", "{9B7776E2-0014-4C87-B1F4-22FB0BC076F1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Persistence.InMemory", "src\persistence\Elsa.Persistence.InMemory\Elsa.Persistence.InMemory.csproj", "{BC97FD3A-1351-443C-9EAE-0F28F8204A6C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "runtime", "runtime", "{04584653-8E42-47DB-850B-7C6C37B307B7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Runtime", "src\runtime\Elsa.Runtime\Elsa.Runtime.csproj", "{8C5894A4-F358-45CF-B198-BFBCA30B77F6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Persistence.Abstractions", "src\persistence\Elsa.Persistence.Abstractions\Elsa.Persistence.Abstractions.csproj", "{C0226CDD-DF79-4767-A4A5-3D9037535929}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Runtime.HostedServices", "src\runtime\Elsa.Runtime.HostedServices\Elsa.Runtime.HostedServices.csproj", "{4A874C29-2915-4F5C-A01A-80F56B929541}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dsl", "dsl", "{CE121DFD-72D4-4DB7-968F-C0DB320D08FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Dsl", "src\dsl\Elsa.Dsl\Elsa.Dsl.csproj", "{61F5C50C-C572-46AB-8F37-54D1F84CC31D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Console2", "src\samples\console\Elsa.Samples.Console2\Elsa.Samples.Console2.csproj", "{48960E35-903C-4EF0-A8F4-3871B000B12D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.Timers", "src\activities\Elsa.Activities.Timers\Elsa.Activities.Timers.csproj", "{9FE7C169-F639-4F6D-86EB-D97FE3E9D206}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripting", "scripting", "{2633B8B9-4AEC-4A54-8832-1C940B54E041}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Scripting.JavaScript", "src\scripting\Elsa.Scripting.JavaScript\Elsa.Scripting.JavaScript.csproj", "{5D349665-3B5D-4DA4-A7B0-C6F11611B0C9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Runtime.ProtoActor", "src\runtime\Elsa.Runtime.ProtoActor\Elsa.Runtime.ProtoActor.csproj", "{E5A05E1D-0F7F-4030-B434-596C76486CDB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Web2", "src\samples\aspnet\Elsa.Samples.Web2\Elsa.Samples.Web2.csproj", "{13A6BAF1-9BAD-4A3A-A61F-5E4F01CDF0BF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "protoactor", "protoactor", "{03A5D774-A546-4D4B-8687-C6F96A886C72}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Proto.Actor", "..\..\ProtoActor\protoactor-dotnet\src\Proto.Actor\Proto.Actor.csproj", "{B8F68D35-C561-4C45-881D-84142B557A54}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Proto.Cluster", "..\..\ProtoActor\protoactor-dotnet\src\Proto.Cluster\Proto.Cluster.csproj", "{E27E725F-C4B0-4D6E-ADA0-DD7D014F4C6C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Proto.Cluster.TestProvider", "..\..\ProtoActor\protoactor-dotnet\src\Proto.Cluster.TestProvider\Proto.Cluster.TestProvider.csproj", "{EB1BACFB-3DF0-45D0-89AF-4571023DDE50}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Proto.Remote", "..\..\ProtoActor\protoactor-dotnet\src\Proto.Remote\Proto.Remote.csproj", "{0BD81F36-D6B4-4DF1-8BAA-5F25B6779FC5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Proto.Remote.GrpcCore", "..\..\ProtoActor\protoactor-dotnet\src\Proto.Remote.GrpcCore\Proto.Remote.GrpcCore.csproj", "{466E5047-4184-42C6-9C4D-14A5707E2F49}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Api.Core", "src\api\Elsa.Api.Core\Elsa.Api.Core.csproj", "{A335B51D-8219-4361-BAAF-5F5D1B74EF89}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa", "src\core\Elsa\Elsa.csproj", "{521365AE-A681-4E4B-87E6-B4D982CDD520}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B44A9DDA-8091-490A-BC0B-2EAE4A601B09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B44A9DDA-8091-490A-BC0B-2EAE4A601B09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B44A9DDA-8091-490A-BC0B-2EAE4A601B09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B44A9DDA-8091-490A-BC0B-2EAE4A601B09}.Release|Any CPU.Build.0 = Release|Any CPU + {6FA9ED7D-2D32-46BC-B47F-E503B2A4C0A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6FA9ED7D-2D32-46BC-B47F-E503B2A4C0A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FA9ED7D-2D32-46BC-B47F-E503B2A4C0A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6FA9ED7D-2D32-46BC-B47F-E503B2A4C0A9}.Release|Any CPU.Build.0 = Release|Any CPU + {042022EA-62BC-44BD-A93F-11ABDB39C791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {042022EA-62BC-44BD-A93F-11ABDB39C791}.Debug|Any CPU.Build.0 = Debug|Any CPU + {042022EA-62BC-44BD-A93F-11ABDB39C791}.Release|Any CPU.ActiveCfg = Release|Any CPU + {042022EA-62BC-44BD-A93F-11ABDB39C791}.Release|Any CPU.Build.0 = Release|Any CPU + {A2E6E9A4-4C21-4022-A53E-BDF5ED0D0678}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2E6E9A4-4C21-4022-A53E-BDF5ED0D0678}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2E6E9A4-4C21-4022-A53E-BDF5ED0D0678}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2E6E9A4-4C21-4022-A53E-BDF5ED0D0678}.Release|Any CPU.Build.0 = Release|Any CPU + {CF88276B-D1C8-4E0D-8043-0BFA54DA693E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF88276B-D1C8-4E0D-8043-0BFA54DA693E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF88276B-D1C8-4E0D-8043-0BFA54DA693E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF88276B-D1C8-4E0D-8043-0BFA54DA693E}.Release|Any CPU.Build.0 = Release|Any CPU + {39C6C4A2-077E-48C2-B295-DFE357EC38B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39C6C4A2-077E-48C2-B295-DFE357EC38B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39C6C4A2-077E-48C2-B295-DFE357EC38B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39C6C4A2-077E-48C2-B295-DFE357EC38B2}.Release|Any CPU.Build.0 = Release|Any CPU + {BC97FD3A-1351-443C-9EAE-0F28F8204A6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC97FD3A-1351-443C-9EAE-0F28F8204A6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC97FD3A-1351-443C-9EAE-0F28F8204A6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC97FD3A-1351-443C-9EAE-0F28F8204A6C}.Release|Any CPU.Build.0 = Release|Any CPU + {8C5894A4-F358-45CF-B198-BFBCA30B77F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C5894A4-F358-45CF-B198-BFBCA30B77F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C5894A4-F358-45CF-B198-BFBCA30B77F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C5894A4-F358-45CF-B198-BFBCA30B77F6}.Release|Any CPU.Build.0 = Release|Any CPU + {C0226CDD-DF79-4767-A4A5-3D9037535929}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0226CDD-DF79-4767-A4A5-3D9037535929}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0226CDD-DF79-4767-A4A5-3D9037535929}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0226CDD-DF79-4767-A4A5-3D9037535929}.Release|Any CPU.Build.0 = Release|Any CPU + {4A874C29-2915-4F5C-A01A-80F56B929541}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A874C29-2915-4F5C-A01A-80F56B929541}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A874C29-2915-4F5C-A01A-80F56B929541}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A874C29-2915-4F5C-A01A-80F56B929541}.Release|Any CPU.Build.0 = Release|Any CPU + {61F5C50C-C572-46AB-8F37-54D1F84CC31D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61F5C50C-C572-46AB-8F37-54D1F84CC31D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61F5C50C-C572-46AB-8F37-54D1F84CC31D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61F5C50C-C572-46AB-8F37-54D1F84CC31D}.Release|Any CPU.Build.0 = Release|Any CPU + {48960E35-903C-4EF0-A8F4-3871B000B12D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48960E35-903C-4EF0-A8F4-3871B000B12D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48960E35-903C-4EF0-A8F4-3871B000B12D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48960E35-903C-4EF0-A8F4-3871B000B12D}.Release|Any CPU.Build.0 = Release|Any CPU + {9FE7C169-F639-4F6D-86EB-D97FE3E9D206}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FE7C169-F639-4F6D-86EB-D97FE3E9D206}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FE7C169-F639-4F6D-86EB-D97FE3E9D206}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FE7C169-F639-4F6D-86EB-D97FE3E9D206}.Release|Any CPU.Build.0 = Release|Any CPU + {5D349665-3B5D-4DA4-A7B0-C6F11611B0C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D349665-3B5D-4DA4-A7B0-C6F11611B0C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D349665-3B5D-4DA4-A7B0-C6F11611B0C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D349665-3B5D-4DA4-A7B0-C6F11611B0C9}.Release|Any CPU.Build.0 = Release|Any CPU + {E5A05E1D-0F7F-4030-B434-596C76486CDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5A05E1D-0F7F-4030-B434-596C76486CDB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5A05E1D-0F7F-4030-B434-596C76486CDB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5A05E1D-0F7F-4030-B434-596C76486CDB}.Release|Any CPU.Build.0 = Release|Any CPU + {13A6BAF1-9BAD-4A3A-A61F-5E4F01CDF0BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {13A6BAF1-9BAD-4A3A-A61F-5E4F01CDF0BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13A6BAF1-9BAD-4A3A-A61F-5E4F01CDF0BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {13A6BAF1-9BAD-4A3A-A61F-5E4F01CDF0BF}.Release|Any CPU.Build.0 = Release|Any CPU + {B8F68D35-C561-4C45-881D-84142B557A54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8F68D35-C561-4C45-881D-84142B557A54}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8F68D35-C561-4C45-881D-84142B557A54}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8F68D35-C561-4C45-881D-84142B557A54}.Release|Any CPU.Build.0 = Release|Any CPU + {E27E725F-C4B0-4D6E-ADA0-DD7D014F4C6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E27E725F-C4B0-4D6E-ADA0-DD7D014F4C6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E27E725F-C4B0-4D6E-ADA0-DD7D014F4C6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E27E725F-C4B0-4D6E-ADA0-DD7D014F4C6C}.Release|Any CPU.Build.0 = Release|Any CPU + {EB1BACFB-3DF0-45D0-89AF-4571023DDE50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB1BACFB-3DF0-45D0-89AF-4571023DDE50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB1BACFB-3DF0-45D0-89AF-4571023DDE50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB1BACFB-3DF0-45D0-89AF-4571023DDE50}.Release|Any CPU.Build.0 = Release|Any CPU + {0BD81F36-D6B4-4DF1-8BAA-5F25B6779FC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BD81F36-D6B4-4DF1-8BAA-5F25B6779FC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BD81F36-D6B4-4DF1-8BAA-5F25B6779FC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BD81F36-D6B4-4DF1-8BAA-5F25B6779FC5}.Release|Any CPU.Build.0 = Release|Any CPU + {466E5047-4184-42C6-9C4D-14A5707E2F49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {466E5047-4184-42C6-9C4D-14A5707E2F49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {466E5047-4184-42C6-9C4D-14A5707E2F49}.Release|Any CPU.ActiveCfg = Release|Any CPU + {466E5047-4184-42C6-9C4D-14A5707E2F49}.Release|Any CPU.Build.0 = Release|Any CPU + {A335B51D-8219-4361-BAAF-5F5D1B74EF89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A335B51D-8219-4361-BAAF-5F5D1B74EF89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A335B51D-8219-4361-BAAF-5F5D1B74EF89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A335B51D-8219-4361-BAAF-5F5D1B74EF89}.Release|Any CPU.Build.0 = Release|Any CPU + {521365AE-A681-4E4B-87E6-B4D982CDD520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {521365AE-A681-4E4B-87E6-B4D982CDD520}.Debug|Any CPU.Build.0 = Debug|Any CPU + {521365AE-A681-4E4B-87E6-B4D982CDD520}.Release|Any CPU.ActiveCfg = Release|Any CPU + {521365AE-A681-4E4B-87E6-B4D982CDD520}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {F8604289-6D28-428E-9FA8-1480464964F0} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {6FA9ED7D-2D32-46BC-B47F-E503B2A4C0A9} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + {042022EA-62BC-44BD-A93F-11ABDB39C791} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + {A34FD350-79C5-492A-8811-6DBD80830F8B} = {0354F050-3992-4DD4-B0EE-5FBA04AC72B6} + {873BFC3E-63C2-4495-A503-5EC05DCD84E4} = {155227F0-A33B-40AA-A4B4-06F813EB921B} + {B44A9DDA-8091-490A-BC0B-2EAE4A601B09} = {873BFC3E-63C2-4495-A503-5EC05DCD84E4} + {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} = {155227F0-A33B-40AA-A4B4-06F813EB921B} + {A2E6E9A4-4C21-4022-A53E-BDF5ED0D0678} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} + {CF88276B-D1C8-4E0D-8043-0BFA54DA693E} = {F8604289-6D28-428E-9FA8-1480464964F0} + {298BA226-B766-40AE-933F-EB9AFCDE051C} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {39C6C4A2-077E-48C2-B295-DFE357EC38B2} = {298BA226-B766-40AE-933F-EB9AFCDE051C} + {9B7776E2-0014-4C87-B1F4-22FB0BC076F1} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {BC97FD3A-1351-443C-9EAE-0F28F8204A6C} = {9B7776E2-0014-4C87-B1F4-22FB0BC076F1} + {04584653-8E42-47DB-850B-7C6C37B307B7} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {8C5894A4-F358-45CF-B198-BFBCA30B77F6} = {04584653-8E42-47DB-850B-7C6C37B307B7} + {C0226CDD-DF79-4767-A4A5-3D9037535929} = {9B7776E2-0014-4C87-B1F4-22FB0BC076F1} + {4A874C29-2915-4F5C-A01A-80F56B929541} = {04584653-8E42-47DB-850B-7C6C37B307B7} + {CE121DFD-72D4-4DB7-968F-C0DB320D08FB} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {61F5C50C-C572-46AB-8F37-54D1F84CC31D} = {CE121DFD-72D4-4DB7-968F-C0DB320D08FB} + {48960E35-903C-4EF0-A8F4-3871B000B12D} = {873BFC3E-63C2-4495-A503-5EC05DCD84E4} + {9FE7C169-F639-4F6D-86EB-D97FE3E9D206} = {F8604289-6D28-428E-9FA8-1480464964F0} + {2633B8B9-4AEC-4A54-8832-1C940B54E041} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {5D349665-3B5D-4DA4-A7B0-C6F11611B0C9} = {2633B8B9-4AEC-4A54-8832-1C940B54E041} + {E5A05E1D-0F7F-4030-B434-596C76486CDB} = {04584653-8E42-47DB-850B-7C6C37B307B7} + {13A6BAF1-9BAD-4A3A-A61F-5E4F01CDF0BF} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} + {B8F68D35-C561-4C45-881D-84142B557A54} = {03A5D774-A546-4D4B-8687-C6F96A886C72} + {E27E725F-C4B0-4D6E-ADA0-DD7D014F4C6C} = {03A5D774-A546-4D4B-8687-C6F96A886C72} + {EB1BACFB-3DF0-45D0-89AF-4571023DDE50} = {03A5D774-A546-4D4B-8687-C6F96A886C72} + {0BD81F36-D6B4-4DF1-8BAA-5F25B6779FC5} = {03A5D774-A546-4D4B-8687-C6F96A886C72} + {466E5047-4184-42C6-9C4D-14A5707E2F49} = {03A5D774-A546-4D4B-8687-C6F96A886C72} + {A335B51D-8219-4361-BAAF-5F5D1B74EF89} = {298BA226-B766-40AE-933F-EB9AFCDE051C} + {521365AE-A681-4E4B-87E6-B4D982CDD520} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + EndGlobalSection +EndGlobal diff --git a/Elsa.sln b/Elsa.sln new file mode 100644 index 000000000..bb551fd90 --- /dev/null +++ b/Elsa.sln @@ -0,0 +1,202 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{61017E64-6D00-49CB-9E81-5002DC8F7D5F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{155227F0-A33B-40AA-A4B4-06F813EB921B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{C6658DE0-2B2F-47F0-BB61-2CA66D435C09}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "activities", "activities", "{F8604289-6D28-428E-9FA8-1480464964F0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Console1", "src\samples\console\Elsa.Samples.Console1\Elsa.Samples.Console1.csproj", "{B44A9DDA-8091-490A-BC0B-2EAE4A601B09}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Core", "src\core\Elsa.Core\Elsa.Core.csproj", "{6FA9ED7D-2D32-46BC-B47F-E503B2A4C0A9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{7D21EB5E-D6B4-48A5-A9BC-A26387A65F0B}" +ProjectSection(SolutionItems) = preProject + README.md = README.md + docker-compose.yml = docker-compose.yml +EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{0354F050-3992-4DD4-B0EE-5FBA04AC72B6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "architecture", "architecture", "{A34FD350-79C5-492A-8811-6DBD80830F8B}" +ProjectSection(SolutionItems) = preProject + docs\architecture\Layer1.md = docs\architecture\Layer1.md + docs\architecture\Layer2.md = docs\architecture\Layer2.md +EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "console", "console", "{873BFC3E-63C2-4495-A503-5EC05DCD84E4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "aspnet", "aspnet", "{56C2FFB8-EA54-45B5-A095-4A78142EB4B5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Web1", "src\samples\aspnet\Elsa.Samples.Web1\Elsa.Samples.Web1.csproj", "{A2E6E9A4-4C21-4022-A53E-BDF5ED0D0678}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.Http", "src\activities\Elsa.Activities.Http\Elsa.Activities.Http.csproj", "{CF88276B-D1C8-4E0D-8043-0BFA54DA693E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "api", "api", "{298BA226-B766-40AE-933F-EB9AFCDE051C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Api", "src\api\Elsa.Api\Elsa.Api.csproj", "{39C6C4A2-077E-48C2-B295-DFE357EC38B2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "persistence", "persistence", "{9B7776E2-0014-4C87-B1F4-22FB0BC076F1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Persistence.InMemory", "src\persistence\Elsa.Persistence.InMemory\Elsa.Persistence.InMemory.csproj", "{BC97FD3A-1351-443C-9EAE-0F28F8204A6C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "runtime", "runtime", "{04584653-8E42-47DB-850B-7C6C37B307B7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Runtime", "src\runtime\Elsa.Runtime\Elsa.Runtime.csproj", "{8C5894A4-F358-45CF-B198-BFBCA30B77F6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Persistence.Abstractions", "src\persistence\Elsa.Persistence.Abstractions\Elsa.Persistence.Abstractions.csproj", "{C0226CDD-DF79-4767-A4A5-3D9037535929}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dsl", "dsl", "{CE121DFD-72D4-4DB7-968F-C0DB320D08FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Dsl", "src\dsl\Elsa.Dsl\Elsa.Dsl.csproj", "{61F5C50C-C572-46AB-8F37-54D1F84CC31D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Console2", "src\samples\console\Elsa.Samples.Console2\Elsa.Samples.Console2.csproj", "{48960E35-903C-4EF0-A8F4-3871B000B12D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.Timers", "src\activities\Elsa.Activities.Timers\Elsa.Activities.Timers.csproj", "{9FE7C169-F639-4F6D-86EB-D97FE3E9D206}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripting", "scripting", "{2633B8B9-4AEC-4A54-8832-1C940B54E041}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Scripting.JavaScript", "src\scripting\Elsa.Scripting.JavaScript\Elsa.Scripting.JavaScript.csproj", "{5D349665-3B5D-4DA4-A7B0-C6F11611B0C9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Runtime.ProtoActor", "src\runtime\Elsa.Runtime.ProtoActor\Elsa.Runtime.ProtoActor.csproj", "{E5A05E1D-0F7F-4030-B434-596C76486CDB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Web2", "src\samples\aspnet\Elsa.Samples.Web2\Elsa.Samples.Web2.csproj", "{13A6BAF1-9BAD-4A3A-A61F-5E4F01CDF0BF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa", "src\core\Elsa\Elsa.csproj", "{521365AE-A681-4E4B-87E6-B4D982CDD520}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Mediator", "src\core\Elsa.Mediator\Elsa.Mediator.csproj", "{5CC45C6C-0751-4A33-8306-758BB17417F7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Management", "src\core\Elsa.Management\Elsa.Management.csproj", "{03C2D8B9-C61F-4750-8107-765E3FE02ADA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "efcore", "efcore", "{9F8AE7FB-E5F9-4DCB-9CF8-0362B3D18DAA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Persistence.EntityFrameworkCore", "src\persistence\Elsa.Persistence.EntityFrameworkCore\Elsa.Persistence.EntityFrameworkCore.csproj", "{D8130816-C1D9-40A3-9079-F2887FE35EA9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Persistence.EntityFrameworkCore.Sqlite", "src\persistence\Elsa.Persistence.EntityFrameworkCore.Sqlite\Elsa.Persistence.EntityFrameworkCore.Sqlite.csproj", "{820016B7-01CD-4032-8239-6A4E53F1A352}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Serialization", "src\core\Elsa.Serialization\Elsa.Serialization.csproj", "{55F9B33D-5FAF-4355-97EF-445C93208E85}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B44A9DDA-8091-490A-BC0B-2EAE4A601B09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B44A9DDA-8091-490A-BC0B-2EAE4A601B09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B44A9DDA-8091-490A-BC0B-2EAE4A601B09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B44A9DDA-8091-490A-BC0B-2EAE4A601B09}.Release|Any CPU.Build.0 = Release|Any CPU + {6FA9ED7D-2D32-46BC-B47F-E503B2A4C0A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6FA9ED7D-2D32-46BC-B47F-E503B2A4C0A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FA9ED7D-2D32-46BC-B47F-E503B2A4C0A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6FA9ED7D-2D32-46BC-B47F-E503B2A4C0A9}.Release|Any CPU.Build.0 = Release|Any CPU + {A2E6E9A4-4C21-4022-A53E-BDF5ED0D0678}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2E6E9A4-4C21-4022-A53E-BDF5ED0D0678}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2E6E9A4-4C21-4022-A53E-BDF5ED0D0678}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2E6E9A4-4C21-4022-A53E-BDF5ED0D0678}.Release|Any CPU.Build.0 = Release|Any CPU + {CF88276B-D1C8-4E0D-8043-0BFA54DA693E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF88276B-D1C8-4E0D-8043-0BFA54DA693E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF88276B-D1C8-4E0D-8043-0BFA54DA693E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF88276B-D1C8-4E0D-8043-0BFA54DA693E}.Release|Any CPU.Build.0 = Release|Any CPU + {39C6C4A2-077E-48C2-B295-DFE357EC38B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39C6C4A2-077E-48C2-B295-DFE357EC38B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39C6C4A2-077E-48C2-B295-DFE357EC38B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39C6C4A2-077E-48C2-B295-DFE357EC38B2}.Release|Any CPU.Build.0 = Release|Any CPU + {BC97FD3A-1351-443C-9EAE-0F28F8204A6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC97FD3A-1351-443C-9EAE-0F28F8204A6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC97FD3A-1351-443C-9EAE-0F28F8204A6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC97FD3A-1351-443C-9EAE-0F28F8204A6C}.Release|Any CPU.Build.0 = Release|Any CPU + {8C5894A4-F358-45CF-B198-BFBCA30B77F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C5894A4-F358-45CF-B198-BFBCA30B77F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C5894A4-F358-45CF-B198-BFBCA30B77F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C5894A4-F358-45CF-B198-BFBCA30B77F6}.Release|Any CPU.Build.0 = Release|Any CPU + {C0226CDD-DF79-4767-A4A5-3D9037535929}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0226CDD-DF79-4767-A4A5-3D9037535929}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0226CDD-DF79-4767-A4A5-3D9037535929}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0226CDD-DF79-4767-A4A5-3D9037535929}.Release|Any CPU.Build.0 = Release|Any CPU + {61F5C50C-C572-46AB-8F37-54D1F84CC31D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61F5C50C-C572-46AB-8F37-54D1F84CC31D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61F5C50C-C572-46AB-8F37-54D1F84CC31D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61F5C50C-C572-46AB-8F37-54D1F84CC31D}.Release|Any CPU.Build.0 = Release|Any CPU + {48960E35-903C-4EF0-A8F4-3871B000B12D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48960E35-903C-4EF0-A8F4-3871B000B12D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48960E35-903C-4EF0-A8F4-3871B000B12D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48960E35-903C-4EF0-A8F4-3871B000B12D}.Release|Any CPU.Build.0 = Release|Any CPU + {9FE7C169-F639-4F6D-86EB-D97FE3E9D206}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FE7C169-F639-4F6D-86EB-D97FE3E9D206}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FE7C169-F639-4F6D-86EB-D97FE3E9D206}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FE7C169-F639-4F6D-86EB-D97FE3E9D206}.Release|Any CPU.Build.0 = Release|Any CPU + {5D349665-3B5D-4DA4-A7B0-C6F11611B0C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D349665-3B5D-4DA4-A7B0-C6F11611B0C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D349665-3B5D-4DA4-A7B0-C6F11611B0C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D349665-3B5D-4DA4-A7B0-C6F11611B0C9}.Release|Any CPU.Build.0 = Release|Any CPU + {E5A05E1D-0F7F-4030-B434-596C76486CDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5A05E1D-0F7F-4030-B434-596C76486CDB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5A05E1D-0F7F-4030-B434-596C76486CDB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5A05E1D-0F7F-4030-B434-596C76486CDB}.Release|Any CPU.Build.0 = Release|Any CPU + {13A6BAF1-9BAD-4A3A-A61F-5E4F01CDF0BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {13A6BAF1-9BAD-4A3A-A61F-5E4F01CDF0BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13A6BAF1-9BAD-4A3A-A61F-5E4F01CDF0BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {13A6BAF1-9BAD-4A3A-A61F-5E4F01CDF0BF}.Release|Any CPU.Build.0 = Release|Any CPU + {521365AE-A681-4E4B-87E6-B4D982CDD520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {521365AE-A681-4E4B-87E6-B4D982CDD520}.Debug|Any CPU.Build.0 = Debug|Any CPU + {521365AE-A681-4E4B-87E6-B4D982CDD520}.Release|Any CPU.ActiveCfg = Release|Any CPU + {521365AE-A681-4E4B-87E6-B4D982CDD520}.Release|Any CPU.Build.0 = Release|Any CPU + {5CC45C6C-0751-4A33-8306-758BB17417F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5CC45C6C-0751-4A33-8306-758BB17417F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5CC45C6C-0751-4A33-8306-758BB17417F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5CC45C6C-0751-4A33-8306-758BB17417F7}.Release|Any CPU.Build.0 = Release|Any CPU + {03C2D8B9-C61F-4750-8107-765E3FE02ADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03C2D8B9-C61F-4750-8107-765E3FE02ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03C2D8B9-C61F-4750-8107-765E3FE02ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03C2D8B9-C61F-4750-8107-765E3FE02ADA}.Release|Any CPU.Build.0 = Release|Any CPU + {D8130816-C1D9-40A3-9079-F2887FE35EA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8130816-C1D9-40A3-9079-F2887FE35EA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8130816-C1D9-40A3-9079-F2887FE35EA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8130816-C1D9-40A3-9079-F2887FE35EA9}.Release|Any CPU.Build.0 = Release|Any CPU + {820016B7-01CD-4032-8239-6A4E53F1A352}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {820016B7-01CD-4032-8239-6A4E53F1A352}.Debug|Any CPU.Build.0 = Debug|Any CPU + {820016B7-01CD-4032-8239-6A4E53F1A352}.Release|Any CPU.ActiveCfg = Release|Any CPU + {820016B7-01CD-4032-8239-6A4E53F1A352}.Release|Any CPU.Build.0 = Release|Any CPU + {55F9B33D-5FAF-4355-97EF-445C93208E85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55F9B33D-5FAF-4355-97EF-445C93208E85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55F9B33D-5FAF-4355-97EF-445C93208E85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55F9B33D-5FAF-4355-97EF-445C93208E85}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {F8604289-6D28-428E-9FA8-1480464964F0} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {6FA9ED7D-2D32-46BC-B47F-E503B2A4C0A9} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + {A34FD350-79C5-492A-8811-6DBD80830F8B} = {0354F050-3992-4DD4-B0EE-5FBA04AC72B6} + {873BFC3E-63C2-4495-A503-5EC05DCD84E4} = {155227F0-A33B-40AA-A4B4-06F813EB921B} + {B44A9DDA-8091-490A-BC0B-2EAE4A601B09} = {873BFC3E-63C2-4495-A503-5EC05DCD84E4} + {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} = {155227F0-A33B-40AA-A4B4-06F813EB921B} + {A2E6E9A4-4C21-4022-A53E-BDF5ED0D0678} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} + {CF88276B-D1C8-4E0D-8043-0BFA54DA693E} = {F8604289-6D28-428E-9FA8-1480464964F0} + {298BA226-B766-40AE-933F-EB9AFCDE051C} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {39C6C4A2-077E-48C2-B295-DFE357EC38B2} = {298BA226-B766-40AE-933F-EB9AFCDE051C} + {9B7776E2-0014-4C87-B1F4-22FB0BC076F1} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {BC97FD3A-1351-443C-9EAE-0F28F8204A6C} = {9B7776E2-0014-4C87-B1F4-22FB0BC076F1} + {04584653-8E42-47DB-850B-7C6C37B307B7} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {8C5894A4-F358-45CF-B198-BFBCA30B77F6} = {04584653-8E42-47DB-850B-7C6C37B307B7} + {C0226CDD-DF79-4767-A4A5-3D9037535929} = {9B7776E2-0014-4C87-B1F4-22FB0BC076F1} + {CE121DFD-72D4-4DB7-968F-C0DB320D08FB} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {61F5C50C-C572-46AB-8F37-54D1F84CC31D} = {CE121DFD-72D4-4DB7-968F-C0DB320D08FB} + {48960E35-903C-4EF0-A8F4-3871B000B12D} = {873BFC3E-63C2-4495-A503-5EC05DCD84E4} + {9FE7C169-F639-4F6D-86EB-D97FE3E9D206} = {F8604289-6D28-428E-9FA8-1480464964F0} + {2633B8B9-4AEC-4A54-8832-1C940B54E041} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {5D349665-3B5D-4DA4-A7B0-C6F11611B0C9} = {2633B8B9-4AEC-4A54-8832-1C940B54E041} + {E5A05E1D-0F7F-4030-B434-596C76486CDB} = {04584653-8E42-47DB-850B-7C6C37B307B7} + {13A6BAF1-9BAD-4A3A-A61F-5E4F01CDF0BF} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} + {521365AE-A681-4E4B-87E6-B4D982CDD520} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + {5CC45C6C-0751-4A33-8306-758BB17417F7} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + {03C2D8B9-C61F-4750-8107-765E3FE02ADA} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + {9F8AE7FB-E5F9-4DCB-9CF8-0362B3D18DAA} = {9B7776E2-0014-4C87-B1F4-22FB0BC076F1} + {D8130816-C1D9-40A3-9079-F2887FE35EA9} = {9F8AE7FB-E5F9-4DCB-9CF8-0362B3D18DAA} + {820016B7-01CD-4032-8239-6A4E53F1A352} = {9F8AE7FB-E5F9-4DCB-9CF8-0362B3D18DAA} + {55F9B33D-5FAF-4355-97EF-445C93208E85} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + EndGlobalSection +EndGlobal diff --git a/Elsa.sln.DotSettings b/Elsa.sln.DotSettings new file mode 100644 index 000000000..05eadb4ff --- /dev/null +++ b/Elsa.sln.DotSettings @@ -0,0 +1,9 @@ + + True + True + True + True + True + True + True + True \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..ed7211fb7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Elsa Workflows + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 000000000..b879d3722 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..1b8912adf --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# experimental \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..3c1e24fac --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,48 @@ +version: '3.7' +services: + + consul-agent-1: &consul-agent + image: consul:latest + networks: + - consul + command: "agent -retry-join consul-server-bootstrap -client 0.0.0.0" + + consul-agent-2: + <<: *consul-agent + + consul-agent-3: + <<: *consul-agent + + consul-server-1: &consul-server + <<: *consul-agent + command: "agent -server -retry-join consul-server-bootstrap -client 0.0.0.0" + + consul-server-2: + <<: *consul-server + + consul-server-bootstrap: + <<: *consul-agent + ports: + - "8400:8400" + - "8500:8500" + - "8600:8600" + - "8600:8600/udp" + command: "agent -server -bootstrap-expect 3 -ui -client 0.0.0.0" + + mongodb: + image: mongo:latest + ports: + - 127.0.0.1:27017:27017 + volumes: + - mongodb_data:/data/db + + redis: + image: redis:latest + ports: + - 127.0.0.1:6379:6379 + +networks: + consul: + +volumes: + mongodb_data: \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Http/Elsa.Activities.Http.csproj b/src/activities/Elsa.Activities.Http/Elsa.Activities.Http.csproj new file mode 100644 index 000000000..2f8065e2f --- /dev/null +++ b/src/activities/Elsa.Activities.Http/Elsa.Activities.Http.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + Latest + + + + + + + + + + + + diff --git a/src/activities/Elsa.Activities.Http/Elsa.Activities.Http.csproj.DotSettings b/src/activities/Elsa.Activities.Http/Elsa.Activities.Http.csproj.DotSettings new file mode 100644 index 000000000..875c6b6a6 --- /dev/null +++ b/src/activities/Elsa.Activities.Http/Elsa.Activities.Http.csproj.DotSettings @@ -0,0 +1,4 @@ + + True + True + True \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Http/Extensions/ApplicationBuilderExtensions.cs b/src/activities/Elsa.Activities.Http/Extensions/ApplicationBuilderExtensions.cs new file mode 100644 index 000000000..dd92bfc4c --- /dev/null +++ b/src/activities/Elsa.Activities.Http/Extensions/ApplicationBuilderExtensions.cs @@ -0,0 +1,9 @@ +using Elsa.Activities.Http.Middleware; +using Microsoft.AspNetCore.Builder; + +namespace Elsa.Activities.Http.Extensions; + +public static class ApplicationBuilderExtensions +{ + public static IApplicationBuilder UseHttpActivities(this IApplicationBuilder app) => app.UseMiddleware(); +} \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs b/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..58f9f60bc --- /dev/null +++ b/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,13 @@ + + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.DependencyInjection; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddHttpActivityServices(this IServiceCollection services) + { + return services + .AddHttpContextAccessor(); + } +} \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Http/HttpTrigger.cs b/src/activities/Elsa.Activities.Http/HttpTrigger.cs new file mode 100644 index 000000000..117ca9a91 --- /dev/null +++ b/src/activities/Elsa.Activities.Http/HttpTrigger.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using Elsa.Attributes; +using Elsa.Contracts; +using Elsa.Management.Models; +using Elsa.Models; + +namespace Elsa.Activities.Http; + +public class HttpTrigger : Trigger +{ + [Input] public Input Path { get; set; } = default!; + + [Input( + Options = new[] { "GET", "POST", "PUT" }, + UIHint = InputUIHints.CheckList + )] + public Input> SupportedMethods { get; set; } = new(new[] { HttpMethod.Get.Method }); + + [Output] public Output? Result { get; set; } + + protected override IEnumerable GetHashInputs(TriggerIndexingContext context) + { + var path = context.ExpressionExecutionContext.Get(Path); + var methods = context.ExpressionExecutionContext.Get(SupportedMethods); + return methods!.Select(x => (path!.ToLowerInvariant(), x.ToLowerInvariant())).Cast().ToArray(); + } + + protected override void Execute(ActivityExecutionContext context) + { + var bookmarks = CreateBookmarks(context).ToList(); + context.SetBookmarks(bookmarks); + } + + private IEnumerable CreateBookmarks(ActivityExecutionContext context) + { + var path = context.Get(Path)!; + var methods = context.Get(SupportedMethods)!; + var hasher = context.GetRequiredService(); + var identityGenerator = context.GetRequiredService(); + + foreach (var method in methods) + { + var hashInput = (path.ToLowerInvariant(), method.ToLowerInvariant()); + var hash = hasher.Hash(hashInput); + var bookmarkId = identityGenerator.GenerateId(); + yield return new Bookmark(bookmarkId, NodeType, hash, Id, context.Id); + } + } +} \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Http/Middleware/HttpTriggerMiddleware.cs b/src/activities/Elsa.Activities.Http/Middleware/HttpTriggerMiddleware.cs new file mode 100644 index 000000000..d4a1510ff --- /dev/null +++ b/src/activities/Elsa.Activities.Http/Middleware/HttpTriggerMiddleware.cs @@ -0,0 +1,57 @@ +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Elsa.Contracts; +using Elsa.Helpers; +using Elsa.Runtime.Contracts; +using Elsa.Runtime.Models; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Activities.Http.Middleware; + +public class HttpTriggerMiddleware +{ + private readonly RequestDelegate _next; + private readonly IHasher _hasher; + + public HttpTriggerMiddleware(RequestDelegate next, IHasher hasher) + { + _next = next; + _hasher = hasher; + } + + public async Task InvokeAsync(HttpContext httpContext, IWorkflowServer workflowServer) + { + var path = GetPath(httpContext); + var method = httpContext.Request.Method!.ToLowerInvariant(); + var abortToken = httpContext.RequestAborted; + var hash = _hasher.Hash((path.ToLowerInvariant(), method.ToLowerInvariant())); + var activityTypeName = TypeNameHelper.GenerateTypeName(); + var stimulus = Stimuli.Standard(activityTypeName, hash); + var executionResults = (await workflowServer.ExecuteStimulusAsync(stimulus, abortToken)).ToList(); + + if (!executionResults.Any()) + { + await _next(httpContext); + return; + } + + var response = httpContext.Response; + + if (!response.HasStarted) + { + response.ContentType = "application/json"; + response.StatusCode = StatusCodes.Status200OK; + + var model = new + { + workflowInstanceIds = executionResults.Select(x => x.ExecuteWorkflowResult.WorkflowState.Id).ToArray() + }; + + var json = JsonSerializer.Serialize(model); + await response.WriteAsync(json, abortToken); + } + } + + private string GetPath(HttpContext httpContext) => httpContext.Request.Path.Value.ToLowerInvariant(); +} \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Http/Models/HttpRequestModel.cs b/src/activities/Elsa.Activities.Http/Models/HttpRequestModel.cs new file mode 100644 index 000000000..201589e57 --- /dev/null +++ b/src/activities/Elsa.Activities.Http/Models/HttpRequestModel.cs @@ -0,0 +1,8 @@ +using System; + +namespace Elsa.Activities.Http; + +public class HttpRequestModel +{ + public Uri Url { get; set; } = default!; +} \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Http/WriteHttpResponse.cs b/src/activities/Elsa.Activities.Http/WriteHttpResponse.cs new file mode 100644 index 000000000..4d41bf7c2 --- /dev/null +++ b/src/activities/Elsa.Activities.Http/WriteHttpResponse.cs @@ -0,0 +1,26 @@ +using System.Net; +using System.Threading.Tasks; +using Elsa.Models; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Activities.Http; + +public class WriteHttpResponse : Activity +{ + public Input StatusCode { get; set; } = new(HttpStatusCode.OK); + public Input Content { get; set; } = new(""); + + public override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var httpContextAccessor = context.GetRequiredService(); + var httpContext = httpContextAccessor.HttpContext; + var response = httpContext.Response; + + response.StatusCode = (int)context.Get(StatusCode); + + var content = context.Get(Content); + + if (content != null) + await response.WriteAsync(content, context.CancellationToken); + } +} \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Timers/AssemblyInfo.cs b/src/activities/Elsa.Activities.Timers/AssemblyInfo.cs new file mode 100644 index 000000000..e46bb2692 --- /dev/null +++ b/src/activities/Elsa.Activities.Timers/AssemblyInfo.cs @@ -0,0 +1,2 @@ +using System; +[assembly: CLSCompliant(true)] \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Timers/Elsa.Activities.Timers.csproj b/src/activities/Elsa.Activities.Timers/Elsa.Activities.Timers.csproj new file mode 100644 index 000000000..6e698355e --- /dev/null +++ b/src/activities/Elsa.Activities.Timers/Elsa.Activities.Timers.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + Latest + + + + + + + diff --git a/src/activities/Elsa.Activities.Timers/TimerTrigger.cs b/src/activities/Elsa.Activities.Timers/TimerTrigger.cs new file mode 100644 index 000000000..be639d28a --- /dev/null +++ b/src/activities/Elsa.Activities.Timers/TimerTrigger.cs @@ -0,0 +1,10 @@ +using System; +using Elsa.Attributes; +using Elsa.Models; + +namespace Elsa.Activities.Timers; + +public class TimerTrigger : Trigger +{ + [Input] public Input Interval { get; set; } = default!; +} \ No newline at end of file diff --git a/src/api/Elsa.Api/ApiResults/DispatchWorkflowResult.cs b/src/api/Elsa.Api/ApiResults/DispatchWorkflowResult.cs new file mode 100644 index 000000000..3998ce585 --- /dev/null +++ b/src/api/Elsa.Api/ApiResults/DispatchWorkflowResult.cs @@ -0,0 +1,26 @@ +using System.Net; +using System.Threading.Tasks; +using Elsa.Models; +using Elsa.Runtime.Contracts; +using Elsa.Runtime.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Api.ApiResults; + +public class DispatchWorkflowResult : IResult +{ + public DispatchWorkflowResult(Workflow workflow) => Workflow = workflow; + public Workflow Workflow { get; } + + public async Task ExecuteAsync(HttpContext httpContext) + { + var response = httpContext.Response; + var workflowInvoker = httpContext.RequestServices.GetRequiredService(); + var (definitionId, version, _) = Workflow.Identity; + var result = await workflowInvoker.DispatchAsync(new DispatchWorkflowDefinitionRequest(definitionId, version)); + + response.StatusCode = (int)HttpStatusCode.OK; + await response.WriteAsJsonAsync(result, httpContext.RequestAborted); + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/ApiResults/EmptyResult.cs b/src/api/Elsa.Api/ApiResults/EmptyResult.cs new file mode 100644 index 000000000..9c59a2559 --- /dev/null +++ b/src/api/Elsa.Api/ApiResults/EmptyResult.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Api.ApiResults; + +public class EmptyResult : IResult +{ + public Task ExecuteAsync(HttpContext httpContext) => Task.CompletedTask; +} \ No newline at end of file diff --git a/src/api/Elsa.Api/ApiResults/ExecuteWorkflowResult.cs b/src/api/Elsa.Api/ApiResults/ExecuteWorkflowResult.cs new file mode 100644 index 000000000..0dc64c2ab --- /dev/null +++ b/src/api/Elsa.Api/ApiResults/ExecuteWorkflowResult.cs @@ -0,0 +1,27 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Models; +using Elsa.Runtime.Contracts; +using Elsa.Runtime.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Api.ApiResults; + +public class ExecuteWorkflowResult : IResult +{ + public ExecuteWorkflowResult(Workflow workflow) => Workflow = workflow; + public Workflow Workflow { get; } + + public async Task ExecuteAsync(HttpContext httpContext) + { + var response = httpContext.Response; + var workflowInvoker = httpContext.RequestServices.GetRequiredService(); + var (definitionId, version, _) = Workflow.Identity; + var executeRequest = new ExecuteWorkflowDefinitionRequest(definitionId, version); + var result = await workflowInvoker.ExecuteAsync(executeRequest, CancellationToken.None); + + if (!response.HasStarted) + await response.WriteAsJsonAsync(result, httpContext.RequestAborted); + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/ApiResults/ProcessStimulusResult.cs b/src/api/Elsa.Api/ApiResults/ProcessStimulusResult.cs new file mode 100644 index 000000000..52a94e04b --- /dev/null +++ b/src/api/Elsa.Api/ApiResults/ProcessStimulusResult.cs @@ -0,0 +1,38 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Runtime.Contracts; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Api.ApiResults; + +public class ProcessStimulusResult : IResult +{ + public ProcessStimulusResult(IStimulus stimulus) + { + Stimulus = stimulus; + } + + public IStimulus Stimulus { get; } + + public async Task ExecuteAsync(HttpContext httpContext) + { + var response = httpContext.Response; + var stimulusInterpreter = httpContext.RequestServices.GetRequiredService(); + var abortToken = httpContext.RequestAborted; + var instructions = await stimulusInterpreter.GetExecutionInstructionsAsync(Stimulus, abortToken); + var instructionExecutor = httpContext.RequestServices.GetRequiredService(); + var executionResults = (await instructionExecutor.ExecuteInstructionsAsync(instructions, CancellationToken.None)).ToList(); + + if (!response.HasStarted) + { + var model = new + { + Results = executionResults + }; + + await response.WriteAsJsonAsync(model, httpContext.RequestAborted); + } + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Elsa.Api.csproj b/src/api/Elsa.Api/Elsa.Api.csproj new file mode 100644 index 000000000..092f8af5b --- /dev/null +++ b/src/api/Elsa.Api/Elsa.Api.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + preview + + + + + + + + + + + + + + + + diff --git a/src/api/Elsa.Api/Endpoints/ActivityDescriptors/List.cs b/src/api/Elsa.Api/Endpoints/ActivityDescriptors/List.cs new file mode 100644 index 000000000..e000b5c99 --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/ActivityDescriptors/List.cs @@ -0,0 +1,28 @@ +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Management.Contracts; +using Elsa.Management.Serialization.Converters; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Api.Endpoints.ActivityDescriptors; + +public static partial class ActivityDescriptors +{ + public static IResult ListAsync(IActivityRegistry registry, IWellKnownTypeRegistry wellKnownTypeRegistry) + { + var descriptors = registry.ListAll().ToList(); + + var model = new + { + ActivityDescriptors = descriptors + }; + + return Results.Json(model, new JsonSerializerOptions + { + Converters = { new TypeJsonConverter(wellKnownTypeRegistry) }, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault + }); + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/ActivityDescriptors/Resource.cs b/src/api/Elsa.Api/Endpoints/ActivityDescriptors/Resource.cs new file mode 100644 index 000000000..3531d6a0d --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/ActivityDescriptors/Resource.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; + +namespace Elsa.Api.Endpoints.ActivityDescriptors; + +public class ActivityDescriptorsResource +{ + private readonly IEndpointRouteBuilder _endpoints; + + public ActivityDescriptorsResource(IEndpointRouteBuilder endpoints) + { + _endpoints = endpoints; + } + + public ActivityDescriptorsResource List() + { + _endpoints.MapGet("api/descriptors/activities", ActivityDescriptors.ListAsync); + return this; + } +} + +public static class ActivityDescriptorsResourceExtensions +{ + public static IEndpointRouteBuilder MapActivityDescriptors(this IEndpointRouteBuilder endpoints, Action configureResource) + { + configureResource(new(endpoints)); + return endpoints; + } + + public static IEndpointRouteBuilder MapActivityDescriptors(this IEndpointRouteBuilder endpoints) => endpoints.MapActivityDescriptors(x => x.List()); +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/Events/Resource.cs b/src/api/Elsa.Api/Endpoints/Events/Resource.cs new file mode 100644 index 000000000..5089bf761 --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/Events/Resource.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; + +namespace Elsa.Api.Endpoints.Events; + +public class EventsResource +{ + private readonly IEndpointRouteBuilder _endpoints; + + public EventsResource(IEndpointRouteBuilder endpoints) + { + _endpoints = endpoints; + } + + public EventsResource Trigger() + { + _endpoints.MapPost("api/events/{eventName}/trigger", Events.Trigger.Handle); + return this; + } +} + +public static class EventsResourceExtensions +{ + public static IEndpointRouteBuilder MapEvents(this IEndpointRouteBuilder endpoints, Action configureResource) + { + configureResource(new(endpoints)); + return endpoints; + } + + public static IEndpointRouteBuilder MapEvents(this IEndpointRouteBuilder endpoints) => endpoints.MapEvents(x => x.Trigger()); +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/Events/Trigger.cs b/src/api/Elsa.Api/Endpoints/Events/Trigger.cs new file mode 100644 index 000000000..1b1db978a --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/Events/Trigger.cs @@ -0,0 +1,17 @@ +using Elsa.Activities.Primitives; +using Elsa.Api.ApiResults; +using Elsa.Contracts; +using Elsa.Runtime.Models; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Api.Endpoints.Events; + +public static class Trigger +{ + public static IResult Handle(string eventName, IHasher hasher) + { + var hash = hasher.Hash(eventName); + var stimulus = Stimuli.Standard(nameof(Event), hash); + return new ProcessStimulusResult(stimulus); + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/TriggerDescriptors/List.cs b/src/api/Elsa.Api/Endpoints/TriggerDescriptors/List.cs new file mode 100644 index 000000000..5cf232411 --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/TriggerDescriptors/List.cs @@ -0,0 +1,28 @@ +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Management.Contracts; +using Elsa.Management.Serialization.Converters; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Api.Endpoints.TriggerDescriptors; + +public static partial class TriggerDescriptors +{ + public static IResult ListAsync(ITriggerRegistry registry, IWellKnownTypeRegistry wellKnownTypeRegistry) + { + var descriptors = registry.ListAll().ToList(); + + var model = new + { + TriggerDescriptors = descriptors + }; + + return Results.Json(model, new JsonSerializerOptions + { + Converters = { new TypeJsonConverter(wellKnownTypeRegistry) }, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault + }); + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/TriggerDescriptors/Resource.cs b/src/api/Elsa.Api/Endpoints/TriggerDescriptors/Resource.cs new file mode 100644 index 000000000..38b65aeb2 --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/TriggerDescriptors/Resource.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; + +namespace Elsa.Api.Endpoints.TriggerDescriptors; + +public class TriggerDescriptorsResource +{ + private readonly IEndpointRouteBuilder _endpoints; + + public TriggerDescriptorsResource(IEndpointRouteBuilder endpoints) + { + _endpoints = endpoints; + } + + public TriggerDescriptorsResource List() + { + _endpoints.MapGet("api/descriptors/triggers", TriggerDescriptors.ListAsync); + return this; + } +} + +public static class TriggerDescriptorsResourceExtensions +{ + public static IEndpointRouteBuilder MapTriggerDescriptors(this IEndpointRouteBuilder endpoints, Action configureResource) + { + configureResource(new(endpoints)); + return endpoints; + } + + public static IEndpointRouteBuilder MapTriggerDescriptors(this IEndpointRouteBuilder endpoints) => endpoints.MapTriggerDescriptors(x => x.List()); +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/WorkflowInstances/Get.cs b/src/api/Elsa.Api/Endpoints/WorkflowInstances/Get.cs new file mode 100644 index 000000000..aa61cf7ea --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/WorkflowInstances/Get.cs @@ -0,0 +1,19 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Management.Serialization; +using Elsa.Mediator.Contracts; +using Elsa.Persistence.Requests; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Api.Endpoints.WorkflowInstances; + +public static partial class WorkflowInstances +{ + public static async Task GetAsync(IRequestSender requestSender, WorkflowSerializerOptionsProvider serializerOptionsProvider, string id, CancellationToken cancellationToken) + { + var serializerOptions = serializerOptionsProvider.CreateSerializerOptions(); + var request = new FindWorkflowInstance(id); + var workflowInstance = await requestSender.RequestAsync(request, cancellationToken); + return workflowInstance != null ? Results.Json(workflowInstance, serializerOptions, statusCode: StatusCodes.Status200OK) : Results.NotFound(); + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/WorkflowInstances/List.cs b/src/api/Elsa.Api/Endpoints/WorkflowInstances/List.cs new file mode 100644 index 000000000..655ace87b --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/WorkflowInstances/List.cs @@ -0,0 +1,36 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Management.Serialization; +using Elsa.Mediator.Contracts; +using Elsa.Persistence.Entities; +using Elsa.Persistence.Requests; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Elsa.Api.Endpoints.WorkflowInstances; + +public static partial class WorkflowInstances +{ + public static async Task ListAsync( + IRequestSender requestSender, + WorkflowSerializerOptionsProvider serializerOptionsProvider, + CancellationToken cancellationToken, + [FromQuery] int? page, + [FromQuery] int? pageSize, + [FromQuery] string? searchTerm, + [FromQuery] string? definitionId, + [FromQuery] string? correlationId, + [FromQuery] int? version, + [FromQuery] WorkflowStatus? workflowStatus, + [FromQuery] OrderBy? orderBy, + [FromQuery] OrderDirection? orderDirection) + { + var serializerOptions = serializerOptionsProvider.CreateSerializerOptions(); + var skip = page * pageSize; + var take = pageSize; + var request = new ListWorkflowInstanceSummaries(searchTerm, definitionId, version, correlationId, workflowStatus, orderBy ?? OrderBy.Created, orderDirection ?? OrderDirection.Ascending, skip ?? 0, take ?? 50); + var summaries = await requestSender.RequestAsync(request, cancellationToken); + + return Results.Json(summaries, serializerOptions, statusCode: StatusCodes.Status200OK); + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/WorkflowInstances/Resource.cs b/src/api/Elsa.Api/Endpoints/WorkflowInstances/Resource.cs new file mode 100644 index 000000000..9faa62674 --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/WorkflowInstances/Resource.cs @@ -0,0 +1,28 @@ +using System; +using Microsoft.AspNetCore.Routing; + +namespace Elsa.Api.Endpoints.WorkflowInstances; + +public class WorkflowInstancesResource : ResourceBase +{ + public WorkflowInstancesResource(IEndpointRouteBuilder endpoints) : base(endpoints) + { + } + + public WorkflowInstancesResource List() => MapGet("api/workflow-instances", WorkflowInstances.ListAsync); + public WorkflowInstancesResource Get() => MapGet("api/workflow-instances/{id}", WorkflowInstances.GetAsync); +} + +public static class WorkflowsResourceExtensions +{ + public static IEndpointRouteBuilder MapWorkflowInstances(this IEndpointRouteBuilder endpoints, Action configureResource) + { + configureResource(new(endpoints)); + return endpoints; + } + + public static IEndpointRouteBuilder MapWorkflowInstances(this IEndpointRouteBuilder endpoints) => endpoints.MapWorkflowInstances(x => x + .List() + .Get() + ); +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/Workflows/Dispatch.cs b/src/api/Elsa.Api/Endpoints/Workflows/Dispatch.cs new file mode 100644 index 000000000..e1be129c9 --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/Workflows/Dispatch.cs @@ -0,0 +1,17 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Api.ApiResults; +using Elsa.Persistence.Models; +using Elsa.Runtime.Contracts; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Api.Endpoints.Workflows; + +public static partial class Workflows +{ + public static async Task DispatchAsync(string definitionId, IWorkflowRegistry workflowRegistry, HttpResponse response, CancellationToken cancellationToken) + { + var workflow = await workflowRegistry.FindByIdAsync(definitionId, VersionOptions.Published, cancellationToken); + return workflow == null ? Results.NotFound() : new DispatchWorkflowResult(workflow); + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/Workflows/Execute.cs b/src/api/Elsa.Api/Endpoints/Workflows/Execute.cs new file mode 100644 index 000000000..355566f20 --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/Workflows/Execute.cs @@ -0,0 +1,17 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Api.ApiResults; +using Elsa.Persistence.Models; +using Elsa.Runtime.Contracts; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Api.Endpoints.Workflows; + +public static partial class Workflows +{ + public static async Task ExecuteAsync(string definitionId, IWorkflowRegistry workflowRegistry, HttpResponse response, CancellationToken cancellationToken) + { + var workflow = await workflowRegistry.FindByIdAsync(definitionId, VersionOptions.Published, cancellationToken); + return workflow == null ? Results.NotFound() : new ExecuteWorkflowResult(workflow); + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/Workflows/Get.cs b/src/api/Elsa.Api/Endpoints/Workflows/Get.cs new file mode 100644 index 000000000..4b382021b --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/Workflows/Get.cs @@ -0,0 +1,26 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Management.Serialization; +using Elsa.Mediator.Contracts; +using Elsa.Persistence.Models; +using Elsa.Persistence.Requests; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Elsa.Api.Endpoints.Workflows; + +public static partial class Workflows +{ + public static async Task Get( + IRequestSender requestSender, + WorkflowSerializerOptionsProvider serializerOptionsProvider, + CancellationToken cancellationToken, + [FromRoute] string definitionId, + [FromQuery] string? versionOptions = default) + { + var serializerOptions = serializerOptionsProvider.CreateSerializerOptions(); + var parsedVersionOptions = versionOptions != null ? VersionOptions.FromString(versionOptions) : VersionOptions.Latest; + var workflow = await requestSender.RequestAsync(new FindWorkflow(definitionId, parsedVersionOptions), cancellationToken); + return workflow == null ? Results.NotFound() : Results.Json(workflow, serializerOptions, statusCode: StatusCodes.Status200OK); + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/Workflows/GetMany.cs b/src/api/Elsa.Api/Endpoints/Workflows/GetMany.cs new file mode 100644 index 000000000..bdb141532 --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/Workflows/GetMany.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Management.Serialization; +using Elsa.Mediator.Contracts; +using Elsa.Persistence.Models; +using Elsa.Persistence.Requests; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Elsa.Api.Endpoints.Workflows; + +public static partial class Workflows +{ + public static async Task GetManyAsync( + IRequestSender requestSender, + WorkflowSerializerOptionsProvider serializerOptionsProvider, + CancellationToken cancellationToken, + [FromQuery] string? definitionIds = default, + [FromQuery] string? versionOptions = default) + { + var serializerOptions = serializerOptionsProvider.CreateSerializerOptions(); + var splitIds = definitionIds?.Split(',', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty(); + var parsedVersionOptions = versionOptions != null ? VersionOptions.FromString(versionOptions) : VersionOptions.Latest; + var workflowSummaries = await requestSender.RequestAsync(new FindManyWorkflowDefinitions(splitIds, parsedVersionOptions), cancellationToken); + + return Results.Json(workflowSummaries, serializerOptions, statusCode: StatusCodes.Status200OK); + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/Workflows/List.cs b/src/api/Elsa.Api/Endpoints/Workflows/List.cs new file mode 100644 index 000000000..01e0eb55e --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/Workflows/List.cs @@ -0,0 +1,30 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Management.Serialization; +using Elsa.Mediator.Contracts; +using Elsa.Persistence.Models; +using Elsa.Persistence.Requests; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Elsa.Api.Endpoints.Workflows; + +public static partial class Workflows +{ + public static async Task ListAsync( + IRequestSender requestSender, + WorkflowSerializerOptionsProvider serializerOptionsProvider, + CancellationToken cancellationToken, + [FromQuery] string? versionOptions, + [FromQuery] int? page, + [FromQuery] int? pageSize) + { + var serializerOptions = serializerOptionsProvider.CreateSerializerOptions(); + var skip = page * pageSize; + var take = pageSize; + var parsedVersionOptions = versionOptions != null ? VersionOptions.FromString(versionOptions) : default; + var workflowSummaries = await requestSender.RequestAsync(new ListWorkflowSummaries(parsedVersionOptions, skip, take), cancellationToken); + + return Results.Json(workflowSummaries, serializerOptions, statusCode: StatusCodes.Status200OK); + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/Workflows/Post.cs b/src/api/Elsa.Api/Endpoints/Workflows/Post.cs new file mode 100644 index 000000000..31ff0c0cf --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/Workflows/Post.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Activities.Workflows; +using Elsa.Contracts; +using Elsa.Management.Contracts; +using Elsa.Management.Serialization; +using Elsa.Persistence.Mappers; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Api.Endpoints.Workflows; + +public static partial class Workflows +{ + public record SaveWorkflowRequest( + string? DefinitionId, + string? Name, + string? Description, + IActivity? Root, + ICollection? Triggers, + bool Publish); + + public static async Task PostAsync( + HttpContext httpContext, + WorkflowSerializerOptionsProvider serializerOptionsProvider, + IWorkflowPublisher workflowPublisher, + WorkflowDefinitionMapper mapper, + CancellationToken cancellationToken) + { + var serializerOptions = serializerOptionsProvider.CreateSerializerOptions(); + var model = (await httpContext.Request.ReadFromJsonAsync(serializerOptions, cancellationToken))!; + var definitionId = model.DefinitionId; + + // Get a workflow draft version. + var draft = !string.IsNullOrWhiteSpace(definitionId) + ? await workflowPublisher.GetDraftAsync(definitionId, cancellationToken) + : default; + + var isNew = draft == null; + + // Create a new workflow in case no existing definition was found. + if (draft == null) + { + draft = workflowPublisher.New(); + + if (!string.IsNullOrWhiteSpace(definitionId)) + draft = draft.WithDefinitionId(definitionId); + } + + // Update the draft with the received model. + var root = model.Root ?? new Sequence(); + var triggers = model.Triggers ?? new List(); + + draft = draft with + { + Root = root, + Triggers = triggers, + Metadata = draft.Metadata with + { + Name = model.Name, + Description = model.Description + } + }; + + draft = model.Publish ? await workflowPublisher.PublishAsync(draft, cancellationToken) : await workflowPublisher.SaveDraftAsync(draft, cancellationToken); + var statusCode = isNew ? StatusCodes.Status201Created : StatusCodes.Status200OK; + + return Results.Json(draft, serializerOptions, statusCode: statusCode); + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Endpoints/Workflows/Resource.cs b/src/api/Elsa.Api/Endpoints/Workflows/Resource.cs new file mode 100644 index 000000000..7a69d60f4 --- /dev/null +++ b/src/api/Elsa.Api/Endpoints/Workflows/Resource.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.AspNetCore.Routing; + +namespace Elsa.Api.Endpoints.Workflows; + +public class WorkflowsResource : ResourceBase +{ + public WorkflowsResource(IEndpointRouteBuilder endpoints) : base(endpoints) + { + } + + public WorkflowsResource Post() => MapPost("api/workflows", Workflows.PostAsync); + public WorkflowsResource List() => MapGet("api/workflows", Workflows.ListAsync); + public WorkflowsResource GetMany() => MapGet("api/workflows/set", Workflows.GetManyAsync); + public WorkflowsResource Get() => MapGet("api/workflows/{definitionId}", Workflows.Get); + public WorkflowsResource Execute() => MapPost("api/workflows/{definitionId}/execute", Workflows.ExecuteAsync); + public WorkflowsResource Dispatch() => MapPost("api/workflows/{definitionId}/dispatch", Workflows.DispatchAsync); +} + +public static class WorkflowsResourceExtensions +{ + public static IEndpointRouteBuilder MapWorkflows(this IEndpointRouteBuilder endpoints, Action configureResource) + { + configureResource(new(endpoints)); + return endpoints; + } + + public static IEndpointRouteBuilder MapWorkflows(this IEndpointRouteBuilder endpoints) => endpoints.MapWorkflows(x => x + .Post() + .Execute() + .Dispatch() + .List() + .GetMany() + .Get() + ); +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Extensions/ApplicationBuilderExtensions.cs b/src/api/Elsa.Api/Extensions/ApplicationBuilderExtensions.cs new file mode 100644 index 000000000..3e1337603 --- /dev/null +++ b/src/api/Elsa.Api/Extensions/ApplicationBuilderExtensions.cs @@ -0,0 +1,9 @@ +using Elsa.Api.Middleware; +using Microsoft.AspNetCore.Builder; + +namespace Elsa.Api.Extensions; + +public static class ApplicationBuilderExtensions +{ + public static IApplicationBuilder UseJsonSerializationErrorHandler(this IApplicationBuilder app) => app.UseMiddleware(); +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Extensions/EndpointRouteBuilderExtensions.cs b/src/api/Elsa.Api/Extensions/EndpointRouteBuilderExtensions.cs new file mode 100644 index 000000000..0a4385896 --- /dev/null +++ b/src/api/Elsa.Api/Extensions/EndpointRouteBuilderExtensions.cs @@ -0,0 +1,18 @@ +using Elsa.Api.Endpoints.ActivityDescriptors; +using Elsa.Api.Endpoints.Events; +using Elsa.Api.Endpoints.TriggerDescriptors; +using Elsa.Api.Endpoints.WorkflowInstances; +using Elsa.Api.Endpoints.Workflows; +using Microsoft.AspNetCore.Routing; + +namespace Elsa.Api.Extensions; + +public static class EndpointRouteBuilderExtensions +{ + public static IEndpointRouteBuilder MapElsaApiEndpoints(this IEndpointRouteBuilder endpoints) => endpoints + .MapWorkflows() + .MapWorkflowInstances() + .MapActivityDescriptors() + .MapTriggerDescriptors() + .MapEvents(); +} \ No newline at end of file diff --git a/src/api/Elsa.Api/Middleware/JsonSerializationErrorHandlerMiddleware.cs b/src/api/Elsa.Api/Middleware/JsonSerializationErrorHandlerMiddleware.cs new file mode 100644 index 000000000..5f0eb9d54 --- /dev/null +++ b/src/api/Elsa.Api/Middleware/JsonSerializationErrorHandlerMiddleware.cs @@ -0,0 +1,43 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Api.Middleware; + +/// +/// Catches JSON serialization errors during POST requests and returns Bad Request responses. +/// +public class JsonSerializationErrorHandlerMiddleware +{ + private readonly RequestDelegate _next; + public JsonSerializationErrorHandlerMiddleware(RequestDelegate next) => _next = next; + + public async Task InvokeAsync(HttpContext httpContext) + { + var method = httpContext.Request.Method; + + // If the HTTP verb is anything but POST or PUT, do nothing. + if (!string.Equals(method, "POST", StringComparison.InvariantCultureIgnoreCase) && !string.Equals(method, "PUT", StringComparison.InvariantCultureIgnoreCase)) + { + await _next(httpContext); + return; + } + + try + { + await _next(httpContext); + } + catch (JsonException e) + { + var model = new + { + ModelBindingError = e.Message + }; + + var result = Results.BadRequest(model); + + await result.ExecuteAsync(httpContext); + } + } +} \ No newline at end of file diff --git a/src/api/Elsa.Api/ResourceBase.cs b/src/api/Elsa.Api/ResourceBase.cs new file mode 100644 index 000000000..ef8a4c221 --- /dev/null +++ b/src/api/Elsa.Api/ResourceBase.cs @@ -0,0 +1,23 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; + +namespace Elsa.Api; + +public abstract class ResourceBase where T:ResourceBase +{ + protected ResourceBase(IEndpointRouteBuilder endpoints) => Endpoints = endpoints; + protected IEndpointRouteBuilder Endpoints { get; } + + protected T MapGet(string pattern, Delegate handler) + { + Endpoints.MapGet(pattern, handler); + return (T)this; + } + + protected T MapPost(string pattern, Delegate handler) + { + Endpoints.MapPost(pattern, handler); + return (T)this; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/Console/ReadLine.cs b/src/core/Elsa.Core/Activities/Console/ReadLine.cs new file mode 100644 index 000000000..4ee322a6d --- /dev/null +++ b/src/core/Elsa.Core/Activities/Console/ReadLine.cs @@ -0,0 +1,21 @@ +using Elsa.Attributes; +using Elsa.Models; + +namespace Elsa.Activities.Console; + +public class ReadLine : Activity +{ + public ReadLine() + { + } + + public ReadLine(Variable variable, Func? valueConverter = default) => Output = new Output(variable, valueConverter); + + [Output] public Output? Output { get; set; } + + protected override void Execute(ActivityExecutionContext context) + { + var text = System.Console.ReadLine(); + context.Set(Output, text); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/Console/WriteLine.cs b/src/core/Elsa.Core/Activities/Console/WriteLine.cs new file mode 100644 index 000000000..d41206188 --- /dev/null +++ b/src/core/Elsa.Core/Activities/Console/WriteLine.cs @@ -0,0 +1,37 @@ +using System.ComponentModel; +using Elsa.Models; + +namespace Elsa.Activities.Console; + +public class WriteLine : Activity +{ + public WriteLine() + { + } + + public WriteLine(string text) : this(new Literal(text)) + { + } + + public WriteLine(Func text) : this(new DelegateReference(text)) + { + } + + public WriteLine(Func text) : this(new DelegateReference(text)) + { + } + + public WriteLine(Variable variable) => Text = new Input(variable); + public WriteLine(Literal literal) => Text = new Input(literal); + public WriteLine(DelegateReference delegateExpression) => Text = new Input(delegateExpression); + public WriteLine(Input text) => Text = text; + + [Description("The text to write.")] + public Input Text { get; set; } = default!; + + protected override void Execute(ActivityExecutionContext context) + { + var text = context.Get(Text); + System.Console.WriteLine(text); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/For.cs b/src/core/Elsa.Core/Activities/ControlFlow/For.cs new file mode 100644 index 000000000..1ced6704f --- /dev/null +++ b/src/core/Elsa.Core/Activities/ControlFlow/For.cs @@ -0,0 +1,70 @@ +using Elsa.Attributes; +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Activities.ControlFlow; + +public enum ForOperator +{ + LessThan, + LessThanOrEqual, + GreaterThan, + GreaterThanOrEqual +} + +public class For : Activity +{ + public Input Start { get; set; } = new(0); + public Input End { get; set; } = new(0); + public Input Step { get; set; } = new(1); + public Input Operator { get; set; } = new(ForOperator.LessThanOrEqual); + [Outbound] public IActivity? Body { get; set; } + public Variable CurrentValue { get; set; } = new(); + + protected override void Execute(ActivityExecutionContext context) + { + var iterateNode = Body; + + if (iterateNode == null) + return; + + context.ExpressionExecutionContext.Register.Declare(CurrentValue); + HandleIteration(context); + } + + private void HandleIteration(ActivityExecutionContext context) + { + var iterateNode = Body!; + var end = context.Get(End); + var currentValue = CurrentValue.Get(context.ExpressionExecutionContext); + + // Initialize or increment. + var start = context.Get(Start); + var step = context.Get(Step); + var op = context.Get(Operator); + currentValue = currentValue == null ? start : currentValue + step; + + var loop = op switch + { + ForOperator.LessThan => currentValue < end, + ForOperator.LessThanOrEqual => currentValue <= end, + ForOperator.GreaterThan => currentValue > end, + ForOperator.GreaterThanOrEqual => currentValue >= end, + _ => throw new NotSupportedException() + }; + + if (loop) + { + context.ScheduleActivity(iterateNode, OnChildComplete); + + // Update loop variable. + CurrentValue.Set(context.ExpressionExecutionContext, currentValue); + } + } + + private ValueTask OnChildComplete(ActivityExecutionContext completedActivityExecutionContext, ActivityExecutionContext ownerActivityExecutionContext) + { + HandleIteration(ownerActivityExecutionContext); + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/ForEach.cs b/src/core/Elsa.Core/Activities/ControlFlow/ForEach.cs new file mode 100644 index 000000000..410eb5dfc --- /dev/null +++ b/src/core/Elsa.Core/Activities/ControlFlow/ForEach.cs @@ -0,0 +1,44 @@ +using Elsa.Attributes; +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Activities.ControlFlow; + +public class ForEach : Activity +{ + [Input] public Input> Items { get; set; } = new(Array.Empty()); + [Outbound] public IActivity Body { get; set; } = default!; + public Variable CurrentValue { get; set; } = new(); + private const string CurrentIndexProperty = "CurrentIndex"; + + protected override void Execute(ActivityExecutionContext context) + { + // Declare looping variable. + context.ExpressionExecutionContext.Register.Declare(CurrentValue); + + // Execute first iteration. + HandleIteration(context); + } + + private void HandleIteration(ActivityExecutionContext context) + { + var currentIndex = context.GetProperty(CurrentIndexProperty); + var items = context.Get(Items)!.ToList(); + + if (currentIndex >= items.Count) + return; + + var currentItem = items[currentIndex]; + CurrentValue.Set(context, currentItem); + context.ScheduleActivity(Body, OnChildCompleted); + + // Increment index. + context.UpdateProperty(CurrentIndexProperty, x => x + 1); + } + + private ValueTask OnChildCompleted(ActivityExecutionContext context, ActivityExecutionContext childContext) + { + HandleIteration(context); + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/Fork.cs b/src/core/Elsa.Core/Activities/ControlFlow/Fork.cs new file mode 100644 index 000000000..72f95eb04 --- /dev/null +++ b/src/core/Elsa.Core/Activities/ControlFlow/Fork.cs @@ -0,0 +1,67 @@ +using System.Collections.Immutable; +using Elsa.Attributes; +using Elsa.Contracts; +using Elsa.Extensions; +using Elsa.Models; + +namespace Elsa.Activities.ControlFlow; + +public class Fork : Activity +{ + [Input] public Input JoinMode { get; set; } = new(ControlFlow.JoinMode.WaitAny); + [Outbound] public ICollection Branches { get; set; } = new List(); + + protected override void Execute(ActivityExecutionContext context) => context.ScheduleActivities(Branches.Reverse(), CompleteChildAsync); + + private ValueTask CompleteChildAsync(ActivityExecutionContext context, ActivityExecutionContext childContext) + { + OnChildCompleted(context, childContext); + return ValueTask.CompletedTask; + } + + private void OnChildCompleted(ActivityExecutionContext context, ActivityExecutionContext childContext) + { + var completedChildActivityId = childContext.Activity.Id; + + // Append activity to set of completed activities. + var completedActivityIds = context.UpdateProperty>("Completed", set => + { + set ??= new HashSet(); + set.Add(completedChildActivityId); + return set; + }); + + var allChildActivityIds = Branches.Select(x => x.Id).ToImmutableHashSet(); + var joinMode = context.Get(JoinMode); + + switch (joinMode) + { + case ControlFlow.JoinMode.WaitAny: + { + // Remove any and all bookmarks from other branches. + RemoveBookmarks(context); + } + break; + case ControlFlow.JoinMode.WaitAll: + { + var allSet = allChildActivityIds.All(x => completedActivityIds.Contains(x)); + + if (!allSet) + context.PreventContinuation(); + } + break; + } + } + + private void RemoveBookmarks(ActivityExecutionContext context) + { + // Find all descendants for each branch and remove any associated bookmarks. + var workflowExecutionContext = context.WorkflowExecutionContext; + var forkNode = context.ActivityNode; + var branchNodes = forkNode.Children; + var branchDescendantActivityIds = branchNodes.SelectMany(x => x.Flatten()).Select(x => x.Activity.Id).ToHashSet(); + var bookmarksToRemove = workflowExecutionContext.Bookmarks.Where(x => branchDescendantActivityIds.Contains(x.ActivityId)).ToList(); + + workflowExecutionContext.UnregisterBookmarks(bookmarksToRemove); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/If.cs b/src/core/Elsa.Core/Activities/ControlFlow/If.cs new file mode 100644 index 000000000..69beb05b1 --- /dev/null +++ b/src/core/Elsa.Core/Activities/ControlFlow/If.cs @@ -0,0 +1,26 @@ +using Elsa.Attributes; +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Activities.ControlFlow; + +public class If : Activity +{ + [Input] public Input Condition { get; set; } = new(new Literal(false)); + [Outbound] public IActivity? Then { get; set; } + [Outbound] public IActivity? Else { get; set; } + + protected override void Execute(ActivityExecutionContext context) + { + var result = context.Get(Condition); + var nextNode = result ? Then : Else; + + if (nextNode != null) + context.ScheduleActivity(nextNode, OnChildCompletedAsync); + } + + private ValueTask OnChildCompletedAsync(ActivityExecutionContext context, ActivityExecutionContext childContext) + { + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/Join.cs b/src/core/Elsa.Core/Activities/ControlFlow/Join.cs new file mode 100644 index 000000000..9d6b92c7f --- /dev/null +++ b/src/core/Elsa.Core/Activities/ControlFlow/Join.cs @@ -0,0 +1,85 @@ +using Elsa.Attributes; +using Elsa.Models; + +namespace Elsa.Activities.ControlFlow; + +public enum JoinMode +{ + WaitAny, + WaitAll +} + +public class Join : Activity +{ + public Join(JoinMode joinMode = ControlFlow.JoinMode.WaitAny) + { + JoinMode = new Input(joinMode); + } + + [Input] public Input JoinMode { get; set; } + + protected override void Execute(ActivityExecutionContext context) + { + var joinMode = context.Get(JoinMode); + + switch (joinMode) + { + case ControlFlow.JoinMode.WaitAny: + { + // Remove any and all bookmarks from other branches. + RemoveBookmarks(context); + } + break; + case ControlFlow.JoinMode.WaitAll: + { + var workflowExecutionContext = context.WorkflowExecutionContext; + + // Record last executed activity ID. + var lastActivityInstanceId = context.WorkflowExecutionContext.ExecutionLog.LastOrDefault()?.ActivityInstanceId; + var lastActivityId = lastActivityInstanceId != null ? workflowExecutionContext.ActivityExecutionContexts.FirstOrDefault(x => x.Id == lastActivityInstanceId)?.Activity.Id : default; + + if (lastActivityId != null) + { + var recordedActivitiesKey = $"{Id}:RecordedActivities"; + var recordedActivityIds = workflowExecutionContext.UpdateProperty>(recordedActivitiesKey, set => + { + set ??= new HashSet(); + set.Add(lastActivityId); + return set; + }); + + var inboundActivityIds = context.ActivityNode.Parents.Select(x => x.Activity.Id).ToHashSet(); + var allSet = inboundActivityIds.All(x => recordedActivityIds.Contains(x)); + + if (!allSet) + { + // If not all inbound activities have executed yet, instruct engine to not complete this activity. + context.PreventContinuation(); + } + else + { + // If all inbound activities have executed, clean global state. + workflowExecutionContext.Properties.Remove(recordedActivitiesKey); + } + } + + break; + } + } + } + + private void RemoveBookmarks(ActivityExecutionContext context) + { + var workflowExecutionContext = context.WorkflowExecutionContext; + + // Remove all bookmarks of ancestors and siblings. + var joinNode = context.ActivityNode; + var ancestors = joinNode.Ancestors().Select(x => x.Activity.Id); + var siblingsAndCousins = joinNode.SiblingsAndCousins().Select(x => x.Activity.Id); + var activitiesInPath = ancestors.Concat(siblingsAndCousins).ToHashSet(); + var bookmarksToRemove = workflowExecutionContext.Bookmarks.Where(x => activitiesInPath.Contains(x.ActivityId)).ToList(); + + // Remove any bookmarks of any ancestors. + workflowExecutionContext.UnregisterBookmarks(bookmarksToRemove); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/ParallelForEach.cs b/src/core/Elsa.Core/Activities/ControlFlow/ParallelForEach.cs new file mode 100644 index 000000000..0bcf4e78c --- /dev/null +++ b/src/core/Elsa.Core/Activities/ControlFlow/ParallelForEach.cs @@ -0,0 +1,44 @@ +using Elsa.Attributes; +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Activities.ControlFlow; + +public class ParallelForEach : Activity +{ + private const string CollectedCountProperty = nameof(CollectedCountProperty); + [Input] public Input> Items { get; set; } = new(Array.Empty()); + [Outbound] public IActivity Body { get; set; } = default!; + public Variable CurrentValue { get; set; } = new(); + + protected override void Execute(ActivityExecutionContext context) + { + // Declare looping variable. + context.ExpressionExecutionContext.Register.Declare(CurrentValue); + var items = context.Get(Items)!.Reverse().ToList(); + + foreach (var item in items) + { + var localVariable = new Variable(item) + { + // "Capture" the CurrentValues variable by use same ID so that outer scope variable can access inner scope variable. + Id = CurrentValue.Id + }; + + // Schedule a body of work for each item. + context.ScheduleActivity(Body, OnChildCompleted, new[]{localVariable}); + } + } + + private ValueTask OnChildCompleted(ActivityExecutionContext context, ActivityExecutionContext childContext) + { + var itemCount = context.Get(Items)!.Count; + var collectedCount = context.UpdateProperty(CollectedCountProperty, count => count + 1); + + // Prevent next sibling from executing while not all scheduled activities have completed. + if(collectedCount < itemCount) + context.PreventContinuation(); + + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/While.cs b/src/core/Elsa.Core/Activities/ControlFlow/While.cs new file mode 100644 index 000000000..8e19fa3fc --- /dev/null +++ b/src/core/Elsa.Core/Activities/ControlFlow/While.cs @@ -0,0 +1,27 @@ +using Elsa.Attributes; +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Activities.ControlFlow; + +public class While : Activity +{ + [Input] public Input Condition { get; set; } = new(false); + [Outbound] public IActivity Body { get; set; } = default!; + + protected override void Execute(ActivityExecutionContext context) + { + var loop = context.Get(Condition); + + if(loop) + context.ScheduleActivity(Body, OnBodyCompleted); + } + + private async ValueTask OnBodyCompleted(ActivityExecutionContext context, ActivityExecutionContext childContext) + { + var loop = await context.EvaluateAsync(Condition); + + if(loop) + context.ScheduleActivity(Body, OnBodyCompleted); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/Primitives/Event.cs b/src/core/Elsa.Core/Activities/Primitives/Event.cs new file mode 100644 index 000000000..187cdd953 --- /dev/null +++ b/src/core/Elsa.Core/Activities/Primitives/Event.cs @@ -0,0 +1,40 @@ +using Elsa.Attributes; +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Activities.Primitives; + +public class Event : Activity +{ + public Event() + { + } + + public Event(string eventName) : this(new Literal(eventName)) + { + } + + public Event(Func text) : this(new DelegateReference(text)) + { + } + + public Event(Func text) : this(new DelegateReference(text)) + { + } + + public Event(Variable variable) => EventName = new Input(variable); + public Event(Literal literal) => EventName = new Input(literal); + public Event(DelegateReference delegateExpression) => EventName = new Input(delegateExpression); + public Event(Input eventName) => EventName = eventName; + + [Input] public Input EventName { get; set; } = default!; + [Output] public Output? Payload { get; set; } + + protected override void Execute(ActivityExecutionContext context) + { + var hasher = context.GetRequiredService(); + var eventName = context.Get(EventName)!; + var hash = hasher.Hash(eventName); + context.SetBookmark(hash); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/Primitives/UpdateVariable.cs b/src/core/Elsa.Core/Activities/Primitives/UpdateVariable.cs new file mode 100644 index 000000000..b469205b9 --- /dev/null +++ b/src/core/Elsa.Core/Activities/Primitives/UpdateVariable.cs @@ -0,0 +1,16 @@ +using Elsa.Attributes; +using Elsa.Models; + +namespace Elsa.Activities.Primitives; + +public class UpdateVariable : Activity +{ + [Input] public Input Value { get; set; } = new Input(new Literal()); + public Variable Variable { get; set; } = default!; + + protected override void Execute(ActivityExecutionContext context) + { + var value = context.Get(Value); + Variable.Set(context, value); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/Workflows/Connection.cs b/src/core/Elsa.Core/Activities/Workflows/Connection.cs new file mode 100644 index 000000000..805a70e46 --- /dev/null +++ b/src/core/Elsa.Core/Activities/Workflows/Connection.cs @@ -0,0 +1,5 @@ +using Elsa.Contracts; + +namespace Elsa.Activities.Workflows; + +public record Connection(IActivity Source, IActivity Target, string? SourcePort, string TargetPort); \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/Workflows/Container.cs b/src/core/Elsa.Core/Activities/Workflows/Container.cs new file mode 100644 index 000000000..d1bd27fd0 --- /dev/null +++ b/src/core/Elsa.Core/Activities/Workflows/Container.cs @@ -0,0 +1,28 @@ +using System.Collections.ObjectModel; +using Elsa.Attributes; +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Activities.Workflows; + +public abstract class Container : Activity, IContainer +{ + protected Container() + { + } + + protected Container(params IActivity[] activities) => Activities = activities; + [Outbound] public ICollection Activities { get; set; } = new List(); + public ICollection Variables { get; set; } = new Collection(); + + protected override void Execute(ActivityExecutionContext context) + { + // Register variables. + context.ExpressionExecutionContext.Register.Declare(Variables); + + // Schedule children. + ScheduleChildren(context); + } + + protected abstract void ScheduleChildren(ActivityExecutionContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/Workflows/Flowchart.cs b/src/core/Elsa.Core/Activities/Workflows/Flowchart.cs new file mode 100644 index 000000000..dd65da4d5 --- /dev/null +++ b/src/core/Elsa.Core/Activities/Workflows/Flowchart.cs @@ -0,0 +1,38 @@ +using System.ComponentModel; +using Elsa.Attributes; +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Activities.Workflows; + +[Category("Workflows")] +public class Flowchart : Container +{ + [Node] public IActivity? Start { get; set; } = default!; + public ICollection Connections { get; set; } = new List(); + + protected override void ScheduleChildren(ActivityExecutionContext context) + { + if (Start == null!) + return; + + context.ScheduleActivity(Start, OnChildCompleted); + } + + private ValueTask OnChildCompleted(ActivityExecutionContext context, ActivityExecutionContext childContext) + { + ScheduleChildren(context, childContext.Activity); + return ValueTask.CompletedTask; + } + + private void ScheduleChildren(ActivityExecutionContext context, IActivity parent) + { + if (parent == null!) + return; + + var outboundConnections = Connections.Where(x => x.Source == parent).ToList(); + var children = outboundConnections.Select(x => x.Target).ToList(); + + context.ScheduleActivities(children, OnChildCompleted); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/Workflows/Sequence.cs b/src/core/Elsa.Core/Activities/Workflows/Sequence.cs new file mode 100644 index 000000000..1ef5dc161 --- /dev/null +++ b/src/core/Elsa.Core/Activities/Workflows/Sequence.cs @@ -0,0 +1,43 @@ +using System.ComponentModel; +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Activities.Workflows; + +[Category("Workflows")] +public class Sequence : Container +{ + private const string CurrentIndexProperty = "CurrentIndex"; + + public Sequence() + { + } + + public Sequence(params IActivity[] activities) : base(activities) + { + } + + protected override void ScheduleChildren(ActivityExecutionContext context) + { + HandleItem(context); + } + + private void HandleItem(ActivityExecutionContext context) + { + var currentIndex = context.GetProperty(CurrentIndexProperty); + var childActivities = Activities.ToList(); + + if (currentIndex >= childActivities.Count) + return; + + var nextActivity = childActivities.ElementAt(currentIndex); + context.ScheduleActivity(nextActivity, OnChildCompleted); + context.UpdateProperty(CurrentIndexProperty, x => x + 1); + } + + private ValueTask OnChildCompleted(ActivityExecutionContext context, ActivityExecutionContext childContext) + { + HandleItem(context); + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/ActivityNodeResolvers/OutboundActivityNodeResolver.cs b/src/core/Elsa.Core/ActivityNodeResolvers/OutboundActivityNodeResolver.cs new file mode 100644 index 000000000..98513de5c --- /dev/null +++ b/src/core/Elsa.Core/ActivityNodeResolvers/OutboundActivityNodeResolver.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using Elsa.Attributes; +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.ActivityNodeResolvers; + +public class OutboundActivityNodeResolver : IActivityNodeResolver +{ + public int Priority => -1; + public bool GetSupportsActivity(IActivity activity) => activity is Activity; + + public IEnumerable GetPorts(IActivity activity) => + GetSinglePorts(activity) + .Where(x => x != null) + .Select(x => x!) + .ToHashSet(); + + private static IEnumerable GetSinglePorts(IActivity activity) + { + var nodeType = activity.GetType(); + + var ports = + from prop in nodeType.GetProperties(BindingFlags.Public | BindingFlags.Instance) + where typeof(IActivity).IsAssignableFrom(prop.PropertyType) || typeof(IEnumerable).IsAssignableFrom(prop.PropertyType) + let portAttr = prop.GetCustomAttribute() + let nodeAttr = prop.GetCustomAttribute() + where portAttr != null || nodeAttr != null + let value = prop.GetValue(activity) + let isCollection = GetPropertyIsCollection(prop.PropertyType) + select isCollection ? (IEnumerable)value : new[] { (IActivity)value }; + + return ports.SelectMany(x => x); + } + + private static bool GetPropertyIsCollection(Type propertyType) => typeof(IEnumerable).IsAssignableFrom(propertyType); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/AssemblyInfo.cs b/src/core/Elsa.Core/AssemblyInfo.cs new file mode 100644 index 000000000..95465252a --- /dev/null +++ b/src/core/Elsa.Core/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: CLSCompliant(true)] \ No newline at end of file diff --git a/src/core/Elsa.Core/Attributes/ActivityAttribute.cs b/src/core/Elsa.Core/Attributes/ActivityAttribute.cs new file mode 100644 index 000000000..379069559 --- /dev/null +++ b/src/core/Elsa.Core/Attributes/ActivityAttribute.cs @@ -0,0 +1,12 @@ +namespace Elsa.Attributes; + +[AttributeUsage(AttributeTargets.Class)] +public class ActivityAttribute : Attribute +{ + public ActivityAttribute(string? typeName = default) + { + TypeName = typeName; + } + + public string? TypeName { get; } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Attributes/InboundAttribute.cs b/src/core/Elsa.Core/Attributes/InboundAttribute.cs new file mode 100644 index 000000000..493b9aa2b --- /dev/null +++ b/src/core/Elsa.Core/Attributes/InboundAttribute.cs @@ -0,0 +1,6 @@ +namespace Elsa.Attributes; + +[AttributeUsage(AttributeTargets.Property)] +public class InboundAttribute : Attribute +{ +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Attributes/InputAttribute.cs b/src/core/Elsa.Core/Attributes/InputAttribute.cs new file mode 100644 index 000000000..7acc9e7ba --- /dev/null +++ b/src/core/Elsa.Core/Attributes/InputAttribute.cs @@ -0,0 +1,72 @@ +namespace Elsa.Attributes; + +[AttributeUsage(AttributeTargets.Property)] +public class InputAttribute : Attribute +{ + public string? Name { get; set; } + + /// + /// A hint to workflow tooling what input control to use. + /// + public string? UIHint { get; set; } + + /// + /// The user-friendly name of the activity property. + /// + public string? DisplayName { get; set; } + + // /// + // /// A brief description about this property for workflow tooling to use when displaying activity editors. + // /// + // public string? Description { get; set; } + + /// + /// A category to group this property with. + /// + public string? Category { get; set; } + + /// + /// A value representing options specific to a given UI hint. + /// + public object? Options { get; set; } + + /// + /// The type that provides options. If specified, this overrules any value specified via . + /// + public Type? OptionsProvider { get; set; } + + /// + /// A value to order this property by. Properties are displayed in ascending order (lower appears before higher). + /// + public float Order { get; set; } + + /// + /// The default value to set. + /// + public object? DefaultValue { get; set; } + + /// + /// The type that provides a default value. When specified, the will be ignored. + /// + public Type? DefaultValueProvider { get; set; } + + /// + /// The syntax to use by default when evaluating the value. Only used when the property definition doesn't have a syntax specified. + /// + public string? DefaultSyntax { get; set; } + + /// + /// The syntax to use by default when evaluating the value. Only used when the property definition doesn't have a syntax specified. + /// + public string[]? SupportedSyntaxes { get; set; } + + /// + /// A value indicating whether this property should be displayed but as read-only. + /// + public bool IsReadOnly { get; set; } + + /// + /// A value indicating whether this property should be visible. + /// + public bool IsBrowsable { get; set; } = true; +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Attributes/NodeAttribute.cs b/src/core/Elsa.Core/Attributes/NodeAttribute.cs new file mode 100644 index 000000000..0e8eb0760 --- /dev/null +++ b/src/core/Elsa.Core/Attributes/NodeAttribute.cs @@ -0,0 +1,6 @@ +namespace Elsa.Attributes; + +[AttributeUsage(AttributeTargets.Property)] +public class NodeAttribute : Attribute +{ +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Attributes/OutboundAttribute.cs b/src/core/Elsa.Core/Attributes/OutboundAttribute.cs new file mode 100644 index 000000000..ebd2fa275 --- /dev/null +++ b/src/core/Elsa.Core/Attributes/OutboundAttribute.cs @@ -0,0 +1,8 @@ +namespace Elsa.Attributes; + +[AttributeUsage(AttributeTargets.Property)] +public class OutboundAttribute : Attribute +{ + public string? Name { get; set; } + public string? DisplayName { get; set; } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Attributes/OutputAttribute.cs b/src/core/Elsa.Core/Attributes/OutputAttribute.cs new file mode 100644 index 000000000..26a18a142 --- /dev/null +++ b/src/core/Elsa.Core/Attributes/OutputAttribute.cs @@ -0,0 +1,15 @@ +namespace Elsa.Attributes; + +[AttributeUsage(AttributeTargets.Property)] +public class OutputAttribute : Attribute +{ + /// + /// The technical name of the activity property. + /// + public string? Name { get; set; } + + /// + /// A brief description about this property for workflow tooling to use when displaying activity editors. + /// + public string? Description { get; set; } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Attributes/StateAttribute.cs b/src/core/Elsa.Core/Attributes/StateAttribute.cs new file mode 100644 index 000000000..4ae20af2f --- /dev/null +++ b/src/core/Elsa.Core/Attributes/StateAttribute.cs @@ -0,0 +1,6 @@ +namespace Elsa.Attributes; + +[AttributeUsage(AttributeTargets.Property)] +public class StateAttribute : Attribute +{ +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Builders/WorkflowDefinitionBuilder.cs b/src/core/Elsa.Core/Builders/WorkflowDefinitionBuilder.cs new file mode 100644 index 000000000..db51e63fc --- /dev/null +++ b/src/core/Elsa.Core/Builders/WorkflowDefinitionBuilder.cs @@ -0,0 +1,55 @@ +using Elsa.Activities.Workflows; +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Builders; + +public class WorkflowDefinitionBuilder : IWorkflowDefinitionBuilder +{ + public string? Id { get; private set; } + public string? DefinitionId { get; private set; } + public int Version { get; private set; } = 1; + public IActivity? Root { get; private set; } + public ICollection Triggers { get; } = new List(); + + public IWorkflowDefinitionBuilder WithId(string id) + { + Id = id; + return this; + } + + public IWorkflowDefinitionBuilder WithDefinitionId(string definitionId) + { + DefinitionId = definitionId; + return this; + } + + public IWorkflowDefinitionBuilder WithVersion(int version) + { + Version = version; + return this; + } + + public IWorkflowDefinitionBuilder WithRoot(IActivity root) + { + Root = root; + return this; + } + + public IWorkflowDefinitionBuilder AddTrigger(ITrigger trigger) + { + Triggers.Add(trigger); + return this; + } + + public Workflow BuildWorkflow() + { + var definitionId = DefinitionId ?? Guid.NewGuid().ToString("N"); + var id = Id ?? Guid.NewGuid().ToString("N"); + var root = Root ?? new Sequence(); + var identity = new WorkflowIdentity(definitionId, Version, id); + var publication = WorkflowPublication.LatestAndPublished; + var metadata = new WorkflowMetadata(); + return new Workflow(identity, publication, metadata, root, Triggers); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/Delegates.cs b/src/core/Elsa.Core/Contracts/Delegates.cs new file mode 100644 index 000000000..6ad9e4fe1 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/Delegates.cs @@ -0,0 +1,6 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +public delegate ValueTask ExecuteActivityDelegate(ActivityExecutionContext context); +public delegate ValueTask ActivityCompletionCallback(ActivityExecutionContext context, ActivityExecutionContext childContext); \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IActivity.cs b/src/core/Elsa.Core/Contracts/IActivity.cs new file mode 100644 index 000000000..a103015fd --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IActivity.cs @@ -0,0 +1,8 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +public interface IActivity : INode +{ + ValueTask ExecuteAsync(ActivityExecutionContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IActivityExecutionBuilder.cs b/src/core/Elsa.Core/Contracts/IActivityExecutionBuilder.cs new file mode 100644 index 000000000..eb8c6c90d --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IActivityExecutionBuilder.cs @@ -0,0 +1,10 @@ +using Elsa.Pipelines.ActivityExecution; + +namespace Elsa.Contracts; + +public interface IActivityExecutionBuilder +{ + IServiceProvider ServiceProvider { get; } + IActivityExecutionBuilder Use(Func middleware); + public ActivityMiddlewareDelegate Build(); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IActivityExecutionMiddleware.cs b/src/core/Elsa.Core/Contracts/IActivityExecutionMiddleware.cs new file mode 100644 index 000000000..ea3250730 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IActivityExecutionMiddleware.cs @@ -0,0 +1,8 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +public interface IActivityExecutionMiddleware +{ + ValueTask InvokeAsync(ActivityExecutionContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IActivityExecutionPipeline.cs b/src/core/Elsa.Core/Contracts/IActivityExecutionPipeline.cs new file mode 100644 index 000000000..a3fca6697 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IActivityExecutionPipeline.cs @@ -0,0 +1,11 @@ +using Elsa.Models; +using Elsa.Pipelines.ActivityExecution; + +namespace Elsa.Contracts; + +public interface IActivityExecutionPipeline +{ + ActivityMiddlewareDelegate Setup(Action setup); + ActivityMiddlewareDelegate Pipeline { get; } + Task ExecuteAsync(ActivityExecutionContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IActivityInvoker.cs b/src/core/Elsa.Core/Contracts/IActivityInvoker.cs new file mode 100644 index 000000000..64ef38013 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IActivityInvoker.cs @@ -0,0 +1,14 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +public interface IActivityInvoker +{ + Task InvokeAsync( + WorkflowExecutionContext workflowExecutionContext, + IActivity activity, + ActivityExecutionContext? owner = default, + IEnumerable? locationReferences = default); + + Task InvokeAsync(ActivityExecutionContext activityExecutionContext); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IActivityNodeResolver.cs b/src/core/Elsa.Core/Contracts/IActivityNodeResolver.cs new file mode 100644 index 000000000..2a87ae9cd --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IActivityNodeResolver.cs @@ -0,0 +1,8 @@ +namespace Elsa.Contracts; + +public interface IActivityNodeResolver +{ + int Priority { get; } + bool GetSupportsActivity(IActivity activity); + IEnumerable GetPorts(IActivity activity); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IActivityScheduler.cs b/src/core/Elsa.Core/Contracts/IActivityScheduler.cs new file mode 100644 index 000000000..3584d50a5 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IActivityScheduler.cs @@ -0,0 +1,11 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +public interface IActivityScheduler +{ + bool HasAny { get; } + void Push(ActivityWorkItem workItem); + ActivityWorkItem Pop(); + IEnumerable List(); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IActivitySchedulerFactory.cs b/src/core/Elsa.Core/Contracts/IActivitySchedulerFactory.cs new file mode 100644 index 000000000..25fc9af59 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IActivitySchedulerFactory.cs @@ -0,0 +1,6 @@ +namespace Elsa.Contracts; + +public interface IActivitySchedulerFactory +{ + IActivityScheduler CreateScheduler(); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IActivityWalker.cs b/src/core/Elsa.Core/Contracts/IActivityWalker.cs new file mode 100644 index 000000000..ccaec36e3 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IActivityWalker.cs @@ -0,0 +1,11 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +/// +/// Walks an activity tree starting at the root. +/// +public interface IActivityWalker +{ + ActivityNode Walk(IActivity activity); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IContainer.cs b/src/core/Elsa.Core/Contracts/IContainer.cs new file mode 100644 index 000000000..740022970 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IContainer.cs @@ -0,0 +1,8 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +public interface IContainer : IActivity +{ + ICollection Variables { get; } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IExpression.cs b/src/core/Elsa.Core/Contracts/IExpression.cs new file mode 100644 index 000000000..a07160828 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IExpression.cs @@ -0,0 +1,5 @@ +namespace Elsa.Contracts; + +public interface IExpression +{ +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IExpressionEvaluator.cs b/src/core/Elsa.Core/Contracts/IExpressionEvaluator.cs new file mode 100644 index 000000000..231e584cc --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IExpressionEvaluator.cs @@ -0,0 +1,15 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +public interface IExpressionEvaluator +{ + ValueTask EvaluateAsync(IExpression input, ExpressionExecutionContext context); + ValueTask EvaluateAsync(IExpression input, Type returnType, ExpressionExecutionContext context); +} + +public static class ExpressionEvaluatorExtensions +{ + public static ValueTask EvaluateAsync(this IExpressionEvaluator evaluator, Input input, ExpressionExecutionContext context) => evaluator.EvaluateAsync(input.Expression, context); + public static ValueTask EvaluateAsync(this IExpressionEvaluator evaluator, Input input, ExpressionExecutionContext context) => evaluator.EvaluateAsync(input.Expression, input.Type, context); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IExpressionHandler.cs b/src/core/Elsa.Core/Contracts/IExpressionHandler.cs new file mode 100644 index 000000000..f2a7f0740 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IExpressionHandler.cs @@ -0,0 +1,8 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +public interface IExpressionHandler +{ + ValueTask EvaluateAsync(IExpression expression, Type returnType, ExpressionExecutionContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IExpressionHandlerRegistry.cs b/src/core/Elsa.Core/Contracts/IExpressionHandlerRegistry.cs new file mode 100644 index 000000000..d489e56d3 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IExpressionHandlerRegistry.cs @@ -0,0 +1,7 @@ +namespace Elsa.Contracts; + +public interface IExpressionHandlerRegistry +{ + void Register(Type expression, Type handler); + IExpressionHandler? GetHandler(IExpression input); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IHasher.cs b/src/core/Elsa.Core/Contracts/IHasher.cs new file mode 100644 index 000000000..79c2a3432 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IHasher.cs @@ -0,0 +1,9 @@ +namespace Elsa.Contracts; + +/// +/// Computes a hash for a given value. +/// +public interface IHasher +{ + public string Hash(object value); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IIdentityGenerator.cs b/src/core/Elsa.Core/Contracts/IIdentityGenerator.cs new file mode 100644 index 000000000..1dac3d46c --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IIdentityGenerator.cs @@ -0,0 +1,6 @@ +namespace Elsa.Contracts; + +public interface IIdentityGenerator +{ + string GenerateId(); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IIdentityGraphService.cs b/src/core/Elsa.Core/Contracts/IIdentityGraphService.cs new file mode 100644 index 000000000..7d284207f --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IIdentityGraphService.cs @@ -0,0 +1,10 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +public interface IIdentityGraphService +{ + void AssignIdentities(Workflow workflow); + void AssignIdentities(IActivity root); + void AssignIdentities(ActivityNode root); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/INode.cs b/src/core/Elsa.Core/Contracts/INode.cs new file mode 100644 index 000000000..b4ee1fc25 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/INode.cs @@ -0,0 +1,8 @@ +namespace Elsa.Contracts; + +public interface INode +{ + string Id { get; set; } + string NodeType { get; set; } + IDictionary Metadata { get; set; } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IReference.cs b/src/core/Elsa.Core/Contracts/IReference.cs new file mode 100644 index 000000000..8859212d6 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IReference.cs @@ -0,0 +1,5 @@ +namespace Elsa.Contracts; + +public interface IReference +{ +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IScheduledNodeExecuted.cs b/src/core/Elsa.Core/Contracts/IScheduledNodeExecuted.cs new file mode 100644 index 000000000..4bf87fc86 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IScheduledNodeExecuted.cs @@ -0,0 +1,8 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +public interface IScheduledNodeExecuted +{ + ValueTask HandleAsync(ActivityExecutionContext context, IActivity owner); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IScopeCompletedHandler.cs b/src/core/Elsa.Core/Contracts/IScopeCompletedHandler.cs new file mode 100644 index 000000000..06e450159 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IScopeCompletedHandler.cs @@ -0,0 +1,8 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +public interface IScopeCompletedHandler +{ + ValueTask HandleAsync(ActivityExecutionContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/ISystemClock.cs b/src/core/Elsa.Core/Contracts/ISystemClock.cs new file mode 100644 index 000000000..654003bae --- /dev/null +++ b/src/core/Elsa.Core/Contracts/ISystemClock.cs @@ -0,0 +1,6 @@ +namespace Elsa.Contracts; + +public interface ISystemClock +{ + DateTime UtcNow { get; } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/ITrigger.cs b/src/core/Elsa.Core/Contracts/ITrigger.cs new file mode 100644 index 000000000..60c5afe65 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/ITrigger.cs @@ -0,0 +1,9 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +public interface ITrigger : INode +{ + string TriggerType { get; set; } + ValueTask> GetHashInputsAsync(TriggerIndexingContext context, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IWorkflowDefinitionBuilder.cs b/src/core/Elsa.Core/Contracts/IWorkflowDefinitionBuilder.cs new file mode 100644 index 000000000..f17424768 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IWorkflowDefinitionBuilder.cs @@ -0,0 +1,16 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +public interface IWorkflowDefinitionBuilder +{ + string? DefinitionId { get; } + int Version { get; } + IActivity? Root { get; } + ICollection Triggers { get; } + IWorkflowDefinitionBuilder WithDefinitionId(string definitionId); + IWorkflowDefinitionBuilder WithVersion(int version); + IWorkflowDefinitionBuilder WithRoot(IActivity root); + IWorkflowDefinitionBuilder AddTrigger(ITrigger trigger); + Workflow BuildWorkflow(); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IWorkflowEngine.cs b/src/core/Elsa.Core/Contracts/IWorkflowEngine.cs new file mode 100644 index 000000000..62dfc5c75 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IWorkflowEngine.cs @@ -0,0 +1,20 @@ +using Elsa.Models; +using Elsa.State; + +namespace Elsa.Contracts; + +public interface IWorkflowEngine +{ + Task ExecuteAsync(Workflow workflow, CancellationToken cancellationToken = default); + Task ExecuteAsync(Workflow workflow, WorkflowState workflowState, CancellationToken cancellationToken = default); + Task ExecuteAsync(Workflow workflow, WorkflowState workflowState, Bookmark? bookmark, CancellationToken cancellationToken = default); + Task ExecuteAsync(WorkflowExecutionContext workflowExecutionContext); + + WorkflowExecutionContext CreateWorkflowExecutionContext( + IServiceProvider serviceProvider, + Workflow workflow, + WorkflowState? workflowState = default, + Bookmark? bookmark = default, + ExecuteActivityDelegate? executeActivityDelegate = default, + CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IWorkflowExecutionBuilder.cs b/src/core/Elsa.Core/Contracts/IWorkflowExecutionBuilder.cs new file mode 100644 index 000000000..491fb8cc5 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IWorkflowExecutionBuilder.cs @@ -0,0 +1,11 @@ +using Elsa.Pipelines.WorkflowExecution; + +namespace Elsa.Contracts; + +public interface IWorkflowExecutionBuilder +{ + public IDictionary Properties { get; } + IServiceProvider ApplicationServices { get; } + IWorkflowExecutionBuilder Use(Func middleware); + public WorkflowMiddlewareDelegate Build(); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IWorkflowExecutionMiddleware.cs b/src/core/Elsa.Core/Contracts/IWorkflowExecutionMiddleware.cs new file mode 100644 index 000000000..f83acffdf --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IWorkflowExecutionMiddleware.cs @@ -0,0 +1,8 @@ +using Elsa.Models; + +namespace Elsa.Contracts; + +public interface IWorkflowExecutionMiddleware +{ + ValueTask InvokeAsync(WorkflowExecutionContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IWorkflowExecutionPipeline.cs b/src/core/Elsa.Core/Contracts/IWorkflowExecutionPipeline.cs new file mode 100644 index 000000000..81f3ca011 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IWorkflowExecutionPipeline.cs @@ -0,0 +1,11 @@ +using Elsa.Models; +using Elsa.Pipelines.WorkflowExecution; + +namespace Elsa.Contracts; + +public interface IWorkflowExecutionPipeline +{ + WorkflowMiddlewareDelegate Setup(Action setup); + WorkflowMiddlewareDelegate Pipeline { get; } + Task ExecuteAsync(WorkflowExecutionContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Contracts/IWorkflowStateSerializer.cs b/src/core/Elsa.Core/Contracts/IWorkflowStateSerializer.cs new file mode 100644 index 000000000..38dc45c20 --- /dev/null +++ b/src/core/Elsa.Core/Contracts/IWorkflowStateSerializer.cs @@ -0,0 +1,10 @@ +using Elsa.Models; +using Elsa.State; + +namespace Elsa.Contracts; + +public interface IWorkflowStateSerializer +{ + WorkflowState ReadState(WorkflowExecutionContext workflowExecutionContext); + void WriteState(WorkflowExecutionContext workflowExecutionContext, WorkflowState state); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Elsa.Core.csproj b/src/core/Elsa.Core/Elsa.Core.csproj new file mode 100644 index 000000000..c7a8c3989 --- /dev/null +++ b/src/core/Elsa.Core/Elsa.Core.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + enable + Elsa + latest + + + + + + + + + + + + + + diff --git a/src/core/Elsa.Core/Elsa.Core.csproj.DotSettings b/src/core/Elsa.Core/Elsa.Core.csproj.DotSettings new file mode 100644 index 000000000..373203e26 --- /dev/null +++ b/src/core/Elsa.Core/Elsa.Core.csproj.DotSettings @@ -0,0 +1,3 @@ + + True + True \ No newline at end of file diff --git a/src/core/Elsa.Core/Exceptions/TypeConversionException.cs b/src/core/Elsa.Core/Exceptions/TypeConversionException.cs new file mode 100644 index 000000000..bbfd5c982 --- /dev/null +++ b/src/core/Elsa.Core/Exceptions/TypeConversionException.cs @@ -0,0 +1,19 @@ +namespace Elsa.Exceptions; + +public class TypeConversionException : Exception +{ + public object Value { get; } + public Type TargetType { get; } + + public TypeConversionException(string message, object value, Type targetType) : base(message) + { + Value = value; + TargetType = targetType; + } + + public TypeConversionException(string message, object value, Type targetType, Exception innerException) : base(message, innerException) + { + Value = value; + TargetType = targetType; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Expressions/DelegateExpression.cs b/src/core/Elsa.Core/Expressions/DelegateExpression.cs new file mode 100644 index 000000000..328dbe5f2 --- /dev/null +++ b/src/core/Elsa.Core/Expressions/DelegateExpression.cs @@ -0,0 +1,32 @@ +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Expressions; + +public class DelegateExpression : IExpression +{ + public DelegateExpression(DelegateReference delegateReference) + { + DelegateReference = delegateReference; + } + + public DelegateReference DelegateReference { get; } +} + +public class DelegateExpression : DelegateExpression +{ + public DelegateExpression(DelegateReference delegateReference) : base(delegateReference) + { + } +} + +public class DelegateExpressionHandler : IExpressionHandler +{ + public async ValueTask EvaluateAsync(IExpression expression, Type returnType, ExpressionExecutionContext context) + { + var delegateExpression = (DelegateExpression)expression; + var @delegate = delegateExpression.DelegateReference.Delegate; + var value = @delegate != null ? await @delegate(context) : default; + return value; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Expressions/ElsaExpression.cs b/src/core/Elsa.Core/Expressions/ElsaExpression.cs new file mode 100644 index 000000000..97c32d5cd --- /dev/null +++ b/src/core/Elsa.Core/Expressions/ElsaExpression.cs @@ -0,0 +1,25 @@ +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Expressions; + +public class ElsaExpression : IExpression +{ + public ElsaExpression(string scriptExpression) + { + ScriptExpression = scriptExpression; + } + + public string ScriptExpression { get; } +} + +public class ElsaExpressionHandler : IExpressionHandler +{ + public ValueTask EvaluateAsync(IExpression expression, Type returnType, ExpressionExecutionContext context) + { + var elsaExpression = (ElsaExpression)expression; + + // TODO: evaluate expression. + return new ValueTask(elsaExpression.ScriptExpression); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Expressions/JsonExpression.cs b/src/core/Elsa.Core/Expressions/JsonExpression.cs new file mode 100644 index 000000000..7397d7051 --- /dev/null +++ b/src/core/Elsa.Core/Expressions/JsonExpression.cs @@ -0,0 +1,33 @@ +using System.Text.Json; +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Expressions; + +public class JsonExpression : IExpression +{ + public JsonExpression(string? value) => Value = value; + public string? Value { get; } +} + +public class JsonExpression : LiteralExpression +{ + public JsonExpression(T? value) : base(value) + { + } +} + +public class JsonExpressionHandler : IExpressionHandler +{ + public ValueTask EvaluateAsync(IExpression expression, Type returnType, ExpressionExecutionContext context) + { + var jsonExpression = (JsonExpression)expression; + var value = jsonExpression.Value; + + if (string.IsNullOrWhiteSpace(value)) + return ValueTask.FromResult(default(object?)); + + var model = JsonSerializer.Deserialize(value, returnType); + return ValueTask.FromResult(model); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Expressions/LiteralExpression.cs b/src/core/Elsa.Core/Expressions/LiteralExpression.cs new file mode 100644 index 000000000..ac32f81b1 --- /dev/null +++ b/src/core/Elsa.Core/Expressions/LiteralExpression.cs @@ -0,0 +1,28 @@ +using Elsa.Contracts; +using Elsa.Helpers; +using Elsa.Models; + +namespace Elsa.Expressions; + +public class LiteralExpression : IExpression +{ + public LiteralExpression(object? value) => Value = value; + public object? Value { get; } +} + +public class LiteralExpression : LiteralExpression +{ + public LiteralExpression(T? value) : base(value) + { + } +} + +public class LiteralExpressionHandler : IExpressionHandler +{ + public ValueTask EvaluateAsync(IExpression expression, Type returnType, ExpressionExecutionContext context) + { + var literalExpression = (LiteralExpression)expression; + var value = literalExpression.Value.ConvertTo(returnType); + return ValueTask.FromResult(value); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Expressions/VariableExpression.cs b/src/core/Elsa.Core/Expressions/VariableExpression.cs new file mode 100644 index 000000000..4a521176e --- /dev/null +++ b/src/core/Elsa.Core/Expressions/VariableExpression.cs @@ -0,0 +1,36 @@ +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Expressions; + +public class VariableExpression : IExpression +{ + public VariableExpression(Variable variable) + { + Variable = variable; + } + + public VariableExpression() + { + } + + public Variable Variable { get; set; } = default!; +} + +public class VariableExpression : VariableExpression +{ + public VariableExpression(Variable variable) : base(variable) + { + } +} + +public class VariableExpressionHandler : IExpressionHandler +{ + public ValueTask EvaluateAsync(IExpression expression, Type returnType, ExpressionExecutionContext context) + { + var variableExpression = (VariableExpression)expression; + var variable = variableExpression.Variable; + var value = variable.Get(context); + return ValueTask.FromResult(value); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Extensions/ActivityDriverExtensions.cs b/src/core/Elsa.Core/Extensions/ActivityDriverExtensions.cs new file mode 100644 index 000000000..da514ed3d --- /dev/null +++ b/src/core/Elsa.Core/Extensions/ActivityDriverExtensions.cs @@ -0,0 +1,19 @@ +// using System; +// using System.Reflection; +// using Elsa.Contracts; +// +// namespace Elsa.Extensions +// { +// public static class ActivityDriverExtensions +// { +// public static TDelegate GetDelegate(this IActivityDriver driver, string methodName) where TDelegate : Delegate +// { +// var driverType = driver!.GetType(); +// var resumeMethodInfo = driverType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!; +// return (TDelegate)Delegate.CreateDelegate(typeof(TDelegate), driver, resumeMethodInfo); +// } +// +// public static ExecuteActivityDelegate GetResumeActivityDelegate(this IActivityDriver driver, string resumeMethodName) => driver.GetDelegate(resumeMethodName); +// public static ActivityCompletionCallback GetActivityCompletionCallback(this IActivityDriver driver, string completionMethodName) => driver.GetDelegate(completionMethodName); +// } +// } \ No newline at end of file diff --git a/src/core/Elsa.Core/Extensions/ActivityExtensions.cs b/src/core/Elsa.Core/Extensions/ActivityExtensions.cs new file mode 100644 index 000000000..3b8ab7fb0 --- /dev/null +++ b/src/core/Elsa.Core/Extensions/ActivityExtensions.cs @@ -0,0 +1,59 @@ +using System.Reflection; +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Extensions; + +public static class ActivityExtensions +{ + public static IEnumerable GetInputs(this IActivity activity) + { + var inputProps = activity.GetType().GetProperties().Where(x => typeof(Input).IsAssignableFrom(x.PropertyType)).ToList(); + + var query = + from inputProp in inputProps + select (Input?)inputProp.GetValue(activity) + into input + where input != null + select input; + + return query.Select(x => x!).ToList(); + } + + public static IEnumerable GetOutputs(this IActivity activity) + { + var outputProps = activity.GetType().GetProperties().Where(x => typeof(Output).IsAssignableFrom(x.PropertyType)).ToList(); + + var query = + from outputProp in outputProps + select (Output?)outputProp.GetValue(activity) + into output + where output != null + select output; + + return query.Select(x => x!).ToList(); + } + + + + public static IEnumerable GetVariables(this IActivity activity) + { + var properties = activity.GetType().GetProperties(); + var variableProps = properties.Where(x => typeof(Variable).IsAssignableFrom(x.PropertyType)).ToList(); + var variablesProps = properties.Where(x => typeof(IEnumerable).IsAssignableFrom(x.PropertyType)).ToList(); + var variables = variableProps.Select(x => (Variable?)x.GetValue(activity)).Where(x => x != null).Select(x => x!).ToList(); + var manyVariables = variablesProps.Select(x => (IEnumerable?)x.GetValue(activity)).Where(x => x != null).SelectMany(x => x!).ToList(); + + return variables.Concat(manyVariables).ToList(); + } + + public static TDelegate GetDelegate(this IActivity driver, string methodName) where TDelegate : Delegate + { + var driverType = driver!.GetType(); + var resumeMethodInfo = driverType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!; + return (TDelegate)Delegate.CreateDelegate(typeof(TDelegate), driver, resumeMethodInfo); + } + + public static ExecuteActivityDelegate GetResumeActivityDelegate(this IActivity driver, string resumeMethodName) => driver.GetDelegate(resumeMethodName); + public static ActivityCompletionCallback GetActivityCompletionCallback(this IActivity driver, string completionMethodName) => driver.GetDelegate(completionMethodName); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Extensions/NodeExtensions.cs b/src/core/Elsa.Core/Extensions/NodeExtensions.cs new file mode 100644 index 000000000..21b8d1c0c --- /dev/null +++ b/src/core/Elsa.Core/Extensions/NodeExtensions.cs @@ -0,0 +1,34 @@ +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Extensions; + +public static class NodeExtensions +{ + public static IEnumerable GetInputs(this INode node) + { + var inputProps = node.GetType().GetProperties().Where(x => typeof(Input).IsAssignableFrom(x.PropertyType)).ToList(); + + var query = + from inputProp in inputProps + select (Input?)inputProp.GetValue(node) + into input + where input != null + select input; + + return query.Select(x => x!).ToList(); + } + + public static IEnumerable Flatten(this ActivityNode root) + { + yield return root; + + foreach (var node in root.Children) + { + var children = node.Flatten(); + + foreach (var child in children) + yield return child; + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Extensions/ServiceCollectionExtensions.cs b/src/core/Elsa.Core/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..b11d2093a --- /dev/null +++ b/src/core/Elsa.Core/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,22 @@ +using Elsa.Contracts; +using Elsa.Options; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddExpressionHandler(this IServiceCollection services) where THandler : class, IExpressionHandler => + services.AddExpressionHandler(typeof(TExpression)); + + public static IServiceCollection AddExpressionHandler(this IServiceCollection services, Type expression) where THandler : class, IExpressionHandler + { + // Register handler with DI. + services.AddSingleton(); + + // Register handler with options. + services.Configure(elsa => elsa.RegisterExpressionHandler(expression)); + + return services; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Helpers/ObjectConverter.cs b/src/core/Elsa.Core/Helpers/ObjectConverter.cs new file mode 100644 index 000000000..039cca57b --- /dev/null +++ b/src/core/Elsa.Core/Helpers/ObjectConverter.cs @@ -0,0 +1,58 @@ +using System.ComponentModel; +using Elsa.Exceptions; + +namespace Elsa.Helpers; + +public static class ObjectConverter +{ + public static T? ConvertTo(this object? value) => value != null ? (T?)value.ConvertTo(typeof(T)) : default; + + public static object? ConvertTo(this object? value, Type targetType) + { + if (value == null) + return default!; + + var sourceType = value.GetType(); + + if (sourceType == targetType) + return value; + + var underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType; + + if (targetType == typeof(object)) + return value; + + if (underlyingTargetType.IsInstanceOfType(value)) + return value; + + var underlyingSourceType = Nullable.GetUnderlyingType(sourceType) ?? sourceType; + + if (underlyingSourceType == underlyingTargetType) + return value; + + var targetTypeConverter = TypeDescriptor.GetConverter(underlyingTargetType); + + if (targetTypeConverter.CanConvertFrom(underlyingSourceType)) + return targetTypeConverter.ConvertFrom(value); + + var sourceTypeConverter = TypeDescriptor.GetConverter(underlyingSourceType); + + if (sourceTypeConverter.CanConvertTo(underlyingTargetType)) + return sourceTypeConverter.ConvertTo(value, underlyingTargetType); + + if (underlyingTargetType.IsEnum) + { + if (underlyingSourceType != typeof(string)) + return Enum.ToObject(underlyingTargetType, value); + } + + try + { + return Convert.ChangeType(value, underlyingTargetType); + } + catch (InvalidCastException e) + { + throw new TypeConversionException($"Failed to convert an object of type {sourceType} to {underlyingTargetType}", value, underlyingTargetType, e); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Helpers/TypeNameHelper.cs b/src/core/Elsa.Core/Helpers/TypeNameHelper.cs new file mode 100644 index 000000000..8eaff76ff --- /dev/null +++ b/src/core/Elsa.Core/Helpers/TypeNameHelper.cs @@ -0,0 +1,44 @@ +namespace Elsa.Helpers; + +public static class TypeNameHelper +{ + private static readonly string[] WellknownNamespaces = { "Elsa.Activities", "Elsa.Triggers" }; + + public static string? GenerateNamespace(Type activityType) => GenerateTypeNamespace(activityType); + + public static string GenerateTypeName(Type type, string? ns) + { + var typeName = type.Name; + return ns != null ? $"{ns}.{typeName}" : typeName; + } + + public static string GenerateTypeName() => GenerateTypeName(typeof(T)); + + public static string GenerateTypeName(Type type) + { + var ns = GenerateNamespace(type); + return GenerateTypeName(type, ns); + } + + public static string? GetCategoryFromNamespace(string? ns) + { + if (string.IsNullOrWhiteSpace(ns)) + return null; + + var index = ns.LastIndexOf('.'); + + return index < 0 ? ns : ns[(index + 1)..]; + } + + private static string? GenerateTypeNamespace(Type type) + { + if (type.Namespace == null) + return null; + + foreach (var wellknownNamespace in WellknownNamespaces) + if (type.Namespace.StartsWith(wellknownNamespace)) + return type.Namespace[(wellknownNamespace.Length + 1)..]; + + return type.Namespace; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/IsExternalInit.cs b/src/core/Elsa.Core/IsExternalInit.cs new file mode 100644 index 000000000..dfaa576cd --- /dev/null +++ b/src/core/Elsa.Core/IsExternalInit.cs @@ -0,0 +1,17 @@ +// // Licensed to the .NET Foundation under one or more agreements. +// // The .NET Foundation licenses this file to you under the MIT license. +// +// using System.ComponentModel; +// +// // ReSharper disable once CheckNamespace +// namespace System.Runtime.CompilerServices +// { +// /// +// /// Reserved to be used by the compiler for tracking metadata. +// /// This class should not be used by developers in source code. +// /// +// [EditorBrowsable(EditorBrowsableState.Never)] +// internal static class IsExternalInit +// { +// } +// } \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/Activity.cs b/src/core/Elsa.Core/Models/Activity.cs new file mode 100644 index 000000000..b43bb2e28 --- /dev/null +++ b/src/core/Elsa.Core/Models/Activity.cs @@ -0,0 +1,33 @@ +using Elsa.Contracts; +using Elsa.Helpers; + +namespace Elsa.Models; + +public abstract class Activity : IActivity +{ + protected Activity() => NodeType = TypeNameHelper.GenerateTypeName(GetType()); + protected Activity(string activityType) => NodeType = activityType; + + public string Id { get; set; } = default!; + public string NodeType { get; set; } + public IDictionary Metadata { get; set; } = new Dictionary(); + + public virtual ValueTask ExecuteAsync(ActivityExecutionContext context) + { + Execute(context); + return ValueTask.CompletedTask; + } + + protected virtual void Execute(ActivityExecutionContext context) + { + } +} + +public abstract class ActivityWithResult : Activity +{ + public Output? Result { get; set; } +} + +public abstract class Activity : ActivityWithResult +{ +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/ActivityExecutionContext.cs b/src/core/Elsa.Core/Models/ActivityExecutionContext.cs new file mode 100644 index 000000000..aa5f4c20e --- /dev/null +++ b/src/core/Elsa.Core/Models/ActivityExecutionContext.cs @@ -0,0 +1,107 @@ +using System.Collections.ObjectModel; +using Elsa.Contracts; + +namespace Elsa.Models; + +public class ActivityExecutionContext +{ + private readonly List _bookmarks = new(); + + public ActivityExecutionContext( + WorkflowExecutionContext workflowExecutionContext, + ActivityExecutionContext? parentActivityExecutionContext, + ExpressionExecutionContext expressionExecutionContext, + IActivity activity, + CancellationToken cancellationToken) + { + WorkflowExecutionContext = workflowExecutionContext; + ParentActivityExecutionContext = parentActivityExecutionContext; + ExpressionExecutionContext = expressionExecutionContext; + Activity = activity; + CancellationToken = cancellationToken; + Id = Guid.NewGuid().ToString(); + } + + public string Id { get; set; } + public WorkflowExecutionContext WorkflowExecutionContext { get; } + public ActivityExecutionContext? ParentActivityExecutionContext { get; internal set; } + public ExpressionExecutionContext ExpressionExecutionContext { get; } + public IActivity Activity { get; set; } + public CancellationToken CancellationToken { get; } + public IDictionary Properties { get; set; } = new Dictionary(); + public ActivityNode ActivityNode => WorkflowExecutionContext.FindNodeByActivity(Activity); + public IReadOnlyCollection Bookmarks => new ReadOnlyCollection(_bookmarks); + public bool Continue { get; set; } = true; + + public void ScheduleActivity(IActivity activity, ActivityCompletionCallback? completionCallback = default, IEnumerable? locationReferences = default, object? tag = default) => + WorkflowExecutionContext.Schedule(activity, this, completionCallback, locationReferences, tag); + + public void ScheduleActivity(IActivity activity, ActivityExecutionContext owner, ActivityCompletionCallback? completionCallback = default, IEnumerable? locationReferences = default, object? tag = default) => + WorkflowExecutionContext.Schedule(activity, owner, completionCallback, locationReferences, tag); + + public void ScheduleActivities(params IActivity[] activities) => ScheduleActivities((IEnumerable)activities); + + public void ScheduleActivities(IEnumerable activities, ActivityCompletionCallback? completionCallback = default) + { + foreach (var activity in activities) + ScheduleActivity(activity, completionCallback); + } + + public void SetBookmarks(IEnumerable bookmarks) => _bookmarks.AddRange(bookmarks); + public void SetBookmark(Bookmark bookmark) => _bookmarks.Add(bookmark); + + public void SetBookmark(string? hash, IDictionary? data = default, ExecuteActivityDelegate? callback = default) => + SetBookmark(new Bookmark( + Guid.NewGuid().ToString(), + Activity.NodeType, + hash, + Activity.Id, + Id, + data ?? new Dictionary(), + callback?.Method.Name)); + + public T? GetProperty(string key) => Properties.TryGetValue(key, out var value) ? (T?)value : default; + public void SetProperty(string key, T value) => Properties[key] = value; + + public T UpdateProperty(string key, Func updater) + { + var value = GetProperty(key); + value = updater(value); + Properties[key] = value; + return value; + } + + public T GetRequiredService() where T : notnull => WorkflowExecutionContext.GetRequiredService(); + public T? Get(Input input) => Get(input.LocationReference); + + public object? Get(RegisterLocationReference locationReference) + { + var location = GetLocation(locationReference) ?? throw new InvalidOperationException($"No location found with ID {locationReference.Id}. Did you forget to declare a variable with a container?"); + return location.Value; + } + + public T? Get(RegisterLocationReference locationReference) + { + var value = Get(locationReference); + return value != default ? (T?)(value) : default; + } + + public void Set(RegisterLocationReference locationReference, object? value) => ExpressionExecutionContext.Set(locationReference, value); + public void Set(Output? output, object? value) => ExpressionExecutionContext.Set(output, value); + + public async Task EvaluateAsync(Input input) + { + var evaluator = GetRequiredService(); + var locationReference = input.LocationReference; + var value = await evaluator.EvaluateAsync(input, ExpressionExecutionContext); + locationReference.Set(this, value); + return (T?)value; + } + + private RegisterLocation? GetLocation(RegisterLocationReference locationReference) => + ExpressionExecutionContext.Register.TryGetLocation(locationReference.Id, out var location) + ? location + : ParentActivityExecutionContext?.GetLocation(locationReference); + + public void PreventContinuation() => Continue = false; +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/ActivityNode.cs b/src/core/Elsa.Core/Models/ActivityNode.cs new file mode 100644 index 000000000..8eae543cb --- /dev/null +++ b/src/core/Elsa.Core/Models/ActivityNode.cs @@ -0,0 +1,45 @@ +using Elsa.Contracts; + +namespace Elsa.Models; + +public class ActivityNode +{ + public ActivityNode(IActivity activity) + { + Activity = activity; + } + + public string NodeId => Activity.Id; + public IActivity Activity { get; } + public ICollection Parents { get; set; } = new List(); + public ICollection Children { get; set; } = new List(); + + public IEnumerable Descendants() + { + foreach (var child in Children) + { + yield return child; + + var descendants = child.Descendants(); + + foreach (var descendant in descendants) + yield return descendant; + } + } + + public IEnumerable Ancestors() + { + foreach (var parent in Parents) + { + yield return parent; + + var ancestors = parent.Ancestors(); + + foreach (var ancestor in ancestors) + yield return ancestor; + } + } + + public IEnumerable Siblings() => Parents.SelectMany(parent => parent.Children); + public IEnumerable SiblingsAndCousins() => Parents.SelectMany(parent => parent.Descendants()); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/ActivityWorkItem.cs b/src/core/Elsa.Core/Models/ActivityWorkItem.cs new file mode 100644 index 000000000..4acdca058 --- /dev/null +++ b/src/core/Elsa.Core/Models/ActivityWorkItem.cs @@ -0,0 +1,15 @@ +namespace Elsa.Models; + +public class ActivityWorkItem +{ + public ActivityWorkItem(string activityId, Func execute, object? tag = default) + { + ActivityId = activityId; + Execute = execute; + Tag = tag; + } + + public string ActivityId { get; } + public Func Execute { get; } + public object? Tag { get; set; } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/Argument.cs b/src/core/Elsa.Core/Models/Argument.cs new file mode 100644 index 000000000..ea5d4f96e --- /dev/null +++ b/src/core/Elsa.Core/Models/Argument.cs @@ -0,0 +1,13 @@ +namespace Elsa.Models; + +public abstract class Argument +{ + protected Argument(RegisterLocationReference locationReference, Func? valueConverter = default) + { + LocationReference = locationReference; + ValueConverter = valueConverter; + } + + public RegisterLocationReference LocationReference { get; set; } + public Func? ValueConverter { get; set; } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/Bookmark.cs b/src/core/Elsa.Core/Models/Bookmark.cs new file mode 100644 index 000000000..41b16909e --- /dev/null +++ b/src/core/Elsa.Core/Models/Bookmark.cs @@ -0,0 +1,11 @@ +namespace Elsa.Models; + +public record Bookmark( + string Id, + string Name, + string? Hash, + string ActivityId, + string ActivityInstanceId, + IDictionary? Data = default, + string? CallbackMethodName = default +); \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/Composite.cs b/src/core/Elsa.Core/Models/Composite.cs new file mode 100644 index 000000000..1b9144bbf --- /dev/null +++ b/src/core/Elsa.Core/Models/Composite.cs @@ -0,0 +1,17 @@ +using Elsa.Activities.Workflows; +using Elsa.Attributes; +using Elsa.Contracts; + +namespace Elsa.Models; + +public abstract class Composite : Activity +{ + [Outbound] public IActivity Root { get; protected set; } = new Sequence(); + + protected override void Execute(ActivityExecutionContext context) + { + context.ScheduleActivity(Root, OnCompletedAsync); + } + + protected virtual ValueTask OnCompletedAsync(ActivityExecutionContext context, ActivityExecutionContext childContext) => ValueTask.CompletedTask; +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/DelegateReference.cs b/src/core/Elsa.Core/Models/DelegateReference.cs new file mode 100644 index 000000000..65c333ced --- /dev/null +++ b/src/core/Elsa.Core/Models/DelegateReference.cs @@ -0,0 +1,34 @@ +namespace Elsa.Models; + +public class DelegateReference : RegisterLocationReference +{ + public DelegateReference() + { + } + + public DelegateReference(Func @delegate) => Delegate = _ => ValueTask.FromResult(@delegate()); + public DelegateReference(Func @delegate) => Delegate = x => ValueTask.FromResult(@delegate(x)); + public DelegateReference(Func> @delegate) => Delegate = @delegate; + + public Func>? Delegate { get; set; } + public override RegisterLocation Declare() => new(); +} + +public class DelegateReference : DelegateReference +{ + public DelegateReference() + { + } + + public DelegateReference(Func @delegate) : base(x => @delegate()) + { + } + + public DelegateReference(Func @delegate) : base(x => @delegate(x)) + { + } + + public DelegateReference(Func> @delegate) : base(x => @delegate(x)) + { + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/DispatchWorkflowResult.cs b/src/core/Elsa.Core/Models/DispatchWorkflowResult.cs new file mode 100644 index 000000000..050a1e470 --- /dev/null +++ b/src/core/Elsa.Core/Models/DispatchWorkflowResult.cs @@ -0,0 +1,3 @@ +namespace Elsa.Models; + +public record DispatchWorkflowResult(); \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/DynamicActivity.cs b/src/core/Elsa.Core/Models/DynamicActivity.cs new file mode 100644 index 000000000..d6c4a8852 --- /dev/null +++ b/src/core/Elsa.Core/Models/DynamicActivity.cs @@ -0,0 +1,14 @@ +using Elsa.Contracts; + +namespace Elsa.Models; + +/// +/// A dynamically provided activity with custom properties. +/// +public class DynamicActivity : Activity +{ + public IDictionary Properties { get; set; } = new Dictionary(); + public ExecuteActivityDelegate ExecuteHandler { get; set; } = _ => ValueTask.CompletedTask; + + public override ValueTask ExecuteAsync(ActivityExecutionContext context) => ExecuteHandler(context); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/ElsaExpressionReference.cs b/src/core/Elsa.Core/Models/ElsaExpressionReference.cs new file mode 100644 index 000000000..fb1dc8b3d --- /dev/null +++ b/src/core/Elsa.Core/Models/ElsaExpressionReference.cs @@ -0,0 +1,15 @@ +using Elsa.Expressions; + +namespace Elsa.Models; + +public class ElsaExpressionReference : RegisterLocationReference +{ + public ElsaExpressionReference(ElsaExpression expression) + { + Expression = expression; + } + + public ElsaExpression Expression { get; } + + public override RegisterLocation Declare() => new(); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/ExecuteWorkflowResult.cs b/src/core/Elsa.Core/Models/ExecuteWorkflowResult.cs new file mode 100644 index 000000000..08352a0cb --- /dev/null +++ b/src/core/Elsa.Core/Models/ExecuteWorkflowResult.cs @@ -0,0 +1,5 @@ +using Elsa.State; + +namespace Elsa.Models; + +public record ExecuteWorkflowResult(WorkflowState WorkflowState, IReadOnlyCollection Bookmarks); \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/ExpressionExecutionContext.cs b/src/core/Elsa.Core/Models/ExpressionExecutionContext.cs new file mode 100644 index 000000000..9dea97b22 --- /dev/null +++ b/src/core/Elsa.Core/Models/ExpressionExecutionContext.cs @@ -0,0 +1,35 @@ +namespace Elsa.Models; + +public class ExpressionExecutionContext +{ + public ExpressionExecutionContext(Register register, ExpressionExecutionContext? parentContext) + { + Register = register; + ParentContext = parentContext; + } + + public Register Register { get; } + public ExpressionExecutionContext? ParentContext { get; set; } + + public RegisterLocation GetLocation(RegisterLocationReference locationReference) => GetLocationInternal(locationReference) ?? throw new InvalidOperationException(); + public object Get(RegisterLocationReference locationReference) => GetLocation(locationReference).Value!; + public T Get(RegisterLocationReference locationReference) => (T)Get(locationReference); + public T? Get(Input input) => (T?)GetLocation(input.LocationReference).Value; + + public void Set(RegisterLocationReference locationReference, object? value) + { + var location = GetLocationInternal(locationReference) ?? Register.Declare(locationReference); + location.Value = value; + } + + public void Set(Output? output, object? value) + { + if (output?.LocationReference == null) + return; + + var convertedValue = output.ValueConverter?.Invoke(value) ?? value; + Set(output.LocationReference, convertedValue); + } + + private RegisterLocation? GetLocationInternal(RegisterLocationReference locationReference) => Register.TryGetLocation(locationReference.Id, out var location) ? location : ParentContext?.GetLocationInternal(locationReference); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/Input.cs b/src/core/Elsa.Core/Models/Input.cs new file mode 100644 index 000000000..e463a1ff7 --- /dev/null +++ b/src/core/Elsa.Core/Models/Input.cs @@ -0,0 +1,59 @@ +using Elsa.Contracts; +using Elsa.Expressions; + +namespace Elsa.Models; + +public abstract class Input : Argument +{ + protected Input(IExpression expression, RegisterLocationReference locationReference, Type type) : base(locationReference) + { + Expression = expression; + Type = type; + } + + public IExpression Expression { get; } + public Type Type { get; set; } +} + +public class Input : Input +{ + public Input(T literal) : this(new Literal(literal)) + { + } + + public Input(Func @delegate) : this(new DelegateReference(() => @delegate())) + { + } + + public Input(Func @delegate) : this(new DelegateReference(@delegate)) + { + } + + public Input(Variable variable) : base(new VariableExpression(variable), variable, typeof(T)) + { + } + + public Input(Literal literal) : base(new LiteralExpression(literal.Value), literal, typeof(T)) + { + } + + public Input(Literal literal) : base(new LiteralExpression(literal.Value), literal, typeof(T)) + { + } + + public Input(DelegateReference delegateReference) : base(new DelegateExpression(delegateReference), delegateReference, typeof(T)) + { + } + + public Input(ElsaExpression expression) : this(new ElsaExpressionReference(expression)) + { + } + + public Input(IExpression expression, RegisterLocationReference locationReference) : base(expression, locationReference, typeof(T)) + { + } + + private Input(ElsaExpressionReference expressionReference) : base(expressionReference.Expression, expressionReference, typeof(T)) + { + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/JsonObject.cs b/src/core/Elsa.Core/Models/JsonObject.cs new file mode 100644 index 000000000..8c7768164 --- /dev/null +++ b/src/core/Elsa.Core/Models/JsonObject.cs @@ -0,0 +1,17 @@ +namespace Elsa.Models; + +public class JsonObject : RegisterLocationReference +{ + public JsonObject() + { + } + + public JsonObject(string? defaultValue) + { + DefaultValue = defaultValue; + } + + public string? Name { get; set; } + public string? DefaultValue { get; } + public override RegisterLocation Declare() => new(DefaultValue); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/Literal.cs b/src/core/Elsa.Core/Models/Literal.cs new file mode 100644 index 000000000..5fb4dc7e9 --- /dev/null +++ b/src/core/Elsa.Core/Models/Literal.cs @@ -0,0 +1,31 @@ +namespace Elsa.Models; + +public class Literal : RegisterLocationReference +{ + public Literal() + { + } + + public Literal(object? value) + { + Value = value; + } + + public object? Value { get; } + public override RegisterLocation Declare() => new(); + + public static Literal From(T value) => new Literal(value); +} + +public class Literal : Literal +{ + public Literal() + { + } + + public Literal(T value) : base(value!) + { + } + + public new T? Get(ActivityExecutionContext context) => (T?)base.Get(context); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/NodeIdentity.cs b/src/core/Elsa.Core/Models/NodeIdentity.cs new file mode 100644 index 000000000..cce4c2ac7 --- /dev/null +++ b/src/core/Elsa.Core/Models/NodeIdentity.cs @@ -0,0 +1,3 @@ +namespace Elsa.Models; + +//public record NodeIdentity(ActivityNode ActivityNode, string NodeName); \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/Output.cs b/src/core/Elsa.Core/Models/Output.cs new file mode 100644 index 000000000..4127b8425 --- /dev/null +++ b/src/core/Elsa.Core/Models/Output.cs @@ -0,0 +1,15 @@ +namespace Elsa.Models; + +public class Output : Argument +{ + public Output(RegisterLocationReference locationReference, Func? valueConverter = default) : base(locationReference, valueConverter) + { + } +} + +public class Output : Output +{ + public Output(RegisterLocationReference locationReference, Func? valueConverter = default) : base(locationReference, valueConverter) + { + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/Register.cs b/src/core/Elsa.Core/Models/Register.cs new file mode 100644 index 000000000..6703d175c --- /dev/null +++ b/src/core/Elsa.Core/Models/Register.cs @@ -0,0 +1,33 @@ +namespace Elsa.Models; + +/// +/// Represents a register of memory. +/// +public class Register +{ + public Register(IDictionary? locations = default) + { + Locations = locations ?? new Dictionary(); + } + + public IDictionary Locations { get; } + + public bool TryGetLocation(string id, out RegisterLocation location) + { + location = null!; + return Locations.TryGetValue(id, out location!); + } + + public void Declare(IEnumerable references) + { + foreach (var reference in references) + Declare(reference); + } + + public RegisterLocation Declare(RegisterLocationReference reference) + { + var location = reference.Declare(); + Locations[reference.Id] = location; + return location; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/RegisterLocation.cs b/src/core/Elsa.Core/Models/RegisterLocation.cs new file mode 100644 index 000000000..fa7e6aa57 --- /dev/null +++ b/src/core/Elsa.Core/Models/RegisterLocation.cs @@ -0,0 +1,18 @@ +namespace Elsa.Models; + +/// +/// Represents a location within a register +/// +public class RegisterLocation +{ + public RegisterLocation() + { + } + + public RegisterLocation(object? value) + { + Value = value; + } + + public object? Value { get; set; } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/RegisterLocationReference.cs b/src/core/Elsa.Core/Models/RegisterLocationReference.cs new file mode 100644 index 000000000..fa8d54672 --- /dev/null +++ b/src/core/Elsa.Core/Models/RegisterLocationReference.cs @@ -0,0 +1,21 @@ +namespace Elsa.Models; + +public abstract class RegisterLocationReference +{ + protected RegisterLocationReference() + { + } + + protected RegisterLocationReference(string id) => Id = id; + + public string Id { get; set; } = default!; + public abstract RegisterLocation Declare(); + public object? Get(ExpressionExecutionContext context) => context.Get(this); + public object? Get(ActivityExecutionContext context) => Get(context.ExpressionExecutionContext); + public T? Get(ExpressionExecutionContext context) => (T?)Get(context); + public T? Get(ActivityExecutionContext context) => (T?)Get(context.ExpressionExecutionContext); + public void Set(ExpressionExecutionContext context, object? value) => context.Set(this, value); + public void Set(ActivityExecutionContext context, object? value) => Set(context.ExpressionExecutionContext, value); + + public RegisterLocation GetLocation(Register register) => register.TryGetLocation(Id, out var location) ? location : register.Declare(this); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/Trigger.cs b/src/core/Elsa.Core/Models/Trigger.cs new file mode 100644 index 000000000..a45be0a98 --- /dev/null +++ b/src/core/Elsa.Core/Models/Trigger.cs @@ -0,0 +1,28 @@ +using Elsa.Contracts; + +namespace Elsa.Models; + +public class Trigger : Activity, ITrigger +{ + protected Trigger() + { + } + + protected Trigger(string triggerType) : base(triggerType) + { + } + + public string TriggerType + { + get => NodeType; + set => NodeType = value; + } + + public virtual ValueTask> GetHashInputsAsync(TriggerIndexingContext context, CancellationToken cancellationToken = default) + { + var hashes = GetHashInputs(context); + return ValueTask.FromResult(hashes); + } + + protected virtual IEnumerable GetHashInputs(TriggerIndexingContext context) => Enumerable.Empty(); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/TriggerIndexingContext.cs b/src/core/Elsa.Core/Models/TriggerIndexingContext.cs new file mode 100644 index 000000000..d42a27cef --- /dev/null +++ b/src/core/Elsa.Core/Models/TriggerIndexingContext.cs @@ -0,0 +1,17 @@ +using Elsa.Contracts; + +namespace Elsa.Models; + +public class TriggerIndexingContext +{ + public WorkflowIndexingContext WorkflowIndexingContext { get; } + public ExpressionExecutionContext ExpressionExecutionContext { get; } + public ITrigger Trigger { get; } + + public TriggerIndexingContext(WorkflowIndexingContext workflowIndexingContext, ExpressionExecutionContext expressionExecutionContext, ITrigger trigger) + { + WorkflowIndexingContext = workflowIndexingContext; + ExpressionExecutionContext = expressionExecutionContext; + Trigger = trigger; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/TriggerType.cs b/src/core/Elsa.Core/Models/TriggerType.cs new file mode 100644 index 000000000..d44734dd1 --- /dev/null +++ b/src/core/Elsa.Core/Models/TriggerType.cs @@ -0,0 +1,3 @@ +namespace Elsa.Models; + +public record TriggerType(string TypeName, Type Type); \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/TypeDescriptor.cs b/src/core/Elsa.Core/Models/TypeDescriptor.cs new file mode 100644 index 000000000..05a698bc4 --- /dev/null +++ b/src/core/Elsa.Core/Models/TypeDescriptor.cs @@ -0,0 +1,3 @@ +namespace Elsa.Models; + +public record TypeDescriptor(string Name, Type Type, TypeKind Kind); \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/TypeKind.cs b/src/core/Elsa.Core/Models/TypeKind.cs new file mode 100644 index 000000000..e4e9046b8 --- /dev/null +++ b/src/core/Elsa.Core/Models/TypeKind.cs @@ -0,0 +1,11 @@ +namespace Elsa.Models; + +[Flags] +public enum TypeKind +{ + Unknown = 0, + Primitive = 1, + Object = 2, + Activity = 4, + Trigger = 8 +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/Variable.cs b/src/core/Elsa.Core/Models/Variable.cs new file mode 100644 index 000000000..e427c3a7b --- /dev/null +++ b/src/core/Elsa.Core/Models/Variable.cs @@ -0,0 +1,30 @@ +namespace Elsa.Models; + +public class Variable : RegisterLocationReference +{ + public Variable() + { + } + + public Variable(object? defaultValue) + { + DefaultValue = defaultValue; + } + + public string? Name { get; set; } + public object? DefaultValue { get; } + public override RegisterLocation Declare() => new(DefaultValue); +} + +public class Variable : Variable +{ + public Variable() : base(default(T)) + { + } + + public Variable(T value) : base(value ?? default) + { + } + + public new T? Get(ExpressionExecutionContext context) => (T?)base.Get(context); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/Workflow.cs b/src/core/Elsa.Core/Models/Workflow.cs new file mode 100644 index 000000000..6c4848359 --- /dev/null +++ b/src/core/Elsa.Core/Models/Workflow.cs @@ -0,0 +1,21 @@ +using Elsa.Contracts; + +namespace Elsa.Models; + +public record Workflow( + WorkflowIdentity Identity, + WorkflowPublication Publication, + WorkflowMetadata Metadata, + IActivity Root, + ICollection Triggers) +{ + public static Workflow FromActivity(IActivity root) => new(WorkflowIdentity.VersionOne, WorkflowPublication.LatestDraft, new WorkflowMetadata(), root, new List()); + + public Workflow WithVersion(int version) => this with { Identity = Identity with { Version = version } }; + public Workflow IncrementVersion() => WithVersion(Identity.Version + 1); + public Workflow WithPublished(bool value = true) => this with { Publication = Publication with { IsPublished = value } }; + public Workflow WithLatest(bool value = true) => this with { Publication = Publication with { IsLatest = value } }; + public Workflow WithId(string value) => this with { Identity = Identity with { Id = value } }; + public Workflow WithDefinitionId(string value) => this with { Identity = Identity with { DefinitionId = value } }; + +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/WorkflowExecutionContext.cs b/src/core/Elsa.Core/Models/WorkflowExecutionContext.cs new file mode 100644 index 000000000..3f2502fb1 --- /dev/null +++ b/src/core/Elsa.Core/Models/WorkflowExecutionContext.cs @@ -0,0 +1,133 @@ +using System.Collections.ObjectModel; +using Elsa.Contracts; +using Elsa.Extensions; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Models; + +//public record ActivityCompletionCallbackEntry(ActivityExecutionContext Owner, IActivity Child, ActivityCompletionCallback CompletionCallback); +public record ActivityCompletionCallbackEntry(ActivityExecutionContext Owner, IActivity Child, ActivityCompletionCallback CompletionCallback); + +public class WorkflowExecutionContext +{ + private static ValueTask Noop(ActivityExecutionContext context) => new(); + private readonly IServiceProvider _serviceProvider; + private readonly IList _nodes; + private readonly IList _completionCallbackEntries = new List(); + private readonly List _bookmarks = new(); + + public WorkflowExecutionContext( + IServiceProvider serviceProvider, + Workflow workflow, + ActivityNode graph, + IActivityScheduler scheduler, + Bookmark? bookmark, + ExecuteActivityDelegate? executeDelegate, + CancellationToken cancellationToken) + { + _serviceProvider = serviceProvider; + Workflow = workflow; + Graph = graph; + Id = Guid.NewGuid().ToString("N"); + _nodes = graph.Flatten().Distinct().ToList(); + Scheduler = scheduler; + Bookmark = bookmark; + ExecuteDelegate = executeDelegate; + CancellationToken = cancellationToken; + NodeIdLookup = _nodes.ToDictionary(x => x.NodeId); + NodeActivityLookup = _nodes.ToDictionary(x => x.Activity); + } + + public Workflow Workflow { get; } + public ActivityNode Graph { get; set; } + public string Id { get; set; } + public IReadOnlyCollection Nodes => new ReadOnlyCollection(_nodes); + public IDictionary NodeIdLookup { get; } + public IDictionary NodeActivityLookup { get; } + public IActivityScheduler Scheduler { get; } + public Bookmark? Bookmark { get; } + public IDictionary Properties { get; set; } = new Dictionary(); + public ExecuteActivityDelegate? ExecuteDelegate { get; set; } + public CancellationToken CancellationToken { get; } + public IReadOnlyCollection Bookmarks => new ReadOnlyCollection(_bookmarks); + public IReadOnlyCollection CompletionCallbacks => new ReadOnlyCollection(_completionCallbackEntries); + public ICollection ActivityExecutionContexts { get; set; } = new List(); + + /// + /// A volatile collection of executed activity instance IDs. This collection is reset when workflow execution starts. + /// + public ICollection ExecutionLog { get; } = new List(); + + public T GetRequiredService() where T : notnull => _serviceProvider.GetRequiredService(); + + public void Schedule(IActivity activity, ActivityExecutionContext owner, ActivityCompletionCallback? completionCallback = default, IEnumerable? locationReferences = default, object? tag = default) + { + var activityInvoker = GetRequiredService(); + var workItem = new ActivityWorkItem(activity.Id, async () => await activityInvoker.InvokeAsync(this, activity, owner, locationReferences), tag); + Scheduler.Push(workItem); + + if (completionCallback != null) + AddCompletionCallback(owner, activity, completionCallback); + } + + public void AddCompletionCallback(ActivityExecutionContext owner, IActivity child, ActivityCompletionCallback completionCallback) + { + var entry = new ActivityCompletionCallbackEntry(owner, child, completionCallback); + _completionCallbackEntries.Add(entry); + } + + public ActivityCompletionCallback? PopCompletionCallback(ActivityExecutionContext owner, IActivity child) + { + var entry = _completionCallbackEntries.FirstOrDefault(x => x.Owner == owner && x.Child == child); + + if (entry == null) + return default; + + _completionCallbackEntries.Remove(entry); + return entry.CompletionCallback; + } + + public ActivityNode FindNodeById(string nodeId) => NodeIdLookup[nodeId]; + public ActivityNode FindNodeByActivity(IActivity activity) => NodeActivityLookup[activity]; + public IActivity FindActivityById(string activityId) => FindNodeById(activityId).Activity; + public T? GetProperty(string key) => Properties.TryGetValue(key, out var value) ? (T?)value : default(T); + public void SetProperty(string key, T value) => Properties[key] = value; + + public T UpdateProperty(string key, Func updater) + { + var value = GetProperty(key); + value = updater(value); + Properties[key] = value; + return value; + } + + public void RegisterBookmarks(IEnumerable bookmarks) => _bookmarks.AddRange(bookmarks); + + public void UnregisterBookmarks(IEnumerable bookmarks) + { + foreach (var bookmark in bookmarks) + _bookmarks.Remove(bookmark); + } + + public void ScheduleRoot() + { + var activityInvoker = GetRequiredService(); + var workItem = new ActivityWorkItem(Workflow.Root.Id, async () => await activityInvoker.InvokeAsync(this, Workflow.Root)); + Scheduler.Push(workItem); + } + + public void ScheduleBookmark(Bookmark bookmark) + { + // Construct bookmark. + var bookmarkedActivityContext = ActivityExecutionContexts.First(x => x.Id == bookmark.ActivityInstanceId); + var bookmarkedActivity = bookmarkedActivityContext.Activity; + + // Schedule the activity to resume. + var activityInvoker = GetRequiredService(); + var workItem = new ActivityWorkItem(bookmarkedActivity.Id, async () => await activityInvoker.InvokeAsync(bookmarkedActivityContext)); + Scheduler.Push(workItem); + + // If no resumption point was specified, use Noop to prevent the regular "ExecuteAsync" method to be invoked. + ExecuteDelegate = bookmark.CallbackMethodName != null ? bookmarkedActivity.GetResumeActivityDelegate(bookmark.CallbackMethodName) : Noop; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/WorkflowExecutionLogEntry.cs b/src/core/Elsa.Core/Models/WorkflowExecutionLogEntry.cs new file mode 100644 index 000000000..99dc92f52 --- /dev/null +++ b/src/core/Elsa.Core/Models/WorkflowExecutionLogEntry.cs @@ -0,0 +1,3 @@ +namespace Elsa.Models; + +public record WorkflowExecutionLogEntry( string ActivityInstanceId); \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/WorkflowIdentity.cs b/src/core/Elsa.Core/Models/WorkflowIdentity.cs new file mode 100644 index 000000000..2e5be09c5 --- /dev/null +++ b/src/core/Elsa.Core/Models/WorkflowIdentity.cs @@ -0,0 +1,6 @@ +namespace Elsa.Models; + +public record WorkflowIdentity(string DefinitionId, int Version, string Id) +{ + public static WorkflowIdentity VersionOne => new("1", 1, "1"); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/WorkflowIndexingContext.cs b/src/core/Elsa.Core/Models/WorkflowIndexingContext.cs new file mode 100644 index 000000000..785285b9b --- /dev/null +++ b/src/core/Elsa.Core/Models/WorkflowIndexingContext.cs @@ -0,0 +1,25 @@ +using Elsa.Contracts; + +namespace Elsa.Models; + +public class WorkflowIndexingContext +{ + public WorkflowIndexingContext(Workflow workflow) + { + Workflow = workflow; + } + + public Workflow Workflow { get; } + public IDictionary Registers { get; } = new Dictionary(); + + public Register GetOrCreateRegister(ITrigger activity) + { + if (!Registers.TryGetValue(activity, out var register)) + { + register = new Register(); + Registers[activity] = register; + } + + return register; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/WorkflowMetadata.cs b/src/core/Elsa.Core/Models/WorkflowMetadata.cs new file mode 100644 index 000000000..912e8df8f --- /dev/null +++ b/src/core/Elsa.Core/Models/WorkflowMetadata.cs @@ -0,0 +1,5 @@ +namespace Elsa.Models; + +public record WorkflowMetadata(string? Name = default, string? Description = default, DateTime CreatedAt = default) +{ +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/WorkflowPublication.cs b/src/core/Elsa.Core/Models/WorkflowPublication.cs new file mode 100644 index 000000000..84f49aa78 --- /dev/null +++ b/src/core/Elsa.Core/Models/WorkflowPublication.cs @@ -0,0 +1,8 @@ +namespace Elsa.Models; + +public record WorkflowPublication(bool IsLatest, bool IsPublished) +{ + public static WorkflowPublication LatestAndPublished => new(true, true); + public static WorkflowPublication LatestDraft => new(true, false); + public static WorkflowPublication Draft => new(false, false); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Options/WorkflowEngineOptions.cs b/src/core/Elsa.Core/Options/WorkflowEngineOptions.cs new file mode 100644 index 000000000..1c3c4d487 --- /dev/null +++ b/src/core/Elsa.Core/Options/WorkflowEngineOptions.cs @@ -0,0 +1,29 @@ +using System.Collections.ObjectModel; + +namespace Elsa.Options; + +public class WorkflowEngineOptions +{ + private readonly IDictionary _expressionHandlers = new Dictionary(); + + public WorkflowEngineOptions() + { + ExpressionHandlers = new ReadOnlyDictionary(_expressionHandlers); + } + + public IDictionary ExpressionHandlers { get; } + + public WorkflowEngineOptions RegisterExpressionHandler(Type expression, Type handler) + { + _expressionHandlers.Add(expression, handler); + return this; + } + + public WorkflowEngineOptions RegisterExpressionHandler() => RegisterExpressionHandler(typeof(TExpression)); + + public WorkflowEngineOptions RegisterExpressionHandler(Type expression) + { + _expressionHandlers.Add(expression, typeof(THandler)); + return this; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityExecutionMiddlewareExtensions.cs b/src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityExecutionMiddlewareExtensions.cs new file mode 100644 index 000000000..01f6b30f4 --- /dev/null +++ b/src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityExecutionMiddlewareExtensions.cs @@ -0,0 +1,20 @@ +using Elsa.Contracts; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Pipelines.ActivityExecution; + +public static class ActivityExecutionMiddlewareExtensions +{ + public static IActivityExecutionBuilder UseMiddleware(this IActivityExecutionBuilder builder, params object[] args) where TMiddleware : IActivityExecutionMiddleware + { + var middleware = typeof(TMiddleware); + + return builder.Use(next => + { + var invokeMethod = MiddlewareHelpers.GetInvokeMethod(middleware); + var ctorArgs = new[] { next }.Concat(args).Select(x => x!).ToArray(); + var instance = ActivatorUtilities.CreateInstance(builder.ServiceProvider, middleware, ctorArgs); + return (ActivityMiddlewareDelegate)invokeMethod.CreateDelegate(typeof(ActivityMiddlewareDelegate), instance); + }); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityExecutionPipeline.cs b/src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityExecutionPipeline.cs new file mode 100644 index 000000000..efdc4242a --- /dev/null +++ b/src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityExecutionPipeline.cs @@ -0,0 +1,31 @@ +using Elsa.Contracts; +using Elsa.Models; +using Elsa.Pipelines.ActivityExecution.Components; + +namespace Elsa.Pipelines.ActivityExecution; + +public class ActivityExecutionPipeline : IActivityExecutionPipeline +{ + private readonly IServiceProvider _serviceProvider; + private ActivityMiddlewareDelegate? _pipeline; + + public ActivityExecutionPipeline(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public ActivityMiddlewareDelegate Setup(Action setup) + { + var builder = new ActivityExecutionPipelineBuilder(_serviceProvider); + setup(builder); + _pipeline = builder.Build(); + return _pipeline; + } + + public ActivityMiddlewareDelegate Pipeline => _pipeline ??= CreateDefaultPipeline(); + public async Task ExecuteAsync(ActivityExecutionContext context) => await Pipeline(context); + + private ActivityMiddlewareDelegate CreateDefaultPipeline() => Setup(x => x + .UseActivityDrivers() + ); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityExecutionPipelineBuilder.cs b/src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityExecutionPipelineBuilder.cs new file mode 100644 index 000000000..598b5cafa --- /dev/null +++ b/src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityExecutionPipelineBuilder.cs @@ -0,0 +1,31 @@ +using Elsa.Contracts; + +namespace Elsa.Pipelines.ActivityExecution; + +public class ActivityExecutionPipelineBuilder : IActivityExecutionBuilder +{ + private readonly IList> _components = new List>(); + + public ActivityExecutionPipelineBuilder(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IActivityExecutionBuilder Use(Func middleware) + { + _components.Add(middleware); + return this; + } + + public ActivityMiddlewareDelegate Build() + { + ActivityMiddlewareDelegate pipeline = _ => new ValueTask(); + + foreach (var component in _components.Reverse()) + pipeline = component(pipeline); + + return pipeline; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityMiddlewareDelegate.cs b/src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityMiddlewareDelegate.cs new file mode 100644 index 000000000..8e3583908 --- /dev/null +++ b/src/core/Elsa.Core/Pipelines/ActivityExecution/ActivityMiddlewareDelegate.cs @@ -0,0 +1,5 @@ +using Elsa.Models; + +namespace Elsa.Pipelines.ActivityExecution; + +public delegate ValueTask ActivityMiddlewareDelegate(ActivityExecutionContext context); \ No newline at end of file diff --git a/src/core/Elsa.Core/Pipelines/ActivityExecution/Components/ActivityInvokerMiddleware.cs b/src/core/Elsa.Core/Pipelines/ActivityExecution/Components/ActivityInvokerMiddleware.cs new file mode 100644 index 000000000..d6b96cd75 --- /dev/null +++ b/src/core/Elsa.Core/Pipelines/ActivityExecution/Components/ActivityInvokerMiddleware.cs @@ -0,0 +1,114 @@ +using Elsa.Contracts; +using Elsa.Extensions; +using Elsa.Models; +using Microsoft.Extensions.Logging; +using Delegate = System.Delegate; + +namespace Elsa.Pipelines.ActivityExecution.Components; + +public static class InvokeDriversMiddlewareExtensions +{ + public static IActivityExecutionBuilder UseActivityDrivers(this IActivityExecutionBuilder builder) => builder.UseMiddleware(); +} + +public class ActivityInvokerMiddleware : IActivityExecutionMiddleware +{ + private readonly ActivityMiddlewareDelegate _next; + private readonly ILogger _logger; + + public ActivityInvokerMiddleware(ActivityMiddlewareDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + public async ValueTask InvokeAsync(ActivityExecutionContext context) + { + var workflowExecution = context.WorkflowExecutionContext; + + // Evaluate input properties. + await EvaluateInputPropertiesAsync(context); + var activity = context.Activity; + + // Execute activity. + var methodInfo = typeof(IActivity).GetMethod(nameof(IActivity.ExecuteAsync))!; + var executeDelegate = workflowExecution.ExecuteDelegate ?? (ExecuteActivityDelegate)Delegate.CreateDelegate(typeof(ExecuteActivityDelegate), activity, methodInfo); + await executeDelegate(context); + + // Record execution event. + workflowExecution.ExecutionLog.Add(new WorkflowExecutionLogEntry(context.Id)); + + // Reset execute delegate. + workflowExecution.ExecuteDelegate = null; + + // Invoke next middleware. + await _next(context); + + // Exit if any bookmarks were created. + if (context.Bookmarks.Any()) + { + // Store bookmarks. + workflowExecution.RegisterBookmarks(context.Bookmarks); + + // Block current path of execution. + return; + } + + // Complete parent chain. + await CompleteParentsAsync(context); + } + + private async Task EvaluateInputPropertiesAsync(ActivityExecutionContext context) + { + var activity = context.Activity; + var inputs = activity.GetInputs(); + var assignedInputs = inputs.Where(x => x.LocationReference != null!).ToList(); + var evaluator = context.GetRequiredService(); + var expressionExecutionContext = context.ExpressionExecutionContext; + + foreach (var input in assignedInputs) + { + var locationReference = input.LocationReference; + var value = await evaluator.EvaluateAsync(input, expressionExecutionContext); + locationReference.Set(context, value); + } + } + + private static async Task CompleteParentsAsync(ActivityExecutionContext context) + { + var workflowExecutionContext = context.WorkflowExecutionContext; + var currentContext = context; + var currentParentContext = context.ParentActivityExecutionContext; + + while (currentParentContext != null) + { + var scheduledNodes = workflowExecutionContext.Scheduler.List().Select(x => x.ActivityId).ToList(); + var descendantNodes = currentParentContext.ActivityNode.Descendants().Select(x => x.Activity.Id).Distinct().ToList(); + var hasScheduledChildren = scheduledNodes.Intersect(descendantNodes).Any(); + var @continue = currentContext.Continue; + + // Do not continue if the activity instructed not to. + if (!@continue) + return; + + if (!hasScheduledChildren) + { + // Invoke completion callbacks. + var completionCallback = workflowExecutionContext.PopCompletionCallback(currentParentContext, currentContext.Activity); + + if (completionCallback != null) + await completionCallback.Invoke(currentParentContext, currentContext); + + // Remove current activity context. + workflowExecutionContext.ActivityExecutionContexts.Remove(currentContext); + } + + // Do not continue completion callbacks of parents while there are scheduled nodes. + if (workflowExecutionContext.Scheduler.HasAny) + return; + + currentContext = currentParentContext; + currentParentContext = currentContext.ParentActivityExecutionContext; + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Pipelines/ActivityExecution/Components/LoggingMiddleware.cs b/src/core/Elsa.Core/Pipelines/ActivityExecution/Components/LoggingMiddleware.cs new file mode 100644 index 000000000..4d5b4c805 --- /dev/null +++ b/src/core/Elsa.Core/Pipelines/ActivityExecution/Components/LoggingMiddleware.cs @@ -0,0 +1,35 @@ +using System.Diagnostics; +using Elsa.Contracts; +using Elsa.Models; +using Microsoft.Extensions.Logging; + +namespace Elsa.Pipelines.ActivityExecution.Components; + +public class LoggingMiddleware : IActivityExecutionMiddleware +{ + private readonly ActivityMiddlewareDelegate _next; + private readonly ILogger _logger; + private readonly Stopwatch _stopwatch; + + public LoggingMiddleware(ActivityMiddlewareDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + _stopwatch = new Stopwatch(); + } + + public async ValueTask InvokeAsync(ActivityExecutionContext context) + { + var node = context.ActivityNode; + _logger.LogDebug("Executing node {Node}", node.GetType().Name); + _stopwatch.Restart(); + await _next(context); + _stopwatch.Stop(); + _logger.LogDebug("Executed node {Node} in {Elapsed}", node.GetType().Name, _stopwatch.Elapsed); + } +} + +public static class LoggingMiddlewareExtensions +{ + public static IActivityExecutionBuilder UseLogging(this IActivityExecutionBuilder builder) => builder.UseMiddleware(); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Pipelines/MiddlewareHelpers.cs b/src/core/Elsa.Core/Pipelines/MiddlewareHelpers.cs new file mode 100644 index 000000000..32b910c0e --- /dev/null +++ b/src/core/Elsa.Core/Pipelines/MiddlewareHelpers.cs @@ -0,0 +1,29 @@ +using System.Reflection; + +namespace Elsa.Pipelines; + +public static class MiddlewareHelpers +{ + public static MethodInfo GetInvokeMethod(Type middleware) + { + const string invokeMethodName = "Invoke"; + const string invokeAsyncMethodName = "InvokeAsync"; + var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public); + var invokeMethods = methods.Where(m => string.Equals(m.Name, invokeMethodName, StringComparison.Ordinal) || string.Equals(m.Name, invokeAsyncMethodName, StringComparison.Ordinal)).ToArray(); + + switch (invokeMethods.Length) + { + case > 1: + throw new InvalidOperationException("Multiple Invoke methods were found. Use either Invoke or InvokeAsync."); + case 0: + throw new InvalidOperationException("No Invoke methods were found. Use either Invoke or InvokeAsync"); + } + + var methodInfo = invokeMethods[0]; + + if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType) && !typeof(ValueTask).IsAssignableFrom(methodInfo.ReturnType)) + throw new InvalidOperationException($"The {methodInfo.Name} method must return Task or ValueTask"); + + return methodInfo; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Pipelines/WorkflowExecution/Components/ActivitySchedulerMiddleware.cs b/src/core/Elsa.Core/Pipelines/WorkflowExecution/Components/ActivitySchedulerMiddleware.cs new file mode 100644 index 000000000..263398ae6 --- /dev/null +++ b/src/core/Elsa.Core/Pipelines/WorkflowExecution/Components/ActivitySchedulerMiddleware.cs @@ -0,0 +1,33 @@ +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Pipelines.WorkflowExecution.Components; + +public static class UseActivitySchedulerMiddlewareExtensions +{ + public static IWorkflowExecutionBuilder UseActivityScheduler(this IWorkflowExecutionBuilder builder) => builder.UseMiddleware(); +} + +public class ActivitySchedulerMiddleware : IWorkflowExecutionMiddleware +{ + private readonly WorkflowMiddlewareDelegate _next; + public ActivitySchedulerMiddleware(WorkflowMiddlewareDelegate next) => _next = next; + + public async ValueTask InvokeAsync(WorkflowExecutionContext context) + { + var scheduler = context.Scheduler; + + // As long as there are activities scheduled, keep executing them. + while (scheduler.HasAny) + { + // Pop next work item for execution. + var currentWorkItem = scheduler.Pop(); + + // Execute work item. + await currentWorkItem.Execute(); + } + + // Invoke next middleware. + await _next(context); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowExecutionMiddlewareExtensions.cs b/src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowExecutionMiddlewareExtensions.cs new file mode 100644 index 000000000..551caf144 --- /dev/null +++ b/src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowExecutionMiddlewareExtensions.cs @@ -0,0 +1,20 @@ +using Elsa.Contracts; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Pipelines.WorkflowExecution; + +public static class WorkflowExecutionMiddlewareExtensions +{ + public static IWorkflowExecutionBuilder UseMiddleware(this IWorkflowExecutionBuilder builder, params object[] args) where TMiddleware: IWorkflowExecutionMiddleware + { + var middleware = typeof(TMiddleware); + + return builder.Use(next => + { + var invokeMethod = MiddlewareHelpers.GetInvokeMethod(middleware); + var ctorParams = new[] { next }.Concat(args).Select(x => x!).ToArray(); + var instance = ActivatorUtilities.CreateInstance(builder.ApplicationServices, middleware, ctorParams); + return (WorkflowMiddlewareDelegate)invokeMethod.CreateDelegate(typeof(WorkflowMiddlewareDelegate), instance); + }); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowExecutionPipeline.cs b/src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowExecutionPipeline.cs new file mode 100644 index 000000000..55b7ced26 --- /dev/null +++ b/src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowExecutionPipeline.cs @@ -0,0 +1,28 @@ +using Elsa.Contracts; +using Elsa.Models; +using Elsa.Pipelines.WorkflowExecution.Components; + +namespace Elsa.Pipelines.WorkflowExecution; + +public class WorkflowExecutionPipeline : IWorkflowExecutionPipeline +{ + private readonly IServiceProvider _serviceProvider; + private WorkflowMiddlewareDelegate? _pipeline; + + public WorkflowExecutionPipeline(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + public WorkflowMiddlewareDelegate Pipeline => _pipeline ??= CreateDefaultPipeline(); + + public WorkflowMiddlewareDelegate Setup(Action setup) + { + var builder = new WorkflowExecutionPipelineBuilder(_serviceProvider); + setup(builder); + _pipeline = builder.Build(); + return _pipeline; + } + + public async Task ExecuteAsync(WorkflowExecutionContext context) => await Pipeline(context); + + private WorkflowMiddlewareDelegate CreateDefaultPipeline() => Setup(x => x + .UseActivityScheduler() + ); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowExecutionPipelineBuilder.cs b/src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowExecutionPipelineBuilder.cs new file mode 100644 index 000000000..5d6d785c4 --- /dev/null +++ b/src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowExecutionPipelineBuilder.cs @@ -0,0 +1,41 @@ +using Elsa.Contracts; + +namespace Elsa.Pipelines.WorkflowExecution; + +public class WorkflowExecutionPipelineBuilder : IWorkflowExecutionBuilder +{ + private const string ServicesKey = "workflow-execution.Services"; + private readonly IList> _components = new List>(); + + public WorkflowExecutionPipelineBuilder(IServiceProvider serviceProvider) + { + ApplicationServices = serviceProvider; + } + + public IDictionary Properties { get; } = new Dictionary(); + + public IServiceProvider ApplicationServices + { + get => GetProperty(ServicesKey)!; + set => SetProperty(ServicesKey, value); + } + + public IWorkflowExecutionBuilder Use(Func middleware) + { + _components.Add(middleware); + return this; + } + + public WorkflowMiddlewareDelegate Build() + { + WorkflowMiddlewareDelegate pipeline = _ => new ValueTask(); + + foreach (var component in _components.Reverse()) + pipeline = component(pipeline); + + return pipeline; + } + + private T? GetProperty(string key) => Properties.TryGetValue(key, out var value) ? (T?)value : default(T); + private void SetProperty(string key, T value) => Properties[key] = value; +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowMiddlewareDelegate.cs b/src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowMiddlewareDelegate.cs new file mode 100644 index 000000000..b2c7e9bf7 --- /dev/null +++ b/src/core/Elsa.Core/Pipelines/WorkflowExecution/WorkflowMiddlewareDelegate.cs @@ -0,0 +1,5 @@ +using Elsa.Models; + +namespace Elsa.Pipelines.WorkflowExecution; + +public delegate ValueTask WorkflowMiddlewareDelegate(WorkflowExecutionContext context); \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/ActivityInvoker.cs b/src/core/Elsa.Core/Services/ActivityInvoker.cs new file mode 100644 index 000000000..4ee899e37 --- /dev/null +++ b/src/core/Elsa.Core/Services/ActivityInvoker.cs @@ -0,0 +1,47 @@ +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Services; + +public class ActivityInvoker : IActivityInvoker +{ + private readonly IActivityExecutionPipeline _pipeline; + + public ActivityInvoker(IActivityExecutionPipeline pipeline) + { + _pipeline = pipeline; + } + + public async Task InvokeAsync( + WorkflowExecutionContext workflowExecutionContext, + IActivity activity, + ActivityExecutionContext? owner, + IEnumerable? locationReferences = default) + { + var cancellationToken = workflowExecutionContext.CancellationToken; + + // Get a handle to the parent execution context. + var parentActivityExecutionContext = owner; + + // Setup an activity execution context. + var register = new Register(); + var expressionExecutionContext = new ExpressionExecutionContext(register, parentActivityExecutionContext?.ExpressionExecutionContext); + var activityExecutionContext = new ActivityExecutionContext(workflowExecutionContext, parentActivityExecutionContext, expressionExecutionContext, activity, cancellationToken); + + // Declare locations. + if (locationReferences != null) + register.Declare(locationReferences); + + // Push the activity context into the workflow context. + workflowExecutionContext.ActivityExecutionContexts.Add(activityExecutionContext); + + // Execute the activity execution pipeline. + await InvokeAsync(activityExecutionContext); + } + + public async Task InvokeAsync(ActivityExecutionContext activityExecutionContext) + { + // Execute the activity execution pipeline. + await _pipeline.ExecuteAsync(activityExecutionContext); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/ActivityScheduler.cs b/src/core/Elsa.Core/Services/ActivityScheduler.cs new file mode 100644 index 000000000..e36ebc53c --- /dev/null +++ b/src/core/Elsa.Core/Services/ActivityScheduler.cs @@ -0,0 +1,14 @@ +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Services; + +public class ActivityScheduler : IActivityScheduler +{ + private readonly Stack _stack = new(); + + public bool HasAny => _stack.Any(); + public void Push(ActivityWorkItem activity) => _stack.Push(activity); + public ActivityWorkItem Pop() => _stack.Pop(); + public IEnumerable List() => _stack.ToList(); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/ActivitySchedulerFactory.cs b/src/core/Elsa.Core/Services/ActivitySchedulerFactory.cs new file mode 100644 index 000000000..7bffc053d --- /dev/null +++ b/src/core/Elsa.Core/Services/ActivitySchedulerFactory.cs @@ -0,0 +1,8 @@ +using Elsa.Contracts; + +namespace Elsa.Services; + +public class ActivitySchedulerFactory : IActivitySchedulerFactory +{ + public IActivityScheduler CreateScheduler() => new ActivityScheduler(); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/ActivityWalker.cs b/src/core/Elsa.Core/Services/ActivityWalker.cs new file mode 100644 index 000000000..329a13f84 --- /dev/null +++ b/src/core/Elsa.Core/Services/ActivityWalker.cs @@ -0,0 +1,54 @@ +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Services; + +public class ActivityWalker : IActivityWalker +{ + private readonly IEnumerable _portResolvers; + + public ActivityWalker(IEnumerable portResolvers) + { + _portResolvers = portResolvers.OrderByDescending(x => x.Priority).ToList(); + } + + public ActivityNode Walk(IActivity activity) + { + var collectedActivities = new HashSet(new[] { activity }); + var graph = new ActivityNode(activity); + var collectedNodes = new HashSet(new[] { graph }); + WalkRecursive((graph, activity), collectedActivities, collectedNodes); + return graph; + } + + private void WalkRecursive((ActivityNode Node, IActivity Activity) pair, HashSet collectedActivities, HashSet collectedNodes) + { + WalkPortsRecursive(pair, collectedActivities, collectedNodes); + } + + private void WalkPortsRecursive((ActivityNode Node, IActivity Activity) pair, HashSet collectedActivities, HashSet collectedNodes) + { + var resolver = _portResolvers.FirstOrDefault(x => x.GetSupportsActivity(pair.Activity)); + + if (resolver == null) + return; + + var ports = resolver.GetPorts(pair.Activity); + + foreach (var port in ports) + { + var childNode = collectedNodes.FirstOrDefault(x => x.Activity == port); + + if (childNode == null) + { + childNode = new ActivityNode(port); + collectedNodes.Add(childNode); + } + + childNode.Parents.Add(pair.Node); + pair.Node.Children.Add(childNode); + collectedActivities.Add(port); + WalkRecursive((childNode, port), collectedActivities, collectedNodes); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/ExpressionEvaluator.cs b/src/core/Elsa.Core/Services/ExpressionEvaluator.cs new file mode 100644 index 000000000..6a9de0f84 --- /dev/null +++ b/src/core/Elsa.Core/Services/ExpressionEvaluator.cs @@ -0,0 +1,27 @@ +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Services; + +public class ExpressionEvaluator : IExpressionEvaluator +{ + private readonly IExpressionHandlerRegistry _registry; + + public ExpressionEvaluator(IExpressionHandlerRegistry registry) + { + _registry = registry; + } + + public async ValueTask EvaluateAsync(IExpression input, ExpressionExecutionContext context) => (T?)await EvaluateAsync(input, typeof(T), context); + + public async ValueTask EvaluateAsync(IExpression input, Type returnType, ExpressionExecutionContext context) + { + var handler = _registry.GetHandler(input); + + if (handler != null) + return await handler.EvaluateAsync(input, returnType, context); + + var expressionType = input.GetType().GetGenericTypeDefinition(); + throw new InvalidOperationException($"Could not find handler for expression type {expressionType}"); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/ExpressionHandlerRegistry.cs b/src/core/Elsa.Core/Services/ExpressionHandlerRegistry.cs new file mode 100644 index 000000000..0daf4119b --- /dev/null +++ b/src/core/Elsa.Core/Services/ExpressionHandlerRegistry.cs @@ -0,0 +1,31 @@ +using Elsa.Contracts; +using Elsa.Options; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Elsa.Services; + +public class ExpressionHandlerRegistry : IExpressionHandlerRegistry +{ + private readonly IServiceProvider _serviceProvider; + + public ExpressionHandlerRegistry(IOptions options, IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + Dictionary = new Dictionary(options.Value.ExpressionHandlers); + } + + private IDictionary Dictionary { get; } + + public void Register(Type expression, Type handler) => Dictionary.Add(expression, handler); + + public IExpressionHandler? GetHandler(IExpression input) + { + var expressionType = input.GetType(); + + if (!Dictionary.TryGetValue(expressionType, out var handlerType)) + return null; + + return (IExpressionHandler)ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, handlerType); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/Hasher.cs b/src/core/Elsa.Core/Services/Hasher.cs new file mode 100644 index 000000000..4a726c291 --- /dev/null +++ b/src/core/Elsa.Core/Services/Hasher.cs @@ -0,0 +1,33 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using Elsa.Contracts; + +namespace Elsa.Services; + +public class Hasher : IHasher +{ + public string Hash(object value) + { + var options = new JsonSerializerOptions + { + // Enables serialization of ValueTuples, which use fields instead of properties. + IncludeFields = true + }; + + var json = JsonSerializer.Serialize(value, options); + return Hash(json); + } + + private static string Hash(string input) + { + using var sha = HashAlgorithm.Create(HashAlgorithmName.SHA256.ToString())!; + return Hash(sha, input); + } + + private static string Hash(HashAlgorithm hashAlgorithm, string input) + { + var data = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(input)); + return Convert.ToHexString(data); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/IdentityGenerator.cs b/src/core/Elsa.Core/Services/IdentityGenerator.cs new file mode 100644 index 000000000..33b907d2d --- /dev/null +++ b/src/core/Elsa.Core/Services/IdentityGenerator.cs @@ -0,0 +1,8 @@ +using Elsa.Contracts; + +namespace Elsa.Services; + +public class IdentityGenerator : IIdentityGenerator +{ + public string GenerateId() => Guid.NewGuid().ToString("N"); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/IdentityGraphService.cs b/src/core/Elsa.Core/Services/IdentityGraphService.cs new file mode 100644 index 000000000..a1ecd5b84 --- /dev/null +++ b/src/core/Elsa.Core/Services/IdentityGraphService.cs @@ -0,0 +1,110 @@ +using Elsa.Contracts; +using Elsa.Extensions; +using Elsa.Models; + +namespace Elsa.Services; + +public class IdentityGraphService : IIdentityGraphService +{ + private readonly IActivityWalker _activityWalker; + + public IdentityGraphService(IActivityWalker activityWalker) + { + _activityWalker = activityWalker; + } + + public void AssignIdentities(Workflow workflow) + { + AssignIdentities(workflow.Root); + + var triggers = workflow.Triggers; + + if (triggers == null) + return; + + foreach (var trigger in triggers) + { + if(trigger is IActivity activity) + AssignIdentities(activity); + } + } + + public void AssignIdentities(IActivity root) + { + var graph = _activityWalker.Walk(root); + AssignIdentities(graph); + } + + public void AssignIdentities(ActivityNode root) + { + var identityCounters = new Dictionary(); + var list = root.Flatten(); + + foreach (var node in list) + { + node.Activity.Id = CreateId(node, identityCounters); + AssignInputOutputs(node.Activity); + AssignVariables(node.Activity); + } + } + + private void AssignInputOutputs(IActivity activity) + { + var inputs = activity.GetInputs(); + var assignedInputs = inputs.Where(x => x.LocationReference != null! && x.LocationReference.Id == null!).ToList(); + var seed = 0; + + foreach (var input in assignedInputs) + { + var locationReference = input.LocationReference; + locationReference.Id = $"{activity.Id}:input-{++seed}"; + } + + seed = 0; + var outputs = activity.GetOutputs(); + var assignedOutputs = outputs.Where(x => x.LocationReference != null! && x.LocationReference.Id == null!).ToList(); + + foreach (var output in assignedOutputs) + { + var locationReference = output.LocationReference; + locationReference.Id = $"{activity.Id}:output-{++seed}"; + } + } + + private void AssignVariables(IActivity activity) + { + var variables = activity.GetVariables(); + var seed = 0; + + foreach (var variable in variables) + variable.Id = variable.Name != null! ? variable.Name : $"{activity.Id}:variable-{++seed}"; + } + + private string CreateId(ActivityNode activityNode, IDictionary identityCounters) + { + if (!string.IsNullOrWhiteSpace(activityNode.NodeId)) + return activityNode.NodeId; + + var type = activityNode.Activity.NodeType; + var index = GetNextIndexFor(type, identityCounters); + var name = $"{Camelize(type)}{index + 1}"; + return name; + } + + private int GetNextIndexFor(string nodeType, IDictionary identityCounters) + { + if (!identityCounters.TryGetValue(nodeType, out var index)) + { + identityCounters[nodeType] = index; + } + else + { + index = identityCounters[nodeType] + 1; + identityCounters[nodeType] = index; + } + + return index; + } + + private string Camelize(string symbol) => char.ToLowerInvariant(symbol[0]) + symbol[1..]; +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/SystemClock.cs b/src/core/Elsa.Core/Services/SystemClock.cs new file mode 100644 index 000000000..3fc658e0b --- /dev/null +++ b/src/core/Elsa.Core/Services/SystemClock.cs @@ -0,0 +1,8 @@ +using Elsa.Contracts; + +namespace Elsa.Services; + +public class SystemClock : ISystemClock +{ + public DateTime UtcNow => DateTime.UtcNow; +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/WorkflowEngine.cs b/src/core/Elsa.Core/Services/WorkflowEngine.cs new file mode 100644 index 000000000..eeb9c752f --- /dev/null +++ b/src/core/Elsa.Core/Services/WorkflowEngine.cs @@ -0,0 +1,120 @@ +using Elsa.Contracts; +using Elsa.Models; +using Elsa.State; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Services; + +public class WorkflowEngine : IWorkflowEngine +{ + private static ValueTask Noop(ActivityExecutionContext context) => new(); + + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly IActivityWalker _activityWalker; + private readonly IWorkflowExecutionPipeline _pipeline; + private readonly IWorkflowStateSerializer _workflowStateSerializer; + private readonly IIdentityGraphService _identityGraphService; + private readonly IActivitySchedulerFactory _schedulerFactory; + + public WorkflowEngine( + IServiceScopeFactory serviceScopeFactory, + IActivityWalker activityWalker, + IWorkflowExecutionPipeline pipeline, + IWorkflowStateSerializer workflowStateSerializer, + IIdentityGraphService identityGraphService, + IActivitySchedulerFactory schedulerFactory) + { + _serviceScopeFactory = serviceScopeFactory; + _activityWalker = activityWalker; + _pipeline = pipeline; + _workflowStateSerializer = workflowStateSerializer; + _identityGraphService = identityGraphService; + _schedulerFactory = schedulerFactory; + } + + public async Task ExecuteAsync(Workflow workflow, CancellationToken cancellationToken = default) + { + // Create a child scope. + using var scope = _serviceScopeFactory.CreateScope(); + + // Setup a workflow execution context. + var workflowExecutionContext = CreateWorkflowExecutionContext(scope.ServiceProvider, workflow, default, default, default, cancellationToken); + + // Schedule the first node. + workflowExecutionContext.ScheduleRoot(); + + return await ExecuteAsync(workflowExecutionContext); + } + + public async Task ExecuteAsync(Workflow workflow, WorkflowState workflowState, CancellationToken cancellationToken = default) + { + // Create a child scope. + using var scope = _serviceScopeFactory.CreateScope(); + + // Create workflow execution context. + var workflowExecutionContext = CreateWorkflowExecutionContext(scope.ServiceProvider, workflow, workflowState, default, default, cancellationToken); + + // Schedule the first node. + workflowExecutionContext.ScheduleRoot(); + + return await ExecuteAsync(workflowExecutionContext); + } + + public async Task ExecuteAsync(Workflow workflow, WorkflowState workflowState, Bookmark? bookmark, CancellationToken cancellationToken = default) + { + // Create a child scope. + using var scope = _serviceScopeFactory.CreateScope(); + + // Create workflow execution context. + var workflowExecutionContext = CreateWorkflowExecutionContext(scope.ServiceProvider, workflow, workflowState, bookmark, default, cancellationToken); + + if (bookmark != null) + workflowExecutionContext.ScheduleBookmark(bookmark); + + return await ExecuteAsync(workflowExecutionContext); + } + + public async Task ExecuteAsync(WorkflowExecutionContext workflowExecutionContext) + { + // Execute the activity execution pipeline. + await _pipeline.ExecuteAsync(workflowExecutionContext); + + // Extract workflow state. + var workflowState = _workflowStateSerializer.ReadState(workflowExecutionContext); + + // Return workflow execution result containing state + bookmarks. + return new ExecuteWorkflowResult(workflowState, workflowExecutionContext.Bookmarks); + } + + public WorkflowExecutionContext CreateWorkflowExecutionContext( + IServiceProvider serviceProvider, + Workflow workflow, + WorkflowState? workflowState, + Bookmark? bookmark, + ExecuteActivityDelegate? executeActivityDelegate, + CancellationToken cancellationToken) + { + var root = workflow.Root; + + // Build graph. + var graph = _activityWalker.Walk(root); + + // Assign identities. + _identityGraphService.AssignIdentities(graph); + + // Create scheduler. + var scheduler = _schedulerFactory.CreateScheduler(); + + // Setup a workflow execution context. + var workflowExecutionContext = new WorkflowExecutionContext(serviceProvider, workflow, graph, scheduler, bookmark, executeActivityDelegate, cancellationToken); + + // Restore workflow execution context from state, if provided. + if (workflowState != null) + { + var workflowStateService = serviceProvider.GetRequiredService(); + workflowStateService.WriteState(workflowExecutionContext, workflowState); + } + + return workflowExecutionContext; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/WorkflowStateSerializer.cs b/src/core/Elsa.Core/Services/WorkflowStateSerializer.cs new file mode 100644 index 000000000..f37878044 --- /dev/null +++ b/src/core/Elsa.Core/Services/WorkflowStateSerializer.cs @@ -0,0 +1,162 @@ +using System.Reflection; +using Elsa.Attributes; +using Elsa.Contracts; +using Elsa.Extensions; +using Elsa.Models; +using Elsa.State; +using Microsoft.Extensions.Logging; + +namespace Elsa.Services; + +public class WorkflowStateSerializer : IWorkflowStateSerializer +{ + private readonly ILogger _logger; + + public WorkflowStateSerializer(ILogger logger) + { + _logger = logger; + } + + public WorkflowState ReadState(WorkflowExecutionContext workflowExecutionContext) + { + var state = new WorkflowState + { + Id = workflowExecutionContext.Id + }; + + //GetOutput(state, workflowExecutionContext); + GetProperties(state, workflowExecutionContext); + GetCompletionCallbacks(state, workflowExecutionContext); + GetActivityExecutionContexts(state, workflowExecutionContext); + + return state; + } + + public void WriteState(WorkflowExecutionContext workflowExecutionContext, WorkflowState state) + { + workflowExecutionContext.Id = state.Id; + //SetOutput(state, workflowExecutionContext); + SetProperties(state, workflowExecutionContext); + SetActivityExecutionContexts(state, workflowExecutionContext); + SetCompletionCallbacks(state, workflowExecutionContext); + } + + private void GetProperties(WorkflowState state, WorkflowExecutionContext workflowExecutionContext) + { + state.Properties = workflowExecutionContext.Properties; + } + + private void SetProperties(WorkflowState state, WorkflowExecutionContext workflowExecutionContext) + { + workflowExecutionContext.Properties = state.Properties; + } + + private void GetOutput(WorkflowState state, WorkflowExecutionContext workflowExecutionContext) + { + foreach (var node in workflowExecutionContext.Nodes) + GetOutput(state, node); + } + + private void GetOutput(WorkflowState state, ActivityNode activityNode) + { + var output = GetOutputFrom(activityNode); + + if (output.Any()) + state.ActivityOutput.Add(activityNode.NodeId, output); + } + + private void SetOutput(WorkflowState state, WorkflowExecutionContext workflowExecutionContext) + { + foreach (var nodeEntry in state.ActivityOutput) + { + var nodeId = nodeEntry.Key; + var node = workflowExecutionContext.FindNodeById(nodeId); + var nodeType = node.GetType(); + + foreach (var outputEntry in nodeEntry.Value) + { + var propertyName = outputEntry.Key; + var propertyValue = outputEntry.Value; + var propertyInfo = nodeType.GetProperty(propertyName, BindingFlags.Public)!; + propertyInfo.SetValue(node, propertyValue); + } + } + } + + private void SetCompletionCallbacks(WorkflowState state, WorkflowExecutionContext workflowExecutionContext) + { + foreach (var completionCallbackEntry in state.CompletionCallbacks) + { + var owner = workflowExecutionContext.ActivityExecutionContexts.First(x => x.Id == completionCallbackEntry.OwnerId); + var child = workflowExecutionContext.FindNodeById(completionCallbackEntry.ChildId).Activity; + var callbackName = completionCallbackEntry.MethodName; + var callbackDelegate = owner.Activity.GetActivityCompletionCallback(callbackName); + workflowExecutionContext.AddCompletionCallback(owner, child, callbackDelegate); + } + } + + private void GetCompletionCallbacks(WorkflowState state, WorkflowExecutionContext workflowExecutionContext) + { + var completionCallbacks = workflowExecutionContext.CompletionCallbacks.Select(x => new CompletionCallbackState(x.Owner.Id, x.Child.Id, x.CompletionCallback.Method.Name)); + state.CompletionCallbacks = completionCallbacks.ToList(); + } + + private void GetActivityExecutionContexts(WorkflowState state, WorkflowExecutionContext workflowExecutionContext) + { + ActivityExecutionContextState CreateActivityExecutionContextState(ActivityExecutionContext activityExecutionContext) + { + var registerState = new RegisterState(activityExecutionContext.ExpressionExecutionContext.Register.Locations); + var activityExecutionContextState = new ActivityExecutionContextState + { + Id = activityExecutionContext.Id, + ScheduledActivityId = activityExecutionContext.Activity.Id, + OwnerActivityId = activityExecutionContext.ParentActivityExecutionContext?.Activity.Id, + Properties = activityExecutionContext.Properties, + Register = registerState + }; + return activityExecutionContextState; + } + + // Create a tupled list of contexts and states. + var tuples = workflowExecutionContext.ActivityExecutionContexts.Reverse().Select(x => (x, CreateActivityExecutionContextState(x))).ToList(); + + // Construct hierarchy. + foreach (var tuple in tuples) + tuple.Item2.ParentActivityExecutionContext = tuples.FirstOrDefault(x => tuple.x.ParentActivityExecutionContext == x.x).Item2; + + state.ActivityExecutionContexts = tuples.Select(x => x.Item2).ToList(); + } + + private void SetActivityExecutionContexts(WorkflowState state, WorkflowExecutionContext workflowExecutionContext) + { + ActivityExecutionContext CreateActivityExecutionContext(ActivityExecutionContextState activityExecutionContextState) + { + var activity = workflowExecutionContext.FindActivityById(activityExecutionContextState.ScheduledActivityId); + var register = new Register(activityExecutionContextState.Register.Locations); + var expressionExecutionContext = new ExpressionExecutionContext(register, default); + var properties = activityExecutionContextState.Properties; + var activityExecutionContext = new ActivityExecutionContext(workflowExecutionContext, default, expressionExecutionContext, activity, workflowExecutionContext.CancellationToken) + { + Id = activityExecutionContextState.Id, + Properties = properties + }; + return activityExecutionContext; + } + + // Create a tupled list of states and contexts. + var tuples = state.ActivityExecutionContexts.Select(x => (x, CreateActivityExecutionContext(x))).ToList(); + + // Reconstruct hierarchy. + foreach (var tuple in tuples) + { + var activityExecutionContext = tuple.Item2; + activityExecutionContext.ParentActivityExecutionContext = tuples.FirstOrDefault(x => tuple.x.ParentActivityExecutionContext == x.x).Item2; + activityExecutionContext.ExpressionExecutionContext.ParentContext = activityExecutionContext.ParentActivityExecutionContext?.ExpressionExecutionContext; + } + + workflowExecutionContext.ActivityExecutionContexts = new List(tuples.Select(x => x.Item2)); + } + + private IDictionary GetOutputFrom(ActivityNode activityNode) => + activityNode.GetType().GetProperties(BindingFlags.Public).Where(x => x.GetCustomAttribute() != null).ToDictionary(x => x.Name, x => (object?)x.GetValue(activityNode)); +} \ No newline at end of file diff --git a/src/core/Elsa.Core/State/ActivityExecutionContextState.cs b/src/core/Elsa.Core/State/ActivityExecutionContextState.cs new file mode 100644 index 000000000..321c9fd18 --- /dev/null +++ b/src/core/Elsa.Core/State/ActivityExecutionContextState.cs @@ -0,0 +1,15 @@ +namespace Elsa.State; + +public class ActivityExecutionContextState +{ + public ActivityExecutionContextState() + { + } + + public string Id { get; set; } = default!; + public ActivityExecutionContextState? ParentActivityExecutionContext { get; set; } + public string ScheduledActivityId { get; set; } = default!; + public string? OwnerActivityId { get; set; } + public IDictionary Properties { get; set; } = new Dictionary(); + public RegisterState Register { get; set; } = default!; +} \ No newline at end of file diff --git a/src/core/Elsa.Core/State/CompletionCallbackState.cs b/src/core/Elsa.Core/State/CompletionCallbackState.cs new file mode 100644 index 000000000..7eb33783d --- /dev/null +++ b/src/core/Elsa.Core/State/CompletionCallbackState.cs @@ -0,0 +1,3 @@ +namespace Elsa.State; + +public record CompletionCallbackState(string OwnerId, string ChildId, string MethodName); \ No newline at end of file diff --git a/src/core/Elsa.Core/State/RegisterState.cs b/src/core/Elsa.Core/State/RegisterState.cs new file mode 100644 index 000000000..eb81af13d --- /dev/null +++ b/src/core/Elsa.Core/State/RegisterState.cs @@ -0,0 +1,13 @@ +using Elsa.Models; + +namespace Elsa.State; + +public class RegisterState +{ + public RegisterState(IDictionary locations) + { + Locations = locations; + } + + public IDictionary Locations { get; set; } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/State/WorkflowState.cs b/src/core/Elsa.Core/State/WorkflowState.cs new file mode 100644 index 000000000..982da5a5b --- /dev/null +++ b/src/core/Elsa.Core/State/WorkflowState.cs @@ -0,0 +1,10 @@ +namespace Elsa.State; + +public class WorkflowState +{ + public string Id { get; set; } = default!; + public IDictionary> ActivityOutput { get; set; } = new Dictionary>(); + public ICollection CompletionCallbacks { get; set; } = new List(); + public ICollection ActivityExecutionContexts { get; set; } = new List(); + public IDictionary Properties { get; set; } = new Dictionary(); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/IActivityDescriber.cs b/src/core/Elsa.Management/Contracts/IActivityDescriber.cs new file mode 100644 index 000000000..d2611fea1 --- /dev/null +++ b/src/core/Elsa.Management/Contracts/IActivityDescriber.cs @@ -0,0 +1,8 @@ +using Elsa.Management.Models; + +namespace Elsa.Management.Contracts; + +public interface IActivityDescriber +{ + ValueTask DescribeActivityAsync(Type activityType, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/IActivityFactory.cs b/src/core/Elsa.Management/Contracts/IActivityFactory.cs new file mode 100644 index 000000000..5ffeca008 --- /dev/null +++ b/src/core/Elsa.Management/Contracts/IActivityFactory.cs @@ -0,0 +1,9 @@ +using Elsa.Contracts; +using Elsa.Management.Models; + +namespace Elsa.Management.Contracts; + +public interface IActivityFactory +{ + IActivity Create(Type type, ActivityConstructorContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/IActivityPropertyDefaultValueProvider.cs b/src/core/Elsa.Management/Contracts/IActivityPropertyDefaultValueProvider.cs new file mode 100644 index 000000000..cfcb7acbc --- /dev/null +++ b/src/core/Elsa.Management/Contracts/IActivityPropertyDefaultValueProvider.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +namespace Elsa.Management.Contracts; + +public interface IActivityPropertyDefaultValueProvider +{ + object GetDefaultValue(PropertyInfo property); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/IActivityProvider.cs b/src/core/Elsa.Management/Contracts/IActivityProvider.cs new file mode 100644 index 000000000..a64eb67b7 --- /dev/null +++ b/src/core/Elsa.Management/Contracts/IActivityProvider.cs @@ -0,0 +1,8 @@ +using Elsa.Management.Models; + +namespace Elsa.Management.Contracts; + +public interface IActivityProvider +{ + ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/IActivityRegistry.cs b/src/core/Elsa.Management/Contracts/IActivityRegistry.cs new file mode 100644 index 000000000..98c5343f7 --- /dev/null +++ b/src/core/Elsa.Management/Contracts/IActivityRegistry.cs @@ -0,0 +1,15 @@ +using Elsa.Management.Models; + +namespace Elsa.Management.Contracts; + +public interface IActivityRegistry +{ + void Add(object provider, ActivityDescriptor descriptor); + void AddMany(object provider, IEnumerable descriptors); + void Clear(); + void ClearProvider(object provider); + IEnumerable ListAll(); + IEnumerable ListByProvider(object provider); + ActivityDescriptor? Find(Func predicate); + ActivityDescriptor? Find(string activityType); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/IActivityRegistryPopulator.cs b/src/core/Elsa.Management/Contracts/IActivityRegistryPopulator.cs new file mode 100644 index 000000000..ac2283e8e --- /dev/null +++ b/src/core/Elsa.Management/Contracts/IActivityRegistryPopulator.cs @@ -0,0 +1,6 @@ +namespace Elsa.Management.Contracts; + +public interface IActivityRegistryPopulator +{ + ValueTask PopulateRegistryAsync(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/IExpressionSyntaxProvider.cs b/src/core/Elsa.Management/Contracts/IExpressionSyntaxProvider.cs new file mode 100644 index 000000000..902ff9179 --- /dev/null +++ b/src/core/Elsa.Management/Contracts/IExpressionSyntaxProvider.cs @@ -0,0 +1,8 @@ +using Elsa.Management.Models; + +namespace Elsa.Management.Contracts; + +public interface IExpressionSyntaxProvider +{ + ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/IExpressionSyntaxRegistry.cs b/src/core/Elsa.Management/Contracts/IExpressionSyntaxRegistry.cs new file mode 100644 index 000000000..69552eb8f --- /dev/null +++ b/src/core/Elsa.Management/Contracts/IExpressionSyntaxRegistry.cs @@ -0,0 +1,12 @@ +using Elsa.Management.Models; + +namespace Elsa.Management.Contracts; + +public interface IExpressionSyntaxRegistry +{ + void Add(ExpressionSyntaxDescriptor descriptor); + void AddMany(IEnumerable descriptors); + IEnumerable ListAll(); + ExpressionSyntaxDescriptor? Find(Func predicate); + ExpressionSyntaxDescriptor? Find(string syntax); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/IExpressionSyntaxRegistryPopulator.cs b/src/core/Elsa.Management/Contracts/IExpressionSyntaxRegistryPopulator.cs new file mode 100644 index 000000000..e9d1b25c3 --- /dev/null +++ b/src/core/Elsa.Management/Contracts/IExpressionSyntaxRegistryPopulator.cs @@ -0,0 +1,6 @@ +namespace Elsa.Management.Contracts; + +public interface IExpressionSyntaxRegistryPopulator +{ + ValueTask PopulateRegistryAsync(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/IPropertyDefaultValueResolver.cs b/src/core/Elsa.Management/Contracts/IPropertyDefaultValueResolver.cs new file mode 100644 index 000000000..2647f0d91 --- /dev/null +++ b/src/core/Elsa.Management/Contracts/IPropertyDefaultValueResolver.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +namespace Elsa.Management.Contracts; + +public interface IPropertyDefaultValueResolver +{ + object? GetDefaultValue(PropertyInfo activityPropertyInfo); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/IPropertyOptionsResolver.cs b/src/core/Elsa.Management/Contracts/IPropertyOptionsResolver.cs new file mode 100644 index 000000000..a624a4743 --- /dev/null +++ b/src/core/Elsa.Management/Contracts/IPropertyOptionsResolver.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +namespace Elsa.Management.Contracts; + +public interface IPropertyOptionsResolver +{ + object? GetOptions(PropertyInfo propertyInfo); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/ITriggerDescriber.cs b/src/core/Elsa.Management/Contracts/ITriggerDescriber.cs new file mode 100644 index 000000000..ddc958756 --- /dev/null +++ b/src/core/Elsa.Management/Contracts/ITriggerDescriber.cs @@ -0,0 +1,8 @@ +using Elsa.Management.Models; + +namespace Elsa.Management.Contracts; + +public interface ITriggerDescriber +{ + ValueTask DescribeTriggerAsync(Type triggerType, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/ITriggerFactory.cs b/src/core/Elsa.Management/Contracts/ITriggerFactory.cs new file mode 100644 index 000000000..f60b62dd8 --- /dev/null +++ b/src/core/Elsa.Management/Contracts/ITriggerFactory.cs @@ -0,0 +1,9 @@ +using Elsa.Contracts; +using Elsa.Management.Models; + +namespace Elsa.Management.Contracts; + +public interface ITriggerFactory +{ + ITrigger Create(Type type, TriggerConstructorContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/ITriggerProvider.cs b/src/core/Elsa.Management/Contracts/ITriggerProvider.cs new file mode 100644 index 000000000..d3c404cf7 --- /dev/null +++ b/src/core/Elsa.Management/Contracts/ITriggerProvider.cs @@ -0,0 +1,8 @@ +using Elsa.Management.Models; + +namespace Elsa.Management.Contracts; + +public interface ITriggerProvider +{ + ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/ITriggerRegistry.cs b/src/core/Elsa.Management/Contracts/ITriggerRegistry.cs new file mode 100644 index 000000000..4ac3a4b0b --- /dev/null +++ b/src/core/Elsa.Management/Contracts/ITriggerRegistry.cs @@ -0,0 +1,15 @@ +using Elsa.Management.Models; + +namespace Elsa.Management.Contracts; + +public interface ITriggerRegistry +{ + void Add(object provider, TriggerDescriptor descriptor); + void AddMany(object provider, IEnumerable descriptors); + void Clear(); + void ClearProvider(object provider); + IEnumerable ListAll(); + IEnumerable ListByProvider(object provider); + TriggerDescriptor? Find(Func predicate); + TriggerDescriptor? Find(string triggerType); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/ITriggerRegistryPopulator.cs b/src/core/Elsa.Management/Contracts/ITriggerRegistryPopulator.cs new file mode 100644 index 000000000..3363a5724 --- /dev/null +++ b/src/core/Elsa.Management/Contracts/ITriggerRegistryPopulator.cs @@ -0,0 +1,6 @@ +namespace Elsa.Management.Contracts; + +public interface ITriggerRegistryPopulator +{ + ValueTask PopulateRegistryAsync(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/IWellKnownTypeRegistry.cs b/src/core/Elsa.Management/Contracts/IWellKnownTypeRegistry.cs new file mode 100644 index 000000000..d6712d38d --- /dev/null +++ b/src/core/Elsa.Management/Contracts/IWellKnownTypeRegistry.cs @@ -0,0 +1,13 @@ +namespace Elsa.Management.Contracts; + +public interface IWellKnownTypeRegistry +{ + void RegisterType(Type type, string alias); + bool TryGetAlias(Type type, out string alias); + bool TryGetType(string alias, out Type type); +} + +public static class WellKnowTypeRegistryExtensions +{ + public static void RegisterType(this IWellKnownTypeRegistry registry, string alias) => registry.RegisterType(typeof(T), alias); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/IWorkflowPublisher.cs b/src/core/Elsa.Management/Contracts/IWorkflowPublisher.cs new file mode 100644 index 000000000..e83c9b9a8 --- /dev/null +++ b/src/core/Elsa.Management/Contracts/IWorkflowPublisher.cs @@ -0,0 +1,17 @@ +using Elsa.Models; + +namespace Elsa.Management.Contracts +{ + public interface IWorkflowPublisher + { + Workflow New(); + Task PublishAsync(string definitionId, CancellationToken cancellationToken = default); + Task PublishAsync(Workflow workflow, CancellationToken cancellationToken = default); + Task RetractAsync(string definitionId, CancellationToken cancellationToken = default); + Task RetractAsync(Workflow workflow, CancellationToken cancellationToken = default); + Task GetDraftAsync(string definitionId, CancellationToken cancellationToken= default); + Task SaveDraftAsync(Workflow workflow, CancellationToken cancellationToken = default); + Task DeleteAsync(string definitionId, CancellationToken cancellationToken = default); + Task DeleteAsync(Workflow workflow, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Contracts/WellKnownTypeRegistry.cs b/src/core/Elsa.Management/Contracts/WellKnownTypeRegistry.cs new file mode 100644 index 000000000..84183dc0c --- /dev/null +++ b/src/core/Elsa.Management/Contracts/WellKnownTypeRegistry.cs @@ -0,0 +1,26 @@ +namespace Elsa.Management.Contracts; + +public class WellKnownTypeRegistry : IWellKnownTypeRegistry +{ + private readonly IDictionary _aliasTypeDictionary = new Dictionary(); + private readonly IDictionary _typeAliasDictionary = new Dictionary(); + + public WellKnownTypeRegistry() + { + this.RegisterType("String"); + this.RegisterType("Int32"); + this.RegisterType("Int64"); + this.RegisterType("Decimal"); + this.RegisterType("Single"); + this.RegisterType("Double"); + } + + public void RegisterType(Type type, string alias) + { + _typeAliasDictionary[type] = alias; + _aliasTypeDictionary[alias] = type; + } + + public bool TryGetAlias(Type type, out string alias) => _typeAliasDictionary.TryGetValue(type, out alias!); + public bool TryGetType(string alias, out Type type) => _aliasTypeDictionary.TryGetValue(alias, out type!); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Elsa.Management.csproj b/src/core/Elsa.Management/Elsa.Management.csproj new file mode 100644 index 000000000..57c554a18 --- /dev/null +++ b/src/core/Elsa.Management/Elsa.Management.csproj @@ -0,0 +1,20 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + diff --git a/src/core/Elsa.Management/Extensions/ServiceCollectionExtensions.cs b/src/core/Elsa.Management/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..82267c3f8 --- /dev/null +++ b/src/core/Elsa.Management/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,41 @@ +using Elsa.Contracts; +using Elsa.Management.Contracts; +using Elsa.Management.Options; +using Elsa.Management.Providers; +using Elsa.Management.Serialization; +using Elsa.Management.Services; +using Elsa.Persistence.Mappers; +using Elsa.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Management.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddElsaManagement(this IServiceCollection services) + { + return services + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + } + + public static IServiceCollection ConfigureApiOptions(this IServiceCollection services, Action configure) => services.Configure(configure); + public static IServiceCollection AddActivity(this IServiceCollection services) where T:IActivity => services.ConfigureApiOptions(options => options.ActivityTypes.Add(typeof(T))); + public static IServiceCollection AddTrigger(this IServiceCollection services) where T:ITrigger => services.ConfigureApiOptions(options => options.TriggerTypes.Add(typeof(T))); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Extensions/TypeExtensions.cs b/src/core/Elsa.Management/Extensions/TypeExtensions.cs new file mode 100644 index 000000000..37cb5d798 --- /dev/null +++ b/src/core/Elsa.Management/Extensions/TypeExtensions.cs @@ -0,0 +1,8 @@ +namespace Elsa.Management.Extensions; + +public static class TypeExtensions +{ + public static bool IsGenericType(this Type type, Type genericType) => type.IsGenericType && type.GetGenericTypeDefinition() == genericType; + public static bool IsNullableType(this Type type) => type.IsGenericType(typeof(Nullable<>)); + public static Type GetTypeOfNullable(this Type type) => type.GenericTypeArguments[0]; +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Models/ActivityDescriptor.cs b/src/core/Elsa.Management/Models/ActivityDescriptor.cs new file mode 100644 index 000000000..16d2cd08f --- /dev/null +++ b/src/core/Elsa.Management/Models/ActivityDescriptor.cs @@ -0,0 +1,15 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Contracts; + +namespace Elsa.Management.Models; + +public class ActivityDescriptor : NodeDescriptor +{ + [JsonIgnore] public Func Constructor { get; init; } = default!; + public ActivityTraits Traits { get; set; } = ActivityTraits.Action; + public ICollection InPorts { get; init; } = new List(); + public ICollection OutPorts { get; init; } = new List(); +} + +public record ActivityConstructorContext(JsonElement Element, JsonSerializerOptions SerializerOptions); \ No newline at end of file diff --git a/src/core/Elsa.Management/Models/ActivityTraits.cs b/src/core/Elsa.Management/Models/ActivityTraits.cs new file mode 100644 index 000000000..a4111a00f --- /dev/null +++ b/src/core/Elsa.Management/Models/ActivityTraits.cs @@ -0,0 +1,8 @@ +namespace Elsa.Management.Models; + +[Flags] +public enum ActivityTraits +{ + Action = 1, + Trigger = 2 +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Models/ExpressionSyntaxDescriptor.cs b/src/core/Elsa.Management/Models/ExpressionSyntaxDescriptor.cs new file mode 100644 index 000000000..129dbfb37 --- /dev/null +++ b/src/core/Elsa.Management/Models/ExpressionSyntaxDescriptor.cs @@ -0,0 +1,26 @@ +using System.Text.Json; +using Elsa.Contracts; +using Elsa.Models; + +namespace Elsa.Management.Models; + +public class ExpressionSyntaxDescriptor +{ + public string Syntax { get; init; } = default!; + public Type Type { get; init; } = default!; + public Func CreateExpression { get; init; } = default!; + public Func CreateLocationReference { get; init; } = default!; + public Func CreateSerializableObject { get; init; } = default!; +} + +public record ExpressionConstructorContext(JsonElement Element, JsonSerializerOptions SerializerOptions); + +public record LocationReferenceConstructorContext(IExpression Expression) +{ + public T GetExpression() => (T)Expression; +} + +public record SerializableObjectConstructorContext(IExpression Expression) +{ + public T GetExpression() => (T)Expression; +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Models/InputDescriptor.cs b/src/core/Elsa.Management/Models/InputDescriptor.cs new file mode 100644 index 000000000..ae68d3276 --- /dev/null +++ b/src/core/Elsa.Management/Models/InputDescriptor.cs @@ -0,0 +1,46 @@ +namespace Elsa.Management.Models; + +public class InputDescriptor : PropertyDescriptor +{ + public InputDescriptor() + { + } + + public InputDescriptor( + string name, + Type type, + string uiHint, + string displayName, + string? description = default, + object? options = default, + string? category = default, + float order = 0, + object? defaultValue = default, + string? defaultSyntax = "Literal", + IEnumerable? supportedSyntaxes = default, + bool isReadOnly = false, + bool isBrowsable = true) + { + Name = name; + Type = type; + UIHint = uiHint; + DisplayName = displayName; + Description = description; + Options = options; + Category = category; + Order = order; + DefaultValue = defaultValue; + DefaultSyntax = defaultSyntax; + SupportedSyntaxes = supportedSyntaxes?.ToList() ?? new List(); + IsReadOnly = isReadOnly; + IsBrowsable = isBrowsable; + } + + public string UIHint { get; set; } = default!; + public object? Options { get; set; } + public string? Category { get; set; } + public object? DefaultValue { get; set; } + public string? DefaultSyntax { get; set; } + public ICollection SupportedSyntaxes { get; set; } = new List(); + public bool? IsReadOnly { get; set; } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Models/InputUIHints.cs b/src/core/Elsa.Management/Models/InputUIHints.cs new file mode 100644 index 000000000..0ed71854c --- /dev/null +++ b/src/core/Elsa.Management/Models/InputUIHints.cs @@ -0,0 +1,42 @@ +using System.Collections; +using Elsa.Attributes; +using Elsa.Management.Extensions; + +namespace Elsa.Management.Models; + +public static class InputUIHints +{ + public const string SingleLine = "single-line"; + public const string MultiLine = "multi-line"; + public const string Checkbox = "checkbox"; + public const string CheckList = "check-list"; + public const string RadioList = "radio-list"; + public const string Dropdown = "dropdown"; + public const string MultiText = "multi-text"; + public const string CodeEditor = "code-editor"; + + /// + /// An editor that allows the user to write a blob of JSON. + /// + public const string Json = "json"; + + public static string GetUIHint(Type wrappedPropertyType, InputAttribute? inputAttribute) + { + if (inputAttribute?.UIHint != null) + return inputAttribute.UIHint; + + if (wrappedPropertyType == typeof(bool) || wrappedPropertyType == typeof(bool?)) + return InputUIHints.Checkbox; + + if (wrappedPropertyType == typeof(string)) + return InputUIHints.SingleLine; + + if (typeof(IEnumerable).IsAssignableFrom(wrappedPropertyType)) + return InputUIHints.Dropdown; + + if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable().IsEnum) + return InputUIHints.Dropdown; + + return InputUIHints.SingleLine; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Models/NodeDescriptor.cs b/src/core/Elsa.Management/Models/NodeDescriptor.cs new file mode 100644 index 000000000..b1372f4dd --- /dev/null +++ b/src/core/Elsa.Management/Models/NodeDescriptor.cs @@ -0,0 +1,11 @@ +namespace Elsa.Management.Models; + +public abstract class NodeDescriptor +{ + public string NodeType { get; init; } = default!; + public string Category { get; init; } = default!; + public string? DisplayName { get; init; } + public string? Description { get; init; } + public ICollection InputProperties { get; init; } = new List(); + public ICollection OutputProperties { get; init; } = new List(); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Models/OutputDescriptor.cs b/src/core/Elsa.Management/Models/OutputDescriptor.cs new file mode 100644 index 000000000..ff658c1b0 --- /dev/null +++ b/src/core/Elsa.Management/Models/OutputDescriptor.cs @@ -0,0 +1,18 @@ +namespace Elsa.Management.Models; + +public class OutputDescriptor : PropertyDescriptor +{ + public OutputDescriptor() + { + } + + public OutputDescriptor( + string name, + Type type, + string? description = default) + { + Name = name; + Type = type; + Description = description; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Models/Port.cs b/src/core/Elsa.Management/Models/Port.cs new file mode 100644 index 000000000..8af193d43 --- /dev/null +++ b/src/core/Elsa.Management/Models/Port.cs @@ -0,0 +1,7 @@ +namespace Elsa.Management.Models; + +public class Port +{ + public string Name { get; set; } = default!; + public string? DisplayName { get; set; } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Models/PropertyDescriptor.cs b/src/core/Elsa.Management/Models/PropertyDescriptor.cs new file mode 100644 index 000000000..fc5e5aadc --- /dev/null +++ b/src/core/Elsa.Management/Models/PropertyDescriptor.cs @@ -0,0 +1,11 @@ +namespace Elsa.Management.Models; + +public abstract class PropertyDescriptor +{ + public string Name { get; set; } = default!; + public Type Type { get; set; } = default!; + public string? DisplayName { get; set; } + public string? Description { get; set; } + public float Order { get; set; } + public bool? IsBrowsable { get; set; } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Models/SelectListItem.cs b/src/core/Elsa.Management/Models/SelectListItem.cs new file mode 100644 index 000000000..20a5494fb --- /dev/null +++ b/src/core/Elsa.Management/Models/SelectListItem.cs @@ -0,0 +1,3 @@ +namespace Elsa.Management.Models; + +public record SelectListItem(string Text, string Value); \ No newline at end of file diff --git a/src/core/Elsa.Management/Models/TriggerDescriptor.cs b/src/core/Elsa.Management/Models/TriggerDescriptor.cs new file mode 100644 index 000000000..7be283e6d --- /dev/null +++ b/src/core/Elsa.Management/Models/TriggerDescriptor.cs @@ -0,0 +1,12 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Contracts; + +namespace Elsa.Management.Models; + +public class TriggerDescriptor : NodeDescriptor +{ + [JsonIgnore] public Func Constructor { get; init; } = default!; +} + +public record TriggerConstructorContext(JsonElement Element, JsonSerializerOptions SerializerOptions); \ No newline at end of file diff --git a/src/core/Elsa.Management/Notifications/WorkflowPublished.cs b/src/core/Elsa.Management/Notifications/WorkflowPublished.cs new file mode 100644 index 000000000..5bcee7c48 --- /dev/null +++ b/src/core/Elsa.Management/Notifications/WorkflowPublished.cs @@ -0,0 +1,5 @@ +using Elsa.Mediator.Contracts; +using Elsa.Models; + +namespace Elsa.Management.Notifications; +public record WorkflowPublished(Workflow Workflow) : INotification; \ No newline at end of file diff --git a/src/core/Elsa.Management/Notifications/WorkflowPublishing.cs b/src/core/Elsa.Management/Notifications/WorkflowPublishing.cs new file mode 100644 index 000000000..bdbe263ca --- /dev/null +++ b/src/core/Elsa.Management/Notifications/WorkflowPublishing.cs @@ -0,0 +1,6 @@ +using Elsa.Mediator.Contracts; +using Elsa.Models; + +namespace Elsa.Management.Notifications; + +public record WorkflowPublishing(Workflow Workflow) : INotification; \ No newline at end of file diff --git a/src/core/Elsa.Management/Notifications/WorkflowRetracted.cs b/src/core/Elsa.Management/Notifications/WorkflowRetracted.cs new file mode 100644 index 000000000..704281549 --- /dev/null +++ b/src/core/Elsa.Management/Notifications/WorkflowRetracted.cs @@ -0,0 +1,6 @@ +using Elsa.Mediator.Contracts; +using Elsa.Models; + +namespace Elsa.Management.Notifications; + +public record WorkflowRetracted(Workflow Workflow) : INotification; \ No newline at end of file diff --git a/src/core/Elsa.Management/Notifications/WorkflowRetracting.cs b/src/core/Elsa.Management/Notifications/WorkflowRetracting.cs new file mode 100644 index 000000000..ef842b146 --- /dev/null +++ b/src/core/Elsa.Management/Notifications/WorkflowRetracting.cs @@ -0,0 +1,6 @@ +using Elsa.Mediator.Contracts; +using Elsa.Models; + +namespace Elsa.Management.Notifications; + +public record WorkflowRetracting(Workflow Workflow) : INotification; \ No newline at end of file diff --git a/src/core/Elsa.Management/Options/ApiOptions.cs b/src/core/Elsa.Management/Options/ApiOptions.cs new file mode 100644 index 000000000..fbaef310b --- /dev/null +++ b/src/core/Elsa.Management/Options/ApiOptions.cs @@ -0,0 +1,14 @@ +namespace Elsa.Management.Options; + +public class ApiOptions +{ + /// + /// A collection of activity types made available from the API. + /// + public HashSet ActivityTypes { get; set; } = new(); + + /// + /// A collection of trigger types made available from the API. + /// + public HashSet TriggerTypes { get; set; } = new(); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Providers/DefaultExpressionSyntaxProvider.cs b/src/core/Elsa.Management/Providers/DefaultExpressionSyntaxProvider.cs new file mode 100644 index 000000000..ddb15cd51 --- /dev/null +++ b/src/core/Elsa.Management/Providers/DefaultExpressionSyntaxProvider.cs @@ -0,0 +1,77 @@ +using Elsa.Contracts; +using Elsa.Expressions; +using Elsa.Management.Contracts; +using Elsa.Management.Models; +using Elsa.Models; + +namespace Elsa.Management.Providers; + +public class DefaultExpressionSyntaxProvider : IExpressionSyntaxProvider +{ + private readonly IIdentityGenerator _identityGenerator; + + public DefaultExpressionSyntaxProvider(IIdentityGenerator identityGenerator) + { + _identityGenerator = identityGenerator; + } + + public ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default) + { + var literal = CreateLiteralDescriptor(); + var json = CreateJsonDescriptor(); + + return ValueTask.FromResult>(new[] { literal, json }); + } + + private ExpressionSyntaxDescriptor CreateLiteralDescriptor() => CreateDescriptor( + "Literal", + CreateLiteralExpression, + context => new Literal(context.GetExpression().Value), + expression => expression.Value); + + private ExpressionSyntaxDescriptor CreateJsonDescriptor() => CreateDescriptor( + "Json", + CreateJsonExpression, + context => new JsonObject(context.GetExpression().Value), + expression => expression.Value); + + private ExpressionSyntaxDescriptor CreateDescriptor( + string syntax, + Func constructor, + Func createLocationReference, + Func expressionValue) => + new() + { + Syntax = syntax, + Type = typeof(TExpression), + CreateExpression = constructor, + CreateLocationReference = context => + { + var reference = createLocationReference(context); + + if (string.IsNullOrWhiteSpace(reference.Id)) + reference.Id = GenerateId(); + + return reference; + }, + CreateSerializableObject = context => new + { + Type = syntax, + Value = expressionValue(context.GetExpression()) + } + }; + + private IExpression CreateLiteralExpression(ExpressionConstructorContext context) + { + var expressionValue = context.Element.GetProperty("value").ToString(); + return new LiteralExpression(expressionValue); + } + + private IExpression CreateJsonExpression(ExpressionConstructorContext context) + { + var expressionValue = context.Element.GetProperty("value").ToString(); + return new JsonExpression(expressionValue); + } + + private string GenerateId() => _identityGenerator.GenerateId(); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Providers/TypedActivityProvider.cs b/src/core/Elsa.Management/Providers/TypedActivityProvider.cs new file mode 100644 index 000000000..46ada1fb2 --- /dev/null +++ b/src/core/Elsa.Management/Providers/TypedActivityProvider.cs @@ -0,0 +1,35 @@ +using System.Runtime.CompilerServices; +using Elsa.Management.Contracts; +using Elsa.Management.Models; +using Elsa.Management.Options; +using Microsoft.Extensions.Options; + +namespace Elsa.Management.Providers; + +public class TypedActivityProvider : IActivityProvider +{ + private readonly IActivityDescriber _activityDescriber; + private readonly ApiOptions _options; + + public TypedActivityProvider(IOptions options, IActivityDescriber activityDescriber) + { + _activityDescriber = activityDescriber; + _options = options.Value; + } + + public async ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default) + { + var activityTypes = _options.ActivityTypes; + var descriptors = await DescribeActivityTypesAsync(activityTypes, cancellationToken).ToListAsync(cancellationToken); + return descriptors; + } + + public async IAsyncEnumerable DescribeActivityTypesAsync(IEnumerable activityTypes, [EnumeratorCancellation] CancellationToken cancellationToken) + { + foreach (var activityType in activityTypes) + { + var descriptor = await _activityDescriber.DescribeActivityAsync(activityType, cancellationToken); + yield return descriptor; + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Providers/TypedTriggerProvider.cs b/src/core/Elsa.Management/Providers/TypedTriggerProvider.cs new file mode 100644 index 000000000..7c9846fc4 --- /dev/null +++ b/src/core/Elsa.Management/Providers/TypedTriggerProvider.cs @@ -0,0 +1,35 @@ +using System.Runtime.CompilerServices; +using Elsa.Management.Contracts; +using Elsa.Management.Models; +using Elsa.Management.Options; +using Microsoft.Extensions.Options; + +namespace Elsa.Management.Providers; + +public class TypedTriggerProvider : ITriggerProvider +{ + private readonly ITriggerDescriber _triggerDescriber; + private readonly ApiOptions _options; + + public TypedTriggerProvider(IOptions options, ITriggerDescriber triggerDescriber) + { + _triggerDescriber = triggerDescriber; + _options = options.Value; + } + + public async ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default) + { + var triggerTypes = _options.TriggerTypes; + var descriptors = await DescribeTriggerTypesAsync(triggerTypes, cancellationToken).ToListAsync(cancellationToken); + return descriptors; + } + + public async IAsyncEnumerable DescribeTriggerTypesAsync(IEnumerable triggerTypes, [EnumeratorCancellation] CancellationToken cancellationToken) + { + foreach (var triggerType in triggerTypes) + { + var descriptor = await _triggerDescriber.DescribeTriggerAsync(triggerType, cancellationToken); + yield return descriptor; + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Serialization/Converters/ActivityJsonConverter.cs b/src/core/Elsa.Management/Serialization/Converters/ActivityJsonConverter.cs new file mode 100644 index 000000000..eccbdfc7c --- /dev/null +++ b/src/core/Elsa.Management/Serialization/Converters/ActivityJsonConverter.cs @@ -0,0 +1,52 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Contracts; +using Elsa.Management.Contracts; +using Elsa.Management.Models; + +namespace Elsa.Management.Serialization.Converters; + +/// +/// (De)serializes objects of type . +/// +public class ActivityJsonConverter : JsonConverter +{ + private readonly IActivityRegistry _activityRegistry; + private readonly IServiceProvider _serviceProvider; + + public ActivityJsonConverter(IActivityRegistry activityRegistry, IServiceProvider serviceProvider) + { + _activityRegistry = activityRegistry; + _serviceProvider = serviceProvider; + } + + public override IActivity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (!JsonDocument.TryParseValue(ref reader, out var doc)) + throw new JsonException("Failed to parse JsonDocument"); + + if (!doc.RootElement.TryGetProperty("nodeType", out var activityTypeNameElement)) + throw new JsonException("Failed to extract activity type property"); + + var activityTypeName = activityTypeNameElement.GetString()!; + var activityDescriptor = _activityRegistry.Find(activityTypeName); + + if (activityDescriptor == null) + throw new Exception($"Activity of type {activityTypeName} not found in registry"); + + var newOptions = new JsonSerializerOptions(options); + newOptions.Converters.Add(new InputJsonConverterFactory(_serviceProvider)); + + var context = new ActivityConstructorContext(doc.RootElement, newOptions); + var activity = activityDescriptor.Constructor(context); + + return activity; + } + + public override void Write(Utf8JsonWriter writer, IActivity value, JsonSerializerOptions options) + { + var newOptions = new JsonSerializerOptions(options); + newOptions.Converters.Add(new InputJsonConverterFactory(_serviceProvider)); + JsonSerializer.Serialize(writer, value, value.GetType(), newOptions); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Serialization/Converters/ActivityJsonConverterFactory.cs b/src/core/Elsa.Management/Serialization/Converters/ActivityJsonConverterFactory.cs new file mode 100644 index 000000000..de29c0253 --- /dev/null +++ b/src/core/Elsa.Management/Serialization/Converters/ActivityJsonConverterFactory.cs @@ -0,0 +1,24 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Contracts; +using Elsa.Management.Contracts; + +namespace Elsa.Management.Serialization.Converters; + +public class ActivityJsonConverterFactory : JsonConverterFactory +{ + private readonly IActivityRegistry _activityRegistry; + private readonly IServiceProvider _serviceProvider; + + public ActivityJsonConverterFactory(IActivityRegistry activityRegistry, IServiceProvider serviceProvider) + { + _activityRegistry = activityRegistry; + _serviceProvider = serviceProvider; + } + + // Notice that this factory only creates converters when the type to convert is IActivity. + // The ActivityJsonConverter will create concrete activity objects, which then uses regular serialization + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(IActivity); + + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new ActivityJsonConverter(_activityRegistry, _serviceProvider); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Serialization/Converters/ConnectionJsonConverter.cs b/src/core/Elsa.Management/Serialization/Converters/ConnectionJsonConverter.cs new file mode 100644 index 000000000..c0b64eabc --- /dev/null +++ b/src/core/Elsa.Management/Serialization/Converters/ConnectionJsonConverter.cs @@ -0,0 +1,47 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Activities.Workflows; +using Elsa.Contracts; + +namespace Elsa.Management.Serialization.Converters; + +public class ConnectionJsonConverter : JsonConverter +{ + private readonly IDictionary _activities; + + public ConnectionJsonConverter(IDictionary activities) + { + _activities = activities; + } + + public override Connection? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (!JsonDocument.TryParseValue(ref reader, out var doc)) + throw new JsonException("Failed to parse JsonDocument"); + + var sourceId = doc.RootElement.GetProperty("source").GetString()!; + var targetId = doc.RootElement.GetProperty("target").GetString()!; + var sourcePort = doc.RootElement.GetProperty("sourcePort").GetString()!; + var targetPort = doc.RootElement.GetProperty("targetPort").GetString()!; + + var source = _activities[sourceId]; + var target = _activities[targetId]; + + return new Connection(source, target, sourcePort, targetPort); + } + + public override void Write(Utf8JsonWriter writer, Connection value, JsonSerializerOptions options) + { + var (activity, target, sourcePort, targetPort) = value; + + var model = new + { + Source = activity.Id, + Target = target.Id, + SourcePort = sourcePort, + TargetPort = targetPort + }; + + JsonSerializer.Serialize(writer, model, options); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Serialization/Converters/FlowchartJsonConverter.cs b/src/core/Elsa.Management/Serialization/Converters/FlowchartJsonConverter.cs new file mode 100644 index 000000000..51a107f3f --- /dev/null +++ b/src/core/Elsa.Management/Serialization/Converters/FlowchartJsonConverter.cs @@ -0,0 +1,71 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Activities.Workflows; +using Elsa.Contracts; +using Elsa.Extensions; + +namespace Elsa.Management.Serialization.Converters; + +public class FlowchartJsonConverter : JsonConverter +{ + private readonly IActivityWalker _activityWalker; + + public FlowchartJsonConverter(IActivityWalker activityWalker) + { + _activityWalker = activityWalker; + } + + public override Flowchart? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (!JsonDocument.TryParseValue(ref reader, out var doc)) + throw new JsonException("Failed to parse JsonDocument"); + + var connectionsElement = doc.RootElement.GetProperty("connections"); + var metadataElement = doc.RootElement.GetProperty("metadata"); + var activitiesElement = doc.RootElement.GetProperty("activities"); + var id = doc.RootElement.GetProperty("id").GetString()!; + var startId = doc.RootElement.TryGetProperty("start", out var startElement) ? startElement.GetString() : default; + var rootActivities = activitiesElement.Deserialize(options) ?? Array.Empty(); + var metadata = metadataElement.Deserialize>(options) ?? new Dictionary(); + var start = rootActivities.FirstOrDefault(x => x.Id == startId) ?? rootActivities.FirstOrDefault(); + var connectionSerializerOptions = new JsonSerializerOptions(options); + var activities = WalkActivityTree(rootActivities).ToList(); + var activityDictionary = activities.ToDictionary(x => x.Id); + connectionSerializerOptions.Converters.Add(new ConnectionJsonConverter(activityDictionary)); + + var connections = connectionsElement.Deserialize(connectionSerializerOptions) ?? Array.Empty(); + + return new Flowchart + { + Id = id, + Metadata = metadata, + Activities = rootActivities, + Connections = connections, + Start = start, + }; + } + + public override void Write(Utf8JsonWriter writer, Flowchart value, JsonSerializerOptions options) + { + var activities = value.Activities; + var connectionSerializerOptions = new JsonSerializerOptions(options); + var activityDictionary = activities.ToDictionary(x => x.Id); + connectionSerializerOptions.Converters.Add(new ConnectionJsonConverter(activityDictionary)); + + var model = new + { + NodeType = value.NodeType, + Id = value.Id, + Metadata = value.Metadata, + Start = value.Start?.Id, + Activities = value.Activities, + Connections = value.Connections + }; + + JsonSerializer.Serialize(writer, model, connectionSerializerOptions); + } + + private IEnumerable WalkActivityTree(IEnumerable rootActivities) => rootActivities + .Select(rootActivity => _activityWalker.Walk(rootActivity)) + .SelectMany(node => node.Flatten().Select(x => x.Activity)); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Serialization/Converters/InputJsonConverter.cs b/src/core/Elsa.Management/Serialization/Converters/InputJsonConverter.cs new file mode 100644 index 000000000..1b941f18c --- /dev/null +++ b/src/core/Elsa.Management/Serialization/Converters/InputJsonConverter.cs @@ -0,0 +1,67 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Management.Contracts; +using Elsa.Management.Models; +using Elsa.Models; + +namespace Elsa.Management.Serialization.Converters; + +/// +/// Serializes objects. +/// +public class InputJsonConverter : JsonConverter> +{ + private readonly IExpressionSyntaxRegistry _expressionSyntaxRegistry; + + public InputJsonConverter(IExpressionSyntaxRegistry expressionSyntaxRegistry) + { + _expressionSyntaxRegistry = expressionSyntaxRegistry; + } + + public override bool CanConvert(Type typeToConvert) => typeof(Input).IsAssignableFrom(typeToConvert); + + public override Input Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (!JsonDocument.TryParseValue(ref reader, out var doc)) + throw new JsonException("Failed to parse JsonDocument"); + + if (!doc.RootElement.TryGetProperty("type", out var inputTargetTypeElement)) + throw new JsonException("Failed to extract input type property"); + + var expressionElement = doc.RootElement.GetProperty("expression"); + + if (!expressionElement.TryGetProperty("type", out var expressionTypeNameElement)) + throw new JsonException("Failed to extract expression type property"); + + var expressionTypeName = expressionTypeNameElement.GetString()!; + var expressionSyntaxDescriptor = _expressionSyntaxRegistry.Find(expressionTypeName); + + if (expressionSyntaxDescriptor == null) + throw new Exception($"Syntax with name {expressionTypeName} not found in registry"); + + var context = new ExpressionConstructorContext(expressionElement, options); + var expression = expressionSyntaxDescriptor.CreateExpression(context); + var locationReference = expressionSyntaxDescriptor.CreateLocationReference(new LocationReferenceConstructorContext(expression)); + + return (Input)Activator.CreateInstance(typeof(Input), expression, locationReference)!; + } + + public override void Write(Utf8JsonWriter writer, Input value, JsonSerializerOptions options) + { + var expression = value.Expression; + var expressionType = expression.GetType(); + var targetType = value.Type; + var expressionSyntaxDescriptor = _expressionSyntaxRegistry.Find(x => x.Type == expressionType); + + if (expressionSyntaxDescriptor == null) + throw new Exception($"Syntax descriptor with expression type {expressionType} not found in registry"); + + var model = new + { + Type = targetType, + Expression = expressionSyntaxDescriptor.CreateSerializableObject(new SerializableObjectConstructorContext(expression)) + }; + + JsonSerializer.Serialize(writer, model, options); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Serialization/Converters/InputJsonConverterFactory.cs b/src/core/Elsa.Management/Serialization/Converters/InputJsonConverterFactory.cs new file mode 100644 index 000000000..57588957a --- /dev/null +++ b/src/core/Elsa.Management/Serialization/Converters/InputJsonConverterFactory.cs @@ -0,0 +1,19 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Models; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Management.Serialization.Converters; + +public class InputJsonConverterFactory : JsonConverterFactory +{ + private readonly IServiceProvider _serviceProvider; + public InputJsonConverterFactory(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + public override bool CanConvert(Type typeToConvert) => typeof(Input).IsAssignableFrom(typeToConvert); + + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + var type = typeToConvert.GetGenericArguments().First(); + return (JsonConverter)ActivatorUtilities.CreateInstance(_serviceProvider, typeof(InputJsonConverter<>).MakeGenericType(type))!; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Serialization/Converters/TriggerJsonConverter.cs b/src/core/Elsa.Management/Serialization/Converters/TriggerJsonConverter.cs new file mode 100644 index 000000000..02fc58cd5 --- /dev/null +++ b/src/core/Elsa.Management/Serialization/Converters/TriggerJsonConverter.cs @@ -0,0 +1,52 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Contracts; +using Elsa.Management.Contracts; +using Elsa.Management.Models; + +namespace Elsa.Management.Serialization.Converters; + +/// +/// (De)serializes objects of type . +/// +public class TriggerJsonConverter : JsonConverter +{ + private readonly ITriggerRegistry _triggerRegistry; + private readonly IServiceProvider _serviceProvider; + + public TriggerJsonConverter(ITriggerRegistry triggerRegistry, IServiceProvider serviceProvider) + { + _triggerRegistry = triggerRegistry; + _serviceProvider = serviceProvider; + } + + public override ITrigger Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (!JsonDocument.TryParseValue(ref reader, out var doc)) + throw new JsonException("Failed to parse JsonDocument"); + + if (!doc.RootElement.TryGetProperty("nodeType", out var triggerTypeNameElement)) + throw new JsonException("Failed to extract trigger type property"); + + var triggerType = triggerTypeNameElement.GetString()!; + var triggerDescriptor = _triggerRegistry.Find(triggerType); + + if (triggerDescriptor == null) + throw new Exception($"Trigger of type {triggerType} not found in registry"); + + var newOptions = new JsonSerializerOptions(options); + newOptions.Converters.Add(new InputJsonConverterFactory(_serviceProvider)); + + var context = new TriggerConstructorContext(doc.RootElement, newOptions); + var activity = triggerDescriptor.Constructor(context); + + return activity; + } + + public override void Write(Utf8JsonWriter writer, ITrigger value, JsonSerializerOptions options) + { + var newOptions = new JsonSerializerOptions(options); + newOptions.Converters.Add(new InputJsonConverterFactory(_serviceProvider)); + JsonSerializer.Serialize(writer, value, value.GetType(), newOptions); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Serialization/Converters/TriggerJsonConverterFactory.cs b/src/core/Elsa.Management/Serialization/Converters/TriggerJsonConverterFactory.cs new file mode 100644 index 000000000..26f0e396a --- /dev/null +++ b/src/core/Elsa.Management/Serialization/Converters/TriggerJsonConverterFactory.cs @@ -0,0 +1,24 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Contracts; +using Elsa.Management.Contracts; + +namespace Elsa.Management.Serialization.Converters; + +public class TriggerJsonConverterFactory : JsonConverterFactory +{ + private readonly ITriggerRegistry _triggerRegistry; + private readonly IServiceProvider _serviceProvider; + + public TriggerJsonConverterFactory(ITriggerRegistry triggerRegistry, IServiceProvider serviceProvider) + { + _triggerRegistry = triggerRegistry; + _serviceProvider = serviceProvider; + } + + // Notice that this factory only creates converters when the type to convert is ITrigger. + // The TriggerJsonConverter will create concrete trigger objects, which then uses regular serialization + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(ITrigger); + + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new TriggerJsonConverter(_triggerRegistry, _serviceProvider); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Serialization/Converters/TypeJsonConverter.cs b/src/core/Elsa.Management/Serialization/Converters/TypeJsonConverter.cs new file mode 100644 index 000000000..49b12ae6a --- /dev/null +++ b/src/core/Elsa.Management/Serialization/Converters/TypeJsonConverter.cs @@ -0,0 +1,53 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Management.Contracts; + +namespace Elsa.Management.Serialization.Converters; + +/// +/// Serializes objects to a simple alias representing said type. +/// +public class TypeJsonConverter : JsonConverter +{ + private readonly IWellKnownTypeRegistry _wellKnownTypeRegistry; + + public TypeJsonConverter(IWellKnownTypeRegistry wellKnownTypeRegistry) + { + _wellKnownTypeRegistry = wellKnownTypeRegistry; + } + + public override Type? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var typeAlias = reader.GetString()!; + + // Handle collection types. + if (typeAlias.EndsWith("[]")) + { + var elementTypeAlias = typeAlias[..^"[]".Length]; + var elementType = _wellKnownTypeRegistry.TryGetType(typeAlias, out var t) ? t : Type.GetType(elementTypeAlias)!; + return typeof(List<>).MakeGenericType(elementType); + } + + return _wellKnownTypeRegistry.TryGetType(typeAlias, out var type) ? type : Type.GetType(typeAlias); + } + + public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options) + { + // Handle collection types. + if (value.IsGenericType) + { + var elementType = value.GenericTypeArguments.First(); + var typedEnumerable = typeof(IEnumerable<>).MakeGenericType(elementType); + + if (typedEnumerable.IsAssignableFrom(value)) + { + var elementTypeAlias = _wellKnownTypeRegistry.TryGetAlias(elementType, out var elementAlias) ? elementAlias : value.AssemblyQualifiedName; + JsonSerializer.Serialize(writer, $"{elementTypeAlias}[]", options); + return; + } + } + + var typeAlias = _wellKnownTypeRegistry.TryGetAlias(value, out var @alias) ? alias : value.AssemblyQualifiedName; + JsonSerializer.Serialize(writer, typeAlias, options); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Serialization/Extensions/JsonSerializerExtensions.cs b/src/core/Elsa.Management/Serialization/Extensions/JsonSerializerExtensions.cs new file mode 100644 index 000000000..56c13e646 --- /dev/null +++ b/src/core/Elsa.Management/Serialization/Extensions/JsonSerializerExtensions.cs @@ -0,0 +1,12 @@ +using System.Text.Json; + +namespace Elsa.Management.Serialization.Extensions; + +public static class JsonSerializerExtensions +{ + public static T? DeserializeAnonymousType(string json, T anonymousTypeObject, JsonSerializerOptions? options = default) + => JsonSerializer.Deserialize(json, options); + + public static ValueTask DeserializeAnonymousTypeAsync(Stream stream, TValue anonymousTypeObject, JsonSerializerOptions? options = default, CancellationToken cancellationToken = default) + => JsonSerializer.DeserializeAsync(stream, options, cancellationToken); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Serialization/WorkflowSerializerOptionsProvider.cs b/src/core/Elsa.Management/Serialization/WorkflowSerializerOptionsProvider.cs new file mode 100644 index 000000000..af74b86be --- /dev/null +++ b/src/core/Elsa.Management/Serialization/WorkflowSerializerOptionsProvider.cs @@ -0,0 +1,27 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Management.Serialization.Converters; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Management.Serialization; + +public class WorkflowSerializerOptionsProvider +{ + private readonly IServiceProvider _serviceProvider; + public WorkflowSerializerOptionsProvider(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + + public JsonSerializerOptions CreateSerializerOptions() => new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = + { + Create(), + Create(), + Create(), + Create(), + Create() + } + }; + + private T Create() => ActivatorUtilities.CreateInstance(_serviceProvider); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Services/ActivityDescriber.cs b/src/core/Elsa.Management/Services/ActivityDescriber.cs new file mode 100644 index 000000000..e3793f934 --- /dev/null +++ b/src/core/Elsa.Management/Services/ActivityDescriber.cs @@ -0,0 +1,139 @@ +using System.Collections; +using System.ComponentModel; +using System.Reflection; +using Elsa.Attributes; +using Elsa.Contracts; +using Elsa.Helpers; +using Elsa.Management.Contracts; +using Elsa.Management.Extensions; +using Elsa.Management.Models; +using Elsa.Models; +using Humanizer; + +namespace Elsa.Management.Services; + +public class ActivityDescriber : IActivityDescriber +{ + private readonly IPropertyOptionsResolver _optionsResolver; + private readonly IPropertyDefaultValueResolver _defaultValueResolver; + private readonly IActivityFactory _activityFactory; + + public ActivityDescriber(IPropertyOptionsResolver optionsResolver, IPropertyDefaultValueResolver defaultValueResolver, IActivityFactory activityFactory) + { + _optionsResolver = optionsResolver; + _defaultValueResolver = defaultValueResolver; + _activityFactory = activityFactory; + } + + public ValueTask DescribeActivityAsync(Type activityType, CancellationToken cancellationToken = default) + { + var ns = TypeNameHelper.GenerateNamespace(activityType); + var typeName = activityType.Name; + var fullTypeName = TypeNameHelper.GenerateTypeName(activityType, ns); + var displayNameAttr = activityType.GetCustomAttribute(); + var displayName = displayNameAttr?.DisplayName ?? typeName.Humanize(LetterCasing.Title); + var categoryAttr = activityType.GetCustomAttribute(); + var category = categoryAttr?.Category ?? TypeNameHelper.GetCategoryFromNamespace(ns) ?? "Miscellaneous"; + var descriptionAttr = activityType.GetCustomAttribute(); + var description = descriptionAttr?.Description; + + var outboundPorts = + from prop in activityType.GetProperties(BindingFlags.Public | BindingFlags.Instance) + where typeof(IActivity).IsAssignableFrom(prop.PropertyType) || typeof(IEnumerable).IsAssignableFrom(prop.PropertyType) + let portAttr = prop.GetCustomAttribute() + where portAttr != null + select new Port + { + Name = portAttr.Name ?? prop.Name, + DisplayName = portAttr.DisplayName ?? portAttr.Name ?? prop.Name + }; + + var properties = activityType.GetProperties(); + var inputProperties = properties.Where(x => typeof(Input).IsAssignableFrom(x.PropertyType)).ToList(); + var outputProperties = properties.Where(x => typeof(Output).IsAssignableFrom(x.PropertyType)).ToList(); + var isTrigger = activityType.IsAssignableTo(typeof(ITrigger)); + + var descriptor = new ActivityDescriptor + { + Category = category, + Description = description, + NodeType = fullTypeName, + DisplayName = displayName, + Traits = isTrigger ? ActivityTraits.Trigger : ActivityTraits.Action, + OutPorts = outboundPorts.ToList(), + InputProperties = DescribeInputProperties(inputProperties).ToList(), + OutputProperties = DescribeOutputProperties(outputProperties).ToList(), + Constructor = context => + { + var activity = _activityFactory.Create(activityType, context); + activity.NodeType = fullTypeName; + return activity; + } + }; + + return ValueTask.FromResult(descriptor); + } + + private IEnumerable DescribeInputProperties(IEnumerable properties) + { + foreach (var propertyInfo in properties) + { + var inputAttribute = propertyInfo.GetCustomAttribute(); + var descriptionAttribute = propertyInfo.GetCustomAttribute(); + var wrappedPropertyType = propertyInfo.PropertyType.GenericTypeArguments[0]; + + yield return new InputDescriptor + ( + inputAttribute?.Name ?? propertyInfo.Name, + wrappedPropertyType, + GetUIHint(wrappedPropertyType, inputAttribute), + inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title), + descriptionAttribute?.Description, + _optionsResolver.GetOptions(propertyInfo), + inputAttribute?.Category, + inputAttribute?.Order ?? 0, + _defaultValueResolver.GetDefaultValue(propertyInfo), + inputAttribute?.DefaultSyntax, + inputAttribute?.SupportedSyntaxes, + inputAttribute?.IsReadOnly ?? false, + inputAttribute?.IsBrowsable ?? true + ); + } + } + + private IEnumerable DescribeOutputProperties(IEnumerable properties) + { + foreach (var propertyInfo in properties) + { + var activityPropertyAttribute = propertyInfo.GetCustomAttribute(); + var wrappedPropertyType = propertyInfo.PropertyType.GenericTypeArguments[0]; + + yield return new OutputDescriptor + ( + (activityPropertyAttribute?.Name ?? propertyInfo.Name).Pascalize(), + wrappedPropertyType, + activityPropertyAttribute?.Description + ); + } + } + + private string GetUIHint(Type wrappedPropertyType, InputAttribute? inputAttribute) + { + if (inputAttribute?.UIHint != null) + return inputAttribute.UIHint; + + if (wrappedPropertyType == typeof(bool) || wrappedPropertyType == typeof(bool?)) + return InputUIHints.Checkbox; + + if (wrappedPropertyType == typeof(string)) + return InputUIHints.SingleLine; + + if (typeof(IEnumerable).IsAssignableFrom(wrappedPropertyType)) + return InputUIHints.Dropdown; + + if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable().IsEnum) + return InputUIHints.Dropdown; + + return InputUIHints.SingleLine; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Services/ActivityFactory.cs b/src/core/Elsa.Management/Services/ActivityFactory.cs new file mode 100644 index 000000000..e74463cdf --- /dev/null +++ b/src/core/Elsa.Management/Services/ActivityFactory.cs @@ -0,0 +1,15 @@ +using System.Text.Json; +using Elsa.Contracts; +using Elsa.Management.Contracts; +using Elsa.Management.Models; + +namespace Elsa.Management.Services; + +public class ActivityFactory : IActivityFactory +{ + public IActivity Create(Type type, ActivityConstructorContext context) + { + var activity = (IActivity)context.Element.Deserialize(type, context.SerializerOptions)!; + return activity; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Services/ActivityRegistry.cs b/src/core/Elsa.Management/Services/ActivityRegistry.cs new file mode 100644 index 000000000..3ce53ba3d --- /dev/null +++ b/src/core/Elsa.Management/Services/ActivityRegistry.cs @@ -0,0 +1,58 @@ +using Elsa.Management.Contracts; +using Elsa.Management.Models; + +namespace Elsa.Management.Services; + +public class ActivityRegistry : IActivityRegistry +{ + private readonly IDictionary> _providedActivityDescriptors = new Dictionary>(); + private readonly IDictionary _activityDescriptors = new Dictionary(); + + public void Add(object provider, ActivityDescriptor descriptor) => Add(descriptor, GetOrCreateDescriptors(provider)); + + public void AddMany(object provider, IEnumerable descriptors) + { + var target = GetOrCreateDescriptors(provider); + + foreach (var descriptor in descriptors) + Add(descriptor, target); + } + + public void Clear() + { + _activityDescriptors.Clear(); + _providedActivityDescriptors.Clear(); + } + + public void ClearProvider(object provider) + { + var descriptors = ListByProvider(provider).ToList(); + + foreach (var descriptor in descriptors) + _activityDescriptors.Remove(descriptor.NodeType); + + _providedActivityDescriptors.Remove(provider); + } + + public IEnumerable ListAll() => _activityDescriptors.Values; + public IEnumerable ListByProvider(object provider) => _providedActivityDescriptors.TryGetValue(provider, out var descriptors) ? descriptors : ArraySegment.Empty; + public ActivityDescriptor? Find(Func predicate) => _activityDescriptors.Values.FirstOrDefault(predicate); + public ActivityDescriptor? Find(string activityType) => _activityDescriptors.TryGetValue(activityType, out var descriptor) ? descriptor : null; + + private void Add(ActivityDescriptor descriptor, ICollection target) + { + _activityDescriptors.Add(descriptor.NodeType, descriptor); + target.Add(descriptor); + } + + private ICollection GetOrCreateDescriptors(object provider) + { + if (_providedActivityDescriptors.TryGetValue(provider, out var descriptors)) + return descriptors; + + descriptors = new List(); + _providedActivityDescriptors.Add(provider, descriptors); + + return descriptors; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Services/ActivityRegistryPopulator.cs b/src/core/Elsa.Management/Services/ActivityRegistryPopulator.cs new file mode 100644 index 000000000..c5ecbb207 --- /dev/null +++ b/src/core/Elsa.Management/Services/ActivityRegistryPopulator.cs @@ -0,0 +1,27 @@ +using Elsa.Management.Contracts; + +namespace Elsa.Management.Services; + +/// +/// Populates the with descriptors provided by s. +/// +public class ActivityRegistryPopulator : IActivityRegistryPopulator +{ + private readonly IEnumerable _providers; + private readonly IActivityRegistry _registry; + + public ActivityRegistryPopulator(IEnumerable providers, IActivityRegistry registry) + { + _providers = providers; + _registry = registry; + } + + public async ValueTask PopulateRegistryAsync(CancellationToken cancellationToken) + { + foreach (var provider in _providers) + { + var descriptors = await provider.GetDescriptorsAsync(cancellationToken); + _registry.AddMany(provider, descriptors); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Services/ExpressionSyntaxRegistry.cs b/src/core/Elsa.Management/Services/ExpressionSyntaxRegistry.cs new file mode 100644 index 000000000..b18295bc7 --- /dev/null +++ b/src/core/Elsa.Management/Services/ExpressionSyntaxRegistry.cs @@ -0,0 +1,20 @@ +using Elsa.Management.Contracts; +using Elsa.Management.Models; + +namespace Elsa.Management.Services; + +public class ExpressionSyntaxRegistry : IExpressionSyntaxRegistry +{ + private readonly IDictionary _expressionSyntaxDescriptors = new Dictionary(); + public void Add(ExpressionSyntaxDescriptor descriptor) => _expressionSyntaxDescriptors[descriptor.Syntax] = descriptor; + + public void AddMany(IEnumerable descriptors) + { + foreach (var descriptor in descriptors) + Add(descriptor); + } + + public IEnumerable ListAll() => _expressionSyntaxDescriptors.Values; + public ExpressionSyntaxDescriptor? Find(Func predicate) => _expressionSyntaxDescriptors.Values.FirstOrDefault(predicate); + public ExpressionSyntaxDescriptor? Find(string syntax) => Find(x => x.Syntax == syntax); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Services/ExpressionSyntaxRegistryPopulator.cs b/src/core/Elsa.Management/Services/ExpressionSyntaxRegistryPopulator.cs new file mode 100644 index 000000000..9e79a046f --- /dev/null +++ b/src/core/Elsa.Management/Services/ExpressionSyntaxRegistryPopulator.cs @@ -0,0 +1,24 @@ +using Elsa.Management.Contracts; + +namespace Elsa.Management.Services; + +public class ExpressionSyntaxRegistryPopulator : IExpressionSyntaxRegistryPopulator +{ + private readonly IEnumerable _providers; + private readonly IExpressionSyntaxRegistry _registry; + + public ExpressionSyntaxRegistryPopulator(IEnumerable providers, IExpressionSyntaxRegistry registry) + { + _providers = providers; + _registry = registry; + } + + public async ValueTask PopulateRegistryAsync(CancellationToken cancellationToken) + { + foreach (var provider in _providers) + { + var descriptors = await provider.GetDescriptorsAsync(cancellationToken); + _registry.AddMany(descriptors); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Services/IActivityPropertyOptionsProvider.cs b/src/core/Elsa.Management/Services/IActivityPropertyOptionsProvider.cs new file mode 100644 index 000000000..0e9b48a68 --- /dev/null +++ b/src/core/Elsa.Management/Services/IActivityPropertyOptionsProvider.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +namespace Elsa.Management.Services; + +public interface IActivityPropertyOptionsProvider +{ + object? GetOptions(PropertyInfo property); +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Services/PropertyDefaultValueResolver.cs b/src/core/Elsa.Management/Services/PropertyDefaultValueResolver.cs new file mode 100644 index 000000000..09c6ec180 --- /dev/null +++ b/src/core/Elsa.Management/Services/PropertyDefaultValueResolver.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using Elsa.Attributes; +using Elsa.Management.Contracts; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Management.Services; + +public class PropertyDefaultValueResolver : IPropertyDefaultValueResolver +{ + private readonly IServiceProvider _serviceProvider; + + public PropertyDefaultValueResolver(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public object? GetDefaultValue(PropertyInfo activityPropertyInfo) + { + var inputAttribute = activityPropertyInfo.GetCustomAttribute(); + + if (inputAttribute == null) + return null; + + if (inputAttribute.DefaultValueProvider == null) + return inputAttribute.DefaultValue; + + var providerType = inputAttribute.DefaultValueProvider; + + using var scope = _serviceProvider.CreateScope(); + var provider = (IActivityPropertyDefaultValueProvider) ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, providerType); + return provider.GetDefaultValue(activityPropertyInfo); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Services/PropertyOptionsResolver.cs b/src/core/Elsa.Management/Services/PropertyOptionsResolver.cs new file mode 100644 index 000000000..bf6187db7 --- /dev/null +++ b/src/core/Elsa.Management/Services/PropertyOptionsResolver.cs @@ -0,0 +1,54 @@ +using System.Reflection; +using Elsa.Attributes; +using Elsa.Management.Contracts; +using Elsa.Management.Extensions; +using Elsa.Management.Models; +using Humanizer; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Management.Services; + +public class PropertyOptionsResolver : IPropertyOptionsResolver +{ + private readonly IServiceProvider _serviceProvider; + + public PropertyOptionsResolver(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public object? GetOptions(PropertyInfo propertyInfo) + { + var inputAttribute = propertyInfo.GetCustomAttribute(); + + if (inputAttribute == null) + return null; + + if (inputAttribute.OptionsProvider == null) + return inputAttribute.Options ?? (TryGetEnumOptions(propertyInfo, out var items) ? items : null); + + var providerType = inputAttribute.OptionsProvider; + + using var scope = _serviceProvider.CreateScope(); + var provider = (IActivityPropertyOptionsProvider) ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, providerType); + return provider.GetOptions(propertyInfo); + } + + private bool TryGetEnumOptions(PropertyInfo activityPropertyInfo, out IList? items) + { + var isNullable = activityPropertyInfo.PropertyType.IsNullableType(); + var propertyType = isNullable ? activityPropertyInfo.PropertyType.GetTypeOfNullable() : activityPropertyInfo.PropertyType; + + items = null; + + if (!propertyType.IsEnum) + return false; + + items = propertyType.GetEnumNames().Select(x => new SelectListItem(x.Humanize(LetterCasing.Title), x)).ToList(); + + if (isNullable) + items.Insert(0, new SelectListItem("-", "")); + + return true; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Services/TriggerDescriber.cs b/src/core/Elsa.Management/Services/TriggerDescriber.cs new file mode 100644 index 000000000..b7c1d8e73 --- /dev/null +++ b/src/core/Elsa.Management/Services/TriggerDescriber.cs @@ -0,0 +1,83 @@ +using System.ComponentModel; +using System.Reflection; +using Elsa.Attributes; +using Elsa.Helpers; +using Elsa.Management.Contracts; +using Elsa.Management.Models; +using Elsa.Models; +using Humanizer; + +namespace Elsa.Management.Services; + +public class TriggerDescriber : ITriggerDescriber +{ + private readonly IPropertyOptionsResolver _optionsResolver; + private readonly IPropertyDefaultValueResolver _defaultValueResolver; + private readonly ITriggerFactory _activityFactory; + + public TriggerDescriber(IPropertyOptionsResolver optionsResolver, IPropertyDefaultValueResolver defaultValueResolver, ITriggerFactory activityFactory) + { + _optionsResolver = optionsResolver; + _defaultValueResolver = defaultValueResolver; + _activityFactory = activityFactory; + } + + public ValueTask DescribeTriggerAsync(Type triggerType, CancellationToken cancellationToken = default) + { + var ns = TypeNameHelper.GenerateNamespace(triggerType); + var typeName = triggerType.Name; + var fullTypeName = TypeNameHelper.GenerateTypeName(triggerType, ns); + var displayNameAttr = triggerType.GetCustomAttribute(); + var displayName = displayNameAttr?.DisplayName ?? typeName.Humanize(LetterCasing.Title); + var categoryAttr = triggerType.GetCustomAttribute(); + var category = categoryAttr?.Category ?? TypeNameHelper.GetCategoryFromNamespace(ns) ?? "Miscellaneous"; + var descriptionAttr = triggerType.GetCustomAttribute(); + var description = descriptionAttr?.Description; + var properties = triggerType.GetProperties(); + var inputProperties = properties.Where(x => typeof(Input).IsAssignableFrom(x.PropertyType)).ToList(); + + var descriptor = new TriggerDescriptor + { + Category = category, + Description = description, + NodeType = fullTypeName, + DisplayName = displayName, + InputProperties = DescribeInputProperties(inputProperties).ToList(), + Constructor = context => + { + var activity = _activityFactory.Create(triggerType, context); + activity.TriggerType = fullTypeName; + return activity; + } + }; + + return ValueTask.FromResult(descriptor); + } + + private IEnumerable DescribeInputProperties(IEnumerable properties) + { + foreach (var propertyInfo in properties) + { + var inputAttribute = propertyInfo.GetCustomAttribute(); + var descriptionAttribute = propertyInfo.GetCustomAttribute(); + var wrappedPropertyType = propertyInfo.PropertyType.GenericTypeArguments[0]; + + yield return new InputDescriptor + ( + inputAttribute?.Name ?? propertyInfo.Name, + wrappedPropertyType, + InputUIHints.GetUIHint(wrappedPropertyType, inputAttribute), + inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title), + descriptionAttribute?.Description, + _optionsResolver.GetOptions(propertyInfo), + inputAttribute?.Category, + inputAttribute?.Order ?? 0, + _defaultValueResolver.GetDefaultValue(propertyInfo), + inputAttribute?.DefaultSyntax, + inputAttribute?.SupportedSyntaxes, + inputAttribute?.IsReadOnly ?? false, + inputAttribute?.IsBrowsable ?? true + ); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Services/TriggerFactory.cs b/src/core/Elsa.Management/Services/TriggerFactory.cs new file mode 100644 index 000000000..d300c87cf --- /dev/null +++ b/src/core/Elsa.Management/Services/TriggerFactory.cs @@ -0,0 +1,15 @@ +using System.Text.Json; +using Elsa.Contracts; +using Elsa.Management.Contracts; +using Elsa.Management.Models; + +namespace Elsa.Management.Services; + +public class TriggerFactory : ITriggerFactory +{ + public ITrigger Create(Type type, TriggerConstructorContext context) + { + var trigger = (ITrigger)context.Element.Deserialize(type, context.SerializerOptions)!; + return trigger; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Services/TriggerRegistry.cs b/src/core/Elsa.Management/Services/TriggerRegistry.cs new file mode 100644 index 000000000..b8c5d96ee --- /dev/null +++ b/src/core/Elsa.Management/Services/TriggerRegistry.cs @@ -0,0 +1,58 @@ +using Elsa.Management.Contracts; +using Elsa.Management.Models; + +namespace Elsa.Management.Services; + +public class TriggerRegistry : ITriggerRegistry +{ + private readonly IDictionary> _providedTriggerDescriptors = new Dictionary>(); + private readonly IDictionary _triggerDescriptors = new Dictionary(); + + public void Add(object provider, TriggerDescriptor descriptor) => Add(descriptor, GetOrCreateDescriptors(provider)); + + public void AddMany(object provider, IEnumerable descriptors) + { + var target = GetOrCreateDescriptors(provider); + + foreach (var descriptor in descriptors) + Add(descriptor, target); + } + + public void Clear() + { + _triggerDescriptors.Clear(); + _providedTriggerDescriptors.Clear(); + } + + public void ClearProvider(object provider) + { + var descriptors = ListByProvider(provider).ToList(); + + foreach (var descriptor in descriptors) + _triggerDescriptors.Remove(descriptor.NodeType); + + _providedTriggerDescriptors.Remove(provider); + } + + public IEnumerable ListAll() => _triggerDescriptors.Values; + public IEnumerable ListByProvider(object provider) => _providedTriggerDescriptors.TryGetValue(provider, out var descriptors) ? descriptors : ArraySegment.Empty; + public TriggerDescriptor? Find(Func predicate) => _triggerDescriptors.Values.FirstOrDefault(predicate); + public TriggerDescriptor? Find(string triggerType) => _triggerDescriptors.TryGetValue(triggerType, out var descriptor) ? descriptor : null; + + private void Add(TriggerDescriptor descriptor, ICollection target) + { + _triggerDescriptors.Add(descriptor.NodeType, descriptor); + target.Add(descriptor); + } + + private ICollection GetOrCreateDescriptors(object provider) + { + if (_providedTriggerDescriptors.TryGetValue(provider, out var descriptors)) + return descriptors; + + descriptors = new List(); + _providedTriggerDescriptors.Add(provider, descriptors); + + return descriptors; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Services/TriggerRegistryPopulator.cs b/src/core/Elsa.Management/Services/TriggerRegistryPopulator.cs new file mode 100644 index 000000000..776e95c7a --- /dev/null +++ b/src/core/Elsa.Management/Services/TriggerRegistryPopulator.cs @@ -0,0 +1,27 @@ +using Elsa.Management.Contracts; + +namespace Elsa.Management.Services; + +/// +/// Populates the with descriptors provided by s. +/// +public class TriggerRegistryPopulator : ITriggerRegistryPopulator +{ + private readonly IEnumerable _providers; + private readonly ITriggerRegistry _registry; + + public TriggerRegistryPopulator(IEnumerable providers, ITriggerRegistry registry) + { + _providers = providers; + _registry = registry; + } + + public async ValueTask PopulateRegistryAsync(CancellationToken cancellationToken) + { + foreach (var provider in _providers) + { + var descriptors = await provider.GetDescriptorsAsync(cancellationToken); + _registry.AddMany(provider, descriptors); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Management/Services/WorkflowPublisher.cs b/src/core/Elsa.Management/Services/WorkflowPublisher.cs new file mode 100644 index 000000000..fdc7e0740 --- /dev/null +++ b/src/core/Elsa.Management/Services/WorkflowPublisher.cs @@ -0,0 +1,169 @@ +using Elsa.Activities.Workflows; +using Elsa.Contracts; +using Elsa.Management.Contracts; +using Elsa.Management.Notifications; +using Elsa.Mediator.Contracts; +using Elsa.Models; +using Elsa.Persistence.Commands; +using Elsa.Persistence.Models; +using Elsa.Persistence.Requests; + +namespace Elsa.Management.Services +{ + public class WorkflowPublisher : IWorkflowPublisher + { + private readonly IMediator _mediator; + private readonly IIdentityGenerator _identityGenerator; + private readonly ISystemClock _systemClock; + + public WorkflowPublisher(IMediator mediator, IIdentityGenerator identityGenerator, ISystemClock systemClock) + { + _mediator = mediator; + _identityGenerator = identityGenerator; + _systemClock = systemClock; + } + + public Workflow New() + { + var id = _identityGenerator.GenerateId(); + var definitionId = _identityGenerator.GenerateId(); + const int version = 1; + + return new Workflow( + new WorkflowIdentity(definitionId, version, id), + WorkflowPublication.LatestDraft, + new WorkflowMetadata(CreatedAt: _systemClock.UtcNow), + new Sequence(), + new List()); + } + + public async Task PublishAsync(string definitionId, CancellationToken cancellationToken = default) + { + var workflow = await _mediator.RequestAsync(new FindWorkflow(definitionId, VersionOptions.Latest), cancellationToken); + + if (workflow == null) + return null; + + return await PublishAsync(workflow, cancellationToken); + } + + public async Task PublishAsync(Workflow workflow, CancellationToken cancellationToken = default) + { + var identity = workflow.Identity; + var definitionId = identity.DefinitionId; + + // Reset current latest and published definitions. + var publishedAndOrLatestWorkflows = await _mediator.RequestAsync(new FindLatestAndPublishedWorkflows(definitionId), cancellationToken); + + foreach (var publishedAndOrLatestWorkflow in publishedAndOrLatestWorkflows) + { + var updatedDraft = publishedAndOrLatestWorkflow with + { + Publication = WorkflowPublication.Draft + }; + + await _mediator.ExecuteAsync(new SaveWorkflow(updatedDraft), cancellationToken); + } + + workflow = workflow.Publication.IsPublished ? workflow.IncrementVersion() : workflow.WithPublished(); + + workflow.WithLatest(); + workflow = Initialize(workflow); + + await _mediator.PublishAsync(new WorkflowPublishing(workflow), cancellationToken); + await _mediator.ExecuteAsync(new SaveWorkflow(workflow), cancellationToken); + await _mediator.PublishAsync(new WorkflowPublished(workflow), cancellationToken); + return workflow; + } + + public async Task RetractAsync(string definitionId, CancellationToken cancellationToken = default) + { + var workflow = await _mediator.RequestAsync(new FindWorkflow(definitionId, VersionOptions.Published), cancellationToken); + + if (workflow == null) + return null; + + return await RetractAsync(workflow, cancellationToken); + } + + public async Task RetractAsync(Workflow workflow, CancellationToken cancellationToken = default) + { + if (!workflow.Publication.IsPublished) + throw new InvalidOperationException("Cannot unpublish an unpublished workflow definition."); + + workflow.WithPublished(false); + workflow = Initialize(workflow); + + await _mediator.PublishAsync(new WorkflowRetracting(workflow), cancellationToken); + await _mediator.ExecuteAsync(new SaveWorkflow(workflow), cancellationToken); + await _mediator.PublishAsync(new WorkflowRetracted(workflow), cancellationToken); + return workflow; + } + + public async Task GetDraftAsync(string definitionId, CancellationToken cancellationToken = default) + { + var workflow = await _mediator.RequestAsync(new FindWorkflow(definitionId, VersionOptions.Latest), cancellationToken); + + if (workflow == null) + return null; + + if (!workflow.Publication.IsPublished) + return workflow; + + var draft = workflow with + { + Identity = new WorkflowIdentity( + workflow.Identity.DefinitionId, + workflow.Identity.Version + 1, + _identityGenerator.GenerateId()), + Publication = WorkflowPublication.LatestDraft + }; + + return draft; + } + + public async Task SaveDraftAsync(Workflow workflow, CancellationToken cancellationToken = default) + { + var draft = workflow; + var definitionId = workflow.Identity.DefinitionId; + var latestVersion = await _mediator.RequestAsync(new FindWorkflow(definitionId, VersionOptions.Latest), cancellationToken); + + if (latestVersion?.Publication is { IsPublished: true, IsLatest: true }) + { + latestVersion = latestVersion.WithLatest(false); + await _mediator.ExecuteAsync(new SaveWorkflow(latestVersion), cancellationToken); + } + + draft = draft with + { + Publication = WorkflowPublication.LatestDraft + }; + draft = Initialize(draft); + + await _mediator.ExecuteAsync(new SaveWorkflow(draft), cancellationToken); + return draft; + } + + public async Task DeleteAsync(string definitionId, CancellationToken cancellationToken = default) + { + await _mediator.ExecuteAsync(new DeleteWorkflowInstances(definitionId), cancellationToken); + await _mediator.ExecuteAsync(new DeleteWorkflowDefinition(definitionId), cancellationToken); + } + + public Task DeleteAsync(Workflow workflow, CancellationToken cancellationToken = default) => DeleteAsync(workflow.Identity.DefinitionId, cancellationToken); + + private Workflow Initialize(Workflow workflow) + { + if (workflow.Identity.Id == null!) + workflow = workflow.WithId(_identityGenerator.GenerateId()); + + if (workflow.Identity.DefinitionId == null!) + workflow = workflow.WithDefinitionId(_identityGenerator.GenerateId()); + + if (workflow.Identity.Version == 0) + workflow.WithVersion(1); + + return workflow; + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Contracts/ICommand.cs b/src/core/Elsa.Mediator/Contracts/ICommand.cs new file mode 100644 index 000000000..9262855d9 --- /dev/null +++ b/src/core/Elsa.Mediator/Contracts/ICommand.cs @@ -0,0 +1,9 @@ +namespace Elsa.Mediator.Contracts; + +public interface ICommand +{ +} + +public interface ICommand : ICommand +{ +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Contracts/ICommandHandler.cs b/src/core/Elsa.Mediator/Contracts/ICommandHandler.cs new file mode 100644 index 000000000..7b3ae0c0f --- /dev/null +++ b/src/core/Elsa.Mediator/Contracts/ICommandHandler.cs @@ -0,0 +1,14 @@ +namespace Elsa.Mediator.Contracts; + +public interface ICommandHandler +{ +} + +public interface ICommandHandler : ICommandHandler where TCommand : ICommand +{ + Task HandleAsync(TCommand command, CancellationToken cancellationToken); +} + +public interface ICommandHandler : ICommandHandler where TCommand : ICommand +{ +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Contracts/ICommandSender.cs b/src/core/Elsa.Mediator/Contracts/ICommandSender.cs new file mode 100644 index 000000000..e21499b56 --- /dev/null +++ b/src/core/Elsa.Mediator/Contracts/ICommandSender.cs @@ -0,0 +1,7 @@ +namespace Elsa.Mediator.Contracts; + +public interface ICommandSender +{ + Task ExecuteAsync(ICommand command, CancellationToken cancellationToken = default); + Task ExecuteAsync(ICommand command, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Contracts/IMediator.cs b/src/core/Elsa.Mediator/Contracts/IMediator.cs new file mode 100644 index 000000000..be568f1ba --- /dev/null +++ b/src/core/Elsa.Mediator/Contracts/IMediator.cs @@ -0,0 +1,5 @@ +namespace Elsa.Mediator.Contracts; + +public interface IMediator : IRequestSender, ICommandSender, IPublisher +{ +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Contracts/INotification.cs b/src/core/Elsa.Mediator/Contracts/INotification.cs new file mode 100644 index 000000000..77ec73f0f --- /dev/null +++ b/src/core/Elsa.Mediator/Contracts/INotification.cs @@ -0,0 +1,6 @@ +namespace Elsa.Mediator.Contracts; + +public interface INotification +{ + +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Contracts/INotificationHandler.cs b/src/core/Elsa.Mediator/Contracts/INotificationHandler.cs new file mode 100644 index 000000000..6d3e1184c --- /dev/null +++ b/src/core/Elsa.Mediator/Contracts/INotificationHandler.cs @@ -0,0 +1,10 @@ +namespace Elsa.Mediator.Contracts; + +public interface INotificationHandler +{ +} + +public interface INotificationHandler : INotificationHandler where T : INotification +{ + Task HandleAsync(T notification, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Contracts/IPublisher.cs b/src/core/Elsa.Mediator/Contracts/IPublisher.cs new file mode 100644 index 000000000..7325370cb --- /dev/null +++ b/src/core/Elsa.Mediator/Contracts/IPublisher.cs @@ -0,0 +1,6 @@ +namespace Elsa.Mediator.Contracts; + +public interface IPublisher +{ + Task PublishAsync(INotification notification, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Contracts/IRequest.cs b/src/core/Elsa.Mediator/Contracts/IRequest.cs new file mode 100644 index 000000000..0ccc7e6a4 --- /dev/null +++ b/src/core/Elsa.Mediator/Contracts/IRequest.cs @@ -0,0 +1,9 @@ +namespace Elsa.Mediator.Contracts; + +public interface IRequest +{ +} + +public interface IRequest : IRequest +{ +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Contracts/IRequestHandler.cs b/src/core/Elsa.Mediator/Contracts/IRequestHandler.cs new file mode 100644 index 000000000..4ffaeaa94 --- /dev/null +++ b/src/core/Elsa.Mediator/Contracts/IRequestHandler.cs @@ -0,0 +1,10 @@ +namespace Elsa.Mediator.Contracts; + +public interface IRequestHandler +{ +} + +public interface IRequestHandler : IRequestHandler where TRequest : IRequest? +{ + Task HandleAsync(TRequest request, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Contracts/IRequestSender.cs b/src/core/Elsa.Mediator/Contracts/IRequestSender.cs new file mode 100644 index 000000000..2f5bdcab6 --- /dev/null +++ b/src/core/Elsa.Mediator/Contracts/IRequestSender.cs @@ -0,0 +1,6 @@ +namespace Elsa.Mediator.Contracts; + +public interface IRequestSender +{ + Task RequestAsync(IRequest request, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Contracts/Unit.cs b/src/core/Elsa.Mediator/Contracts/Unit.cs new file mode 100644 index 000000000..bd1711c59 --- /dev/null +++ b/src/core/Elsa.Mediator/Contracts/Unit.cs @@ -0,0 +1,6 @@ +namespace Elsa.Mediator.Contracts; + +public record Unit +{ + public static readonly Unit Instance = new (); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Elsa.Mediator.csproj b/src/core/Elsa.Mediator/Elsa.Mediator.csproj new file mode 100644 index 000000000..168c16fd2 --- /dev/null +++ b/src/core/Elsa.Mediator/Elsa.Mediator.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/src/core/Elsa.Mediator/Extensions/ServiceCollectionExtensions.cs b/src/core/Elsa.Mediator/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..0798be284 --- /dev/null +++ b/src/core/Elsa.Mediator/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,84 @@ +using System.Reflection; +using Elsa.Mediator.Contracts; +using Elsa.Mediator.Middleware.Command; +using Elsa.Mediator.Middleware.Command.Contracts; +using Elsa.Mediator.Middleware.Notification; +using Elsa.Mediator.Middleware.Notification.Contracts; +using Elsa.Mediator.Middleware.Request; +using Elsa.Mediator.Middleware.Request.Contracts; +using Elsa.Mediator.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Mediator.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddMediator(this IServiceCollection services) + { + return services + .AddSingleton() + .AddSingleton(sp => sp.GetRequiredService()) + .AddSingleton(sp => sp.GetRequiredService()) + .AddSingleton(sp => sp.GetRequiredService()) + .AddSingleton() + .AddSingleton() + .AddSingleton(); + } + + public static IServiceCollection AddCommandHandler(this IServiceCollection services) + where THandler : class, ICommandHandler + where TCommand : ICommand => + services.AddCommandHandler(); + + public static IServiceCollection AddCommandHandler(this IServiceCollection services) + where THandler : class, ICommandHandler + where TCommand : ICommand + { + return services.AddSingleton(); + } + + public static IServiceCollection AddRequestHandler(this IServiceCollection services) + where THandler : class, IRequestHandler + where TRequest : IRequest? + { + return services.AddSingleton(); + } + + public static IServiceCollection AddHandlersFrom(this IServiceCollection services) => services.AddHandlersFrom(typeof(TMarker)); + public static IServiceCollection AddHandlersFrom(this IServiceCollection services, Type markerType) => services.AddHandlersFrom(markerType.Assembly); + + public static IServiceCollection AddHandlersFrom(this IServiceCollection services, Assembly assembly) + { + return services + .AddNotificationHandlersFrom(assembly) + .AddRequestHandlersFrom(assembly) + .AddCommandHandlersFrom(assembly); + } + + public static IServiceCollection AddNotificationHandlersFrom(this IServiceCollection services) => services.AddHandlersFromInternal(typeof(TMarker)); + public static IServiceCollection AddNotificationHandlersFrom(this IServiceCollection services, Type markerType) => services.AddHandlersFromInternal(markerType); + public static IServiceCollection AddNotificationHandlersFrom(this IServiceCollection services, Assembly assembly) => services.AddHandlersFromInternal(assembly); + + public static IServiceCollection AddRequestHandlersFrom(this IServiceCollection services) => services.AddHandlersFromInternal(typeof(TMarker)); + public static IServiceCollection AddRequestHandlersFrom(this IServiceCollection services, Type markerType) => services.AddHandlersFromInternal(markerType); + public static IServiceCollection AddRequestHandlersFrom(this IServiceCollection services, Assembly assembly) => services.AddHandlersFromInternal(assembly); + + public static IServiceCollection AddCommandHandlersFrom(this IServiceCollection services) => services.AddHandlersFromInternal(typeof(TMarker)); + public static IServiceCollection AddCommandHandlersFrom(this IServiceCollection services, Type markerType) => services.AddHandlersFromInternal(markerType); + public static IServiceCollection AddCommandHandlersFrom(this IServiceCollection services, Assembly assembly) => services.AddHandlersFromInternal(assembly); + + + private static IServiceCollection AddHandlersFromInternal(this IServiceCollection services) => services.AddHandlersFromInternal(typeof(TMarker)); + private static IServiceCollection AddHandlersFromInternal(this IServiceCollection services, Type assemblyMarkerType) => services.AddHandlersFromInternal(assemblyMarkerType.Assembly); + + private static IServiceCollection AddHandlersFromInternal(this IServiceCollection services, Assembly assembly) + { + var serviceType = typeof(TService); + var types = assembly.ExportedTypes.Where(x => serviceType.IsAssignableFrom(x)); + + foreach (var type in types) + services.AddSingleton(serviceType, type); + + return services; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Command/CommandContext.cs b/src/core/Elsa.Mediator/Middleware/Command/CommandContext.cs new file mode 100644 index 000000000..d43ba8edf --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Command/CommandContext.cs @@ -0,0 +1,18 @@ +using Elsa.Mediator.Contracts; + +namespace Elsa.Mediator.Middleware.Command; + +public class CommandContext +{ + public CommandContext(ICommand command, Type resultType, CancellationToken cancellationToken) + { + Command = command; + ResultType = resultType; + CancellationToken = cancellationToken; + } + + public ICommand Command { get; init; } + public Type ResultType { get; init; } + public CancellationToken CancellationToken { get; init; } + public object? Result { get; set; } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Command/CommandMiddlewareDelegate.cs b/src/core/Elsa.Mediator/Middleware/Command/CommandMiddlewareDelegate.cs new file mode 100644 index 000000000..77745fed3 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Command/CommandMiddlewareDelegate.cs @@ -0,0 +1,3 @@ +namespace Elsa.Mediator.Middleware.Command; + +public delegate ValueTask CommandMiddlewareDelegate(CommandContext context); \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Command/CommandPipeline.cs b/src/core/Elsa.Mediator/Middleware/Command/CommandPipeline.cs new file mode 100644 index 000000000..cf1423786 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Command/CommandPipeline.cs @@ -0,0 +1,24 @@ +using Elsa.Mediator.Middleware.Command.Contracts; + +namespace Elsa.Mediator.Middleware.Command; + +public class CommandPipeline : ICommandPipeline +{ + private readonly IServiceProvider _serviceProvider; + private CommandMiddlewareDelegate? _pipeline; + + public CommandPipeline(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + public CommandMiddlewareDelegate Pipeline => _pipeline ??= CreateDefaultPipeline(); + + public CommandMiddlewareDelegate Setup(Action? setup = default) + { + var builder = new CommandPipelineBuilder(_serviceProvider); + setup?.Invoke(builder); + _pipeline = builder.Build(); + return _pipeline; + } + + public async Task ExecuteAsync(CommandContext context) => await Pipeline(context); + + private CommandMiddlewareDelegate CreateDefaultPipeline() => Setup(x => x.UseCommandHandlers()); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Command/CommandPipelineBuilder.cs b/src/core/Elsa.Mediator/Middleware/Command/CommandPipelineBuilder.cs new file mode 100644 index 000000000..d29c5ede3 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Command/CommandPipelineBuilder.cs @@ -0,0 +1,41 @@ +using Elsa.Mediator.Middleware.Command.Contracts; + +namespace Elsa.Mediator.Middleware.Command; + +public class CommandPipelineBuilder : ICommandPipelineBuilder +{ + private const string ServicesKey = "mediator.Services"; + private readonly IList> _components = new List>(); + + public CommandPipelineBuilder(IServiceProvider serviceProvider) + { + ApplicationServices = serviceProvider; + } + + public IDictionary Properties { get; } = new Dictionary(); + + public IServiceProvider ApplicationServices + { + get => GetProperty(ServicesKey)!; + set => SetProperty(ServicesKey, value); + } + + public ICommandPipelineBuilder Use(Func middleware) + { + _components.Add(middleware); + return this; + } + + public CommandMiddlewareDelegate Build() + { + CommandMiddlewareDelegate pipeline = _ => new ValueTask(); + + foreach (var component in _components.Reverse()) + pipeline = component(pipeline); + + return pipeline; + } + + private T? GetProperty(string key) => Properties.TryGetValue(key, out var value) ? (T?)value : default(T); + private void SetProperty(string key, T value) => Properties[key] = value; +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Command/CommandPipelineBuilderExtensions.cs b/src/core/Elsa.Mediator/Middleware/Command/CommandPipelineBuilderExtensions.cs new file mode 100644 index 000000000..23908a18b --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Command/CommandPipelineBuilderExtensions.cs @@ -0,0 +1,10 @@ +using Elsa.Mediator.Middleware.Command.Contracts; +using Elsa.Mediator.Middleware.Command.Components; + +namespace Elsa.Mediator.Middleware.Command; + +public static class CommandPipelineBuilderExtensions +{ + public static ICommandPipelineBuilder UseCommandHandlers(this ICommandPipelineBuilder builder) => builder.UseMiddleware(); + public static ICommandPipelineBuilder UseCommandLogging(this ICommandPipelineBuilder builder) => builder.UseMiddleware(); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Command/Components/CommandHandlerInvokerMiddleware.cs b/src/core/Elsa.Mediator/Middleware/Command/Components/CommandHandlerInvokerMiddleware.cs new file mode 100644 index 000000000..540ef6761 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Command/Components/CommandHandlerInvokerMiddleware.cs @@ -0,0 +1,46 @@ +using Elsa.Mediator.Contracts; +using Elsa.Mediator.Middleware.Command.Contracts; + +namespace Elsa.Mediator.Middleware.Command.Components; + +public class CommandHandlerInvokerMiddleware : ICommandMiddleware +{ + private readonly CommandMiddlewareDelegate _next; + private readonly IEnumerable _commandHandlers; + + public CommandHandlerInvokerMiddleware(CommandMiddlewareDelegate next, IEnumerable commandHandlers) + { + _next = next; + _commandHandlers = commandHandlers; + } + + public async ValueTask InvokeAsync(CommandContext context) + { + // Find all handlers for the specified command. + var command = context.Command; + var commandType = command.GetType(); + var resultType = context.ResultType; + var handlerType = typeof(ICommandHandler<,>).MakeGenericType(commandType, resultType); + var handlers = _commandHandlers.Where(x => handlerType.IsInstanceOfType(x)).ToArray(); + + if (!handlers.Any()) + throw new InvalidOperationException($"There is no handler to handle the {commandType.FullName} command"); + + if (handlers.Length > 1) + throw new InvalidOperationException($"Multiple handlers were found to handle the {commandType.FullName} command"); + + var handler = handlers.First(); + var handleMethod = handlerType.GetMethod("HandleAsync")!; + var cancellationToken = context.CancellationToken; + var task = (Task)handleMethod.Invoke(handler, new object?[] { command, cancellationToken })!; + await task; + + // Get result of task. + var taskWithReturnType = typeof(Task<>).MakeGenericType(resultType); + var resultProperty = taskWithReturnType.GetProperty(nameof(Task.Result))!; + context.Result = resultProperty.GetValue(task); + + // Invoke next middleware. + await _next(context); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Command/Components/CommandLoggingMiddleware.cs b/src/core/Elsa.Mediator/Middleware/Command/Components/CommandLoggingMiddleware.cs new file mode 100644 index 000000000..7158403cf --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Command/Components/CommandLoggingMiddleware.cs @@ -0,0 +1,15 @@ +using Elsa.Mediator.Middleware.Command.Contracts; + +namespace Elsa.Mediator.Middleware.Command.Components; + +public class CommandLoggingMiddleware : ICommandMiddleware +{ + private readonly CommandMiddlewareDelegate _next; + public CommandLoggingMiddleware(CommandMiddlewareDelegate next) => _next = next; + + public async ValueTask InvokeAsync(CommandContext context) + { + // Invoke next middleware. + await _next(context); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Command/Contracts/ICommandMiddleware.cs b/src/core/Elsa.Mediator/Middleware/Command/Contracts/ICommandMiddleware.cs new file mode 100644 index 000000000..9fbaac397 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Command/Contracts/ICommandMiddleware.cs @@ -0,0 +1,6 @@ +namespace Elsa.Mediator.Middleware.Command.Contracts; + +public interface ICommandMiddleware +{ + ValueTask InvokeAsync(CommandContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Command/Contracts/ICommandPipeline.cs b/src/core/Elsa.Mediator/Middleware/Command/Contracts/ICommandPipeline.cs new file mode 100644 index 000000000..c8936065e --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Command/Contracts/ICommandPipeline.cs @@ -0,0 +1,8 @@ +namespace Elsa.Mediator.Middleware.Command.Contracts; + +public interface ICommandPipeline +{ + CommandMiddlewareDelegate Setup(Action setup); + CommandMiddlewareDelegate Pipeline { get; } + Task ExecuteAsync(CommandContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Command/Contracts/ICommandPipelineBuilder.cs b/src/core/Elsa.Mediator/Middleware/Command/Contracts/ICommandPipelineBuilder.cs new file mode 100644 index 000000000..ffe60193f --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Command/Contracts/ICommandPipelineBuilder.cs @@ -0,0 +1,9 @@ +namespace Elsa.Mediator.Middleware.Command.Contracts; + +public interface ICommandPipelineBuilder +{ + public IDictionary Properties { get; } + IServiceProvider ApplicationServices { get; } + ICommandPipelineBuilder Use(Func middleware); + public CommandMiddlewareDelegate Build(); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Command/MiddlewareExtensions.cs b/src/core/Elsa.Mediator/Middleware/Command/MiddlewareExtensions.cs new file mode 100644 index 000000000..71ab1f2b8 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Command/MiddlewareExtensions.cs @@ -0,0 +1,20 @@ +using Elsa.Mediator.Middleware.Command.Contracts; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Mediator.Middleware.Command; + +public static class MiddlewareExtensions +{ + public static ICommandPipelineBuilder UseMiddleware(this ICommandPipelineBuilder builder, params object[] args) where TMiddleware: ICommandMiddleware + { + var middleware = typeof(TMiddleware); + + return builder.Use(next => + { + var invokeMethod = MiddlewareHelpers.GetInvokeMethod(middleware); + var ctorParams = new[] { next }.Concat(args).Select(x => x!).ToArray(); + var instance = ActivatorUtilities.CreateInstance(builder.ApplicationServices, middleware, ctorParams); + return (CommandMiddlewareDelegate)invokeMethod.CreateDelegate(typeof(CommandMiddlewareDelegate), instance); + }); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/MiddlewareHelpers.cs b/src/core/Elsa.Mediator/Middleware/MiddlewareHelpers.cs new file mode 100644 index 000000000..621a7a5a3 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/MiddlewareHelpers.cs @@ -0,0 +1,29 @@ +using System.Reflection; + +namespace Elsa.Mediator.Middleware; + +public static class MiddlewareHelpers +{ + public static MethodInfo GetInvokeMethod(Type middleware) + { + const string invokeMethodName = "Invoke"; + const string invokeAsyncMethodName = "InvokeAsync"; + var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public); + var invokeMethods = methods.Where(m => string.Equals(m.Name, invokeMethodName, StringComparison.Ordinal) || string.Equals(m.Name, invokeAsyncMethodName, StringComparison.Ordinal)).ToArray(); + + switch (invokeMethods.Length) + { + case > 1: + throw new InvalidOperationException("Multiple Invoke methods were found. Use either Invoke or InvokeAsync."); + case 0: + throw new InvalidOperationException("No Invoke methods were found. Use either Invoke or InvokeAsync"); + } + + var methodInfo = invokeMethods[0]; + + if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType) && !typeof(ValueTask).IsAssignableFrom(methodInfo.ReturnType)) + throw new InvalidOperationException($"The {methodInfo.Name} method must return Task or ValueTask"); + + return methodInfo; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Notification/Components/NotificationHandlerInvokerMiddleware.cs b/src/core/Elsa.Mediator/Middleware/Notification/Components/NotificationHandlerInvokerMiddleware.cs new file mode 100644 index 000000000..fc7a8a0a1 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Notification/Components/NotificationHandlerInvokerMiddleware.cs @@ -0,0 +1,36 @@ +using Elsa.Mediator.Contracts; +using Elsa.Mediator.Middleware.Notification.Contracts; + +namespace Elsa.Mediator.Middleware.Notification.Components; + +public class NotificationHandlerInvokerMiddleware : INotificationMiddleware +{ + private readonly NotificationMiddlewareDelegate _next; + private readonly IEnumerable _notificationHandlers; + + public NotificationHandlerInvokerMiddleware(NotificationMiddlewareDelegate next, IEnumerable notificationHandlers) + { + _next = next; + _notificationHandlers = notificationHandlers; + } + + public async ValueTask InvokeAsync(NotificationContext context) + { + // Find all handlers for the specified notification. + var notification = context.Notification; + var notificationType = notification.GetType(); + var handlerType = typeof(INotificationHandler<>).MakeGenericType(notificationType); + var handlers = _notificationHandlers.Where(x => handlerType.IsInstanceOfType(x)).ToArray(); + var handleMethod = handlerType.GetMethod("HandleAsync")!; + var cancellationToken = context.CancellationToken; + + foreach (var handler in handlers) + { + var task = (Task)handleMethod.Invoke(handler, new object?[] { notification, cancellationToken })!; + await task; + } + + // Invoke next middleware. + await _next(context); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Notification/Components/NotificationLoggingMiddleware.cs b/src/core/Elsa.Mediator/Middleware/Notification/Components/NotificationLoggingMiddleware.cs new file mode 100644 index 000000000..d035b1f89 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Notification/Components/NotificationLoggingMiddleware.cs @@ -0,0 +1,15 @@ +using Elsa.Mediator.Middleware.Notification.Contracts; + +namespace Elsa.Mediator.Middleware.Notification.Components; + +public class NotificationLoggingMiddleware : INotificationMiddleware +{ + private readonly NotificationMiddlewareDelegate _next; + public NotificationLoggingMiddleware(NotificationMiddlewareDelegate next) => _next = next; + + public async ValueTask InvokeAsync(NotificationContext context) + { + // Invoke next middleware. + await _next(context); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Notification/Contracts/INotificationMiddleware.cs b/src/core/Elsa.Mediator/Middleware/Notification/Contracts/INotificationMiddleware.cs new file mode 100644 index 000000000..31da7451f --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Notification/Contracts/INotificationMiddleware.cs @@ -0,0 +1,6 @@ +namespace Elsa.Mediator.Middleware.Notification.Contracts; + +public interface INotificationMiddleware +{ + ValueTask InvokeAsync(NotificationContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Notification/Contracts/INotificationPipeline.cs b/src/core/Elsa.Mediator/Middleware/Notification/Contracts/INotificationPipeline.cs new file mode 100644 index 000000000..630b30996 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Notification/Contracts/INotificationPipeline.cs @@ -0,0 +1,8 @@ +namespace Elsa.Mediator.Middleware.Notification.Contracts; + +public interface INotificationPipeline +{ + NotificationMiddlewareDelegate Setup(Action setup); + NotificationMiddlewareDelegate Pipeline { get; } + Task ExecuteAsync(NotificationContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Notification/Contracts/INotificationPipelineBuilder.cs b/src/core/Elsa.Mediator/Middleware/Notification/Contracts/INotificationPipelineBuilder.cs new file mode 100644 index 000000000..64971aacf --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Notification/Contracts/INotificationPipelineBuilder.cs @@ -0,0 +1,9 @@ +namespace Elsa.Mediator.Middleware.Notification.Contracts; + +public interface INotificationPipelineBuilder +{ + public IDictionary Properties { get; } + IServiceProvider ApplicationServices { get; } + INotificationPipelineBuilder Use(Func middleware); + public NotificationMiddlewareDelegate Build(); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Notification/MiddlewareExtensions.cs b/src/core/Elsa.Mediator/Middleware/Notification/MiddlewareExtensions.cs new file mode 100644 index 000000000..497904844 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Notification/MiddlewareExtensions.cs @@ -0,0 +1,20 @@ +using Elsa.Mediator.Middleware.Notification.Contracts; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Mediator.Middleware.Notification; + +public static class MiddlewareExtensions +{ + public static INotificationPipelineBuilder UseMiddleware(this INotificationPipelineBuilder builder, params object[] args) where TMiddleware: INotificationMiddleware + { + var middleware = typeof(TMiddleware); + + return builder.Use(next => + { + var invokeMethod = MiddlewareHelpers.GetInvokeMethod(middleware); + var ctorParams = new[] { next }.Concat(args).Select(x => x!).ToArray(); + var instance = ActivatorUtilities.CreateInstance(builder.ApplicationServices, middleware, ctorParams); + return (NotificationMiddlewareDelegate)invokeMethod.CreateDelegate(typeof(NotificationMiddlewareDelegate), instance); + }); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Notification/NotificationContext.cs b/src/core/Elsa.Mediator/Middleware/Notification/NotificationContext.cs new file mode 100644 index 000000000..229115fc6 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Notification/NotificationContext.cs @@ -0,0 +1,15 @@ +using Elsa.Mediator.Contracts; + +namespace Elsa.Mediator.Middleware.Notification; + +public class NotificationContext +{ + public NotificationContext(INotification notification, CancellationToken cancellationToken) + { + Notification = notification; + CancellationToken = cancellationToken; + } + + public INotification Notification { get; init; } + public CancellationToken CancellationToken { get; init; } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Notification/NotificationMiddlewareDelegate.cs b/src/core/Elsa.Mediator/Middleware/Notification/NotificationMiddlewareDelegate.cs new file mode 100644 index 000000000..f9503c212 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Notification/NotificationMiddlewareDelegate.cs @@ -0,0 +1,3 @@ +namespace Elsa.Mediator.Middleware.Notification; + +public delegate ValueTask NotificationMiddlewareDelegate(NotificationContext context); \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Notification/NotificationPipeline.cs b/src/core/Elsa.Mediator/Middleware/Notification/NotificationPipeline.cs new file mode 100644 index 000000000..483ef73bc --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Notification/NotificationPipeline.cs @@ -0,0 +1,24 @@ +using Elsa.Mediator.Middleware.Notification.Contracts; + +namespace Elsa.Mediator.Middleware.Notification; + +public class NotificationPipeline : INotificationPipeline +{ + private readonly IServiceProvider _serviceProvider; + private NotificationMiddlewareDelegate? _pipeline; + + public NotificationPipeline(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + public NotificationMiddlewareDelegate Pipeline => _pipeline ??= CreateDefaultPipeline(); + + public NotificationMiddlewareDelegate Setup(Action? setup = default) + { + var builder = new NotificationPipelineBuilder(_serviceProvider); + setup?.Invoke(builder); + _pipeline = builder.Build(); + return _pipeline; + } + + public async Task ExecuteAsync(NotificationContext context) => await Pipeline(context); + + private NotificationMiddlewareDelegate CreateDefaultPipeline() => Setup(x => x.UseNotificationHandlers()); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Notification/NotificationPipelineBuilder.cs b/src/core/Elsa.Mediator/Middleware/Notification/NotificationPipelineBuilder.cs new file mode 100644 index 000000000..3fdaf810e --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Notification/NotificationPipelineBuilder.cs @@ -0,0 +1,41 @@ +using Elsa.Mediator.Middleware.Notification.Contracts; + +namespace Elsa.Mediator.Middleware.Notification; + +public class NotificationPipelineBuilder : INotificationPipelineBuilder +{ + private const string ServicesKey = "mediator.Services"; + private readonly IList> _components = new List>(); + + public NotificationPipelineBuilder(IServiceProvider serviceProvider) + { + ApplicationServices = serviceProvider; + } + + public IDictionary Properties { get; } = new Dictionary(); + + public IServiceProvider ApplicationServices + { + get => GetProperty(ServicesKey)!; + set => SetProperty(ServicesKey, value); + } + + public INotificationPipelineBuilder Use(Func middleware) + { + _components.Add(middleware); + return this; + } + + public NotificationMiddlewareDelegate Build() + { + NotificationMiddlewareDelegate pipeline = _ => new ValueTask(); + + foreach (var component in _components.Reverse()) + pipeline = component(pipeline); + + return pipeline; + } + + private T? GetProperty(string key) => Properties.TryGetValue(key, out var value) ? (T?)value : default(T); + private void SetProperty(string key, T value) => Properties[key] = value; +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Notification/NotificationPipelineBuilderExtensions.cs b/src/core/Elsa.Mediator/Middleware/Notification/NotificationPipelineBuilderExtensions.cs new file mode 100644 index 000000000..8d4fa5bc6 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Notification/NotificationPipelineBuilderExtensions.cs @@ -0,0 +1,10 @@ +using Elsa.Mediator.Middleware.Notification.Components; +using Elsa.Mediator.Middleware.Notification.Contracts; + +namespace Elsa.Mediator.Middleware.Notification; + +public static class NotificationPipelineBuilderExtensions +{ + public static INotificationPipelineBuilder UseNotificationHandlers(this INotificationPipelineBuilder builder) => builder.UseMiddleware(); + public static INotificationPipelineBuilder UseNotificationLogging(this INotificationPipelineBuilder builder) => builder.UseMiddleware(); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Request/Components/RequestHandlerInvokerMiddleware.cs b/src/core/Elsa.Mediator/Middleware/Request/Components/RequestHandlerInvokerMiddleware.cs new file mode 100644 index 000000000..ac3bd8dd1 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Request/Components/RequestHandlerInvokerMiddleware.cs @@ -0,0 +1,46 @@ +using Elsa.Mediator.Contracts; +using Elsa.Mediator.Middleware.Request.Contracts; + +namespace Elsa.Mediator.Middleware.Request.Components; + +public class RequestHandlerInvokerMiddleware : IRequestMiddleware +{ + private readonly RequestMiddlewareDelegate _next; + private readonly IEnumerable _requestHandlers; + + public RequestHandlerInvokerMiddleware(RequestMiddlewareDelegate next, IEnumerable requestHandlers) + { + _next = next; + _requestHandlers = requestHandlers; + } + + public async ValueTask InvokeAsync(RequestContext context) + { + // Find all handlers for the specified request. + var request = context.Request; + var requestType = request.GetType(); + var responseType = context.ResponseType; + var handlerType = typeof(IRequestHandler<,>).MakeGenericType(requestType, responseType); + var handlers = _requestHandlers.Where(x => handlerType.IsInstanceOfType(x)).ToArray(); + + if (!handlers.Any()) + throw new InvalidOperationException($"There is no handler to handle the {requestType.FullName} request"); + + if (handlers.Length > 1) + throw new InvalidOperationException($"Multiple handlers were found to handle the {requestType.FullName} request"); + + var handler = handlers.First(); + var handleMethod = handlerType.GetMethod("HandleAsync")!; + var cancellationToken = context.CancellationToken; + var task = (Task)handleMethod.Invoke(handler, new object?[] { request, cancellationToken })!; + await task; + + // Get result of task. + var taskWithReturnType = typeof(Task<>).MakeGenericType(responseType); + var resultProperty = taskWithReturnType.GetProperty(nameof(Task.Result))!; + context.Response = resultProperty.GetValue(task); + + // Invoke next middleware. + await _next(context); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Request/Components/RequestLoggingMiddleware.cs b/src/core/Elsa.Mediator/Middleware/Request/Components/RequestLoggingMiddleware.cs new file mode 100644 index 000000000..174c1980b --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Request/Components/RequestLoggingMiddleware.cs @@ -0,0 +1,15 @@ +using Elsa.Mediator.Middleware.Request.Contracts; + +namespace Elsa.Mediator.Middleware.Request.Components; + +public class RequestLoggingMiddleware : IRequestMiddleware +{ + private readonly RequestMiddlewareDelegate _next; + public RequestLoggingMiddleware(RequestMiddlewareDelegate next) => _next = next; + + public async ValueTask InvokeAsync(RequestContext context) + { + // Invoke next middleware. + await _next(context); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Request/Contracts/IRequestMiddleware.cs b/src/core/Elsa.Mediator/Middleware/Request/Contracts/IRequestMiddleware.cs new file mode 100644 index 000000000..e3edfb7fe --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Request/Contracts/IRequestMiddleware.cs @@ -0,0 +1,6 @@ +namespace Elsa.Mediator.Middleware.Request.Contracts; + +public interface IRequestMiddleware +{ + ValueTask InvokeAsync(RequestContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Request/Contracts/IRequestPipeline.cs b/src/core/Elsa.Mediator/Middleware/Request/Contracts/IRequestPipeline.cs new file mode 100644 index 000000000..d3cf252a3 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Request/Contracts/IRequestPipeline.cs @@ -0,0 +1,8 @@ +namespace Elsa.Mediator.Middleware.Request.Contracts; + +public interface IRequestPipeline +{ + RequestMiddlewareDelegate Setup(Action setup); + RequestMiddlewareDelegate Pipeline { get; } + Task ExecuteAsync(RequestContext context); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Request/Contracts/IRequestPipelineBuilder.cs b/src/core/Elsa.Mediator/Middleware/Request/Contracts/IRequestPipelineBuilder.cs new file mode 100644 index 000000000..8f245195c --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Request/Contracts/IRequestPipelineBuilder.cs @@ -0,0 +1,9 @@ +namespace Elsa.Mediator.Middleware.Request.Contracts; + +public interface IRequestPipelineBuilder +{ + public IDictionary Properties { get; } + IServiceProvider ApplicationServices { get; } + IRequestPipelineBuilder Use(Func middleware); + public RequestMiddlewareDelegate Build(); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Request/MiddlewareExtensions.cs b/src/core/Elsa.Mediator/Middleware/Request/MiddlewareExtensions.cs new file mode 100644 index 000000000..9ecbd1039 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Request/MiddlewareExtensions.cs @@ -0,0 +1,20 @@ +using Elsa.Mediator.Middleware.Request.Contracts; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Mediator.Middleware.Request; + +public static class MiddlewareExtensions +{ + public static IRequestPipelineBuilder UseMiddleware(this IRequestPipelineBuilder builder, params object[] args) where TMiddleware: IRequestMiddleware + { + var middleware = typeof(TMiddleware); + + return builder.Use(next => + { + var invokeMethod = MiddlewareHelpers.GetInvokeMethod(middleware); + var ctorParams = new[] { next }.Concat(args).Select(x => x!).ToArray(); + var instance = ActivatorUtilities.CreateInstance(builder.ApplicationServices, middleware, ctorParams); + return (RequestMiddlewareDelegate)invokeMethod.CreateDelegate(typeof(RequestMiddlewareDelegate), instance); + }); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Request/RequestContext.cs b/src/core/Elsa.Mediator/Middleware/Request/RequestContext.cs new file mode 100644 index 000000000..01ce217de --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Request/RequestContext.cs @@ -0,0 +1,18 @@ +using Elsa.Mediator.Contracts; + +namespace Elsa.Mediator.Middleware.Request; + +public class RequestContext +{ + public RequestContext(IRequest request, Type responseType, CancellationToken cancellationToken) + { + Request = request; + ResponseType = responseType; + CancellationToken = cancellationToken; + } + + public IRequest Request { get; init; } + public Type ResponseType { get; init; } + public CancellationToken CancellationToken { get; init; } + public object? Response { get; set; } +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Request/RequestMiddlewareDelegate.cs b/src/core/Elsa.Mediator/Middleware/Request/RequestMiddlewareDelegate.cs new file mode 100644 index 000000000..30f1c806a --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Request/RequestMiddlewareDelegate.cs @@ -0,0 +1,3 @@ +namespace Elsa.Mediator.Middleware.Request; + +public delegate ValueTask RequestMiddlewareDelegate(RequestContext context); \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Request/RequestPipeline.cs b/src/core/Elsa.Mediator/Middleware/Request/RequestPipeline.cs new file mode 100644 index 000000000..5facde19f --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Request/RequestPipeline.cs @@ -0,0 +1,24 @@ +using Elsa.Mediator.Middleware.Request.Contracts; + +namespace Elsa.Mediator.Middleware.Request; + +public class RequestPipeline : IRequestPipeline +{ + private readonly IServiceProvider _serviceProvider; + private RequestMiddlewareDelegate? _pipeline; + + public RequestPipeline(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + public RequestMiddlewareDelegate Pipeline => _pipeline ??= CreateDefaultPipeline(); + + public RequestMiddlewareDelegate Setup(Action? setup = default) + { + var builder = new RequestPipelineBuilder(_serviceProvider); + setup?.Invoke(builder); + _pipeline = builder.Build(); + return _pipeline; + } + + public async Task ExecuteAsync(RequestContext context) => await Pipeline(context); + + private RequestMiddlewareDelegate CreateDefaultPipeline() => Setup(x => x.UseRequestHandlers()); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Request/RequestPipelineBuilder.cs b/src/core/Elsa.Mediator/Middleware/Request/RequestPipelineBuilder.cs new file mode 100644 index 000000000..b4785da92 --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Request/RequestPipelineBuilder.cs @@ -0,0 +1,41 @@ +using Elsa.Mediator.Middleware.Request.Contracts; + +namespace Elsa.Mediator.Middleware.Request; + +public class RequestPipelineBuilder : IRequestPipelineBuilder +{ + private const string ServicesKey = "mediator.Services"; + private readonly IList> _components = new List>(); + + public RequestPipelineBuilder(IServiceProvider serviceProvider) + { + ApplicationServices = serviceProvider; + } + + public IDictionary Properties { get; } = new Dictionary(); + + public IServiceProvider ApplicationServices + { + get => GetProperty(ServicesKey)!; + set => SetProperty(ServicesKey, value); + } + + public IRequestPipelineBuilder Use(Func middleware) + { + _components.Add(middleware); + return this; + } + + public RequestMiddlewareDelegate Build() + { + RequestMiddlewareDelegate pipeline = _ => new ValueTask(); + + foreach (var component in _components.Reverse()) + pipeline = component(pipeline); + + return pipeline; + } + + private T? GetProperty(string key) => Properties.TryGetValue(key, out var value) ? (T?)value : default(T); + private void SetProperty(string key, T value) => Properties[key] = value; +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Middleware/Request/RequestPipelineBuilderExtensions.cs b/src/core/Elsa.Mediator/Middleware/Request/RequestPipelineBuilderExtensions.cs new file mode 100644 index 000000000..075d52b1d --- /dev/null +++ b/src/core/Elsa.Mediator/Middleware/Request/RequestPipelineBuilderExtensions.cs @@ -0,0 +1,10 @@ +using Elsa.Mediator.Middleware.Request.Components; +using Elsa.Mediator.Middleware.Request.Contracts; + +namespace Elsa.Mediator.Middleware.Request; + +public static class RequestPipelineBuilderExtensions +{ + public static IRequestPipelineBuilder UseRequestHandlers(this IRequestPipelineBuilder builder) => builder.UseMiddleware(); + public static IRequestPipelineBuilder UseRequestLogging(this IRequestPipelineBuilder builder) => builder.UseMiddleware(); +} \ No newline at end of file diff --git a/src/core/Elsa.Mediator/Services/DefaultMediator.cs b/src/core/Elsa.Mediator/Services/DefaultMediator.cs new file mode 100644 index 000000000..3943b1bdf --- /dev/null +++ b/src/core/Elsa.Mediator/Services/DefaultMediator.cs @@ -0,0 +1,57 @@ +using Elsa.Mediator.Contracts; +using Elsa.Mediator.Middleware.Command; +using Elsa.Mediator.Middleware.Command.Contracts; +using Elsa.Mediator.Middleware.Notification; +using Elsa.Mediator.Middleware.Notification.Contracts; +using Elsa.Mediator.Middleware.Request; +using Elsa.Mediator.Middleware.Request.Contracts; + +namespace Elsa.Mediator.Services; + +public class DefaultMediator : IMediator +{ + private readonly IRequestPipeline _requestPipeline; + private readonly ICommandPipeline _commandPipeline; + private readonly INotificationPipeline _notificationPipeline; + + public DefaultMediator( + IRequestPipeline requestPipeline, + ICommandPipeline commandPipeline, + INotificationPipeline notificationPipeline) + { + _requestPipeline = requestPipeline; + _commandPipeline = commandPipeline; + _notificationPipeline = notificationPipeline; + } + + public async Task RequestAsync(IRequest request, CancellationToken cancellationToken = default) + { + var responseType = typeof(T); + var context = new RequestContext(request, responseType, cancellationToken); + await _requestPipeline.ExecuteAsync(context); + + return (T)context.Response!; + } + + public async Task ExecuteAsync(ICommand command, CancellationToken cancellationToken = default) + { + var resultType = typeof(Unit); + var context = new CommandContext(command, resultType, cancellationToken); + await _commandPipeline.ExecuteAsync(context); + } + + public async Task ExecuteAsync(ICommand command, CancellationToken cancellationToken = default) + { + var resultType = typeof(T); + var context = new CommandContext(command, resultType, cancellationToken); + await _commandPipeline.ExecuteAsync(context); + + return (T)context.Result!; + } + + public async Task PublishAsync(INotification notification, CancellationToken cancellationToken = default) + { + var context = new NotificationContext(notification, cancellationToken); + await _notificationPipeline.ExecuteAsync(context); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Serialization/Elsa.Serialization.csproj b/src/core/Elsa.Serialization/Elsa.Serialization.csproj new file mode 100644 index 000000000..2bb3a4dfc --- /dev/null +++ b/src/core/Elsa.Serialization/Elsa.Serialization.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/src/core/Elsa/Elsa.csproj b/src/core/Elsa/Elsa.csproj new file mode 100644 index 000000000..d3640a17c --- /dev/null +++ b/src/core/Elsa/Elsa.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + Elsa + latest + + + + + + + + + + + + + + + + diff --git a/src/core/Elsa/Extensions/ServiceCollectionExtensions.cs b/src/core/Elsa/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..2cbc48d3a --- /dev/null +++ b/src/core/Elsa/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,78 @@ +using System; +using Elsa.ActivityNodeResolvers; +using Elsa.Contracts; +using Elsa.Dsl.Abstractions; +using Elsa.Dsl.Extensions; +using Elsa.Dsl.Services; +using Elsa.Expressions; +using Elsa.Options; +using Elsa.Persistence.Extensions; +using Elsa.Pipelines.ActivityExecution; +using Elsa.Pipelines.WorkflowExecution; +using Elsa.Runtime.Contracts; +using Elsa.Runtime.Extensions; +using Elsa.Runtime.HostedServices; +using Elsa.Runtime.Options; +using Elsa.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddElsa(this IServiceCollection services) => services + .AddElsaCore() + .AddElsaRuntime() + .AddDefaultExpressionHandlers(); + + public static IServiceCollection AddElsaCore(this IServiceCollection services) + { + services.AddOptions(); + + return services + + // Core. + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + + // DSL. + .AddDsl() + + // Expressions. + .AddSingleton() + .AddSingleton() + + // Pipelines. + .AddSingleton() + .AddSingleton() + + // Persistence services. + .AddPersistenceServices() + + // Logging + .AddLogging(); + } + + private static IServiceCollection AddDefaultExpressionHandlers(this IServiceCollection services) => + services + .AddExpressionHandler() + .AddExpressionHandler() + .AddExpressionHandler() + .AddExpressionHandler() + .AddExpressionHandler(); + + public static IServiceCollection AddWorkflowProvider(this IServiceCollection services) where T : class, IWorkflowProvider => services.AddSingleton(); + public static IServiceCollection AddStimulusHandler(this IServiceCollection services) where T : class, IStimulusHandler => services.AddSingleton(); + public static IServiceCollection AddInstructionInterpreter(this IServiceCollection services) where T : class, IWorkflowInstructionInterpreter => services.AddSingleton(); + public static IServiceCollection ConfigureWorkflowRuntime(this IServiceCollection services, Action configure) => services.Configure(configure); + public static IServiceCollection IndexWorkflowTriggers(this IServiceCollection services) => services.AddHostedService(); +} \ No newline at end of file diff --git a/src/core/Elsa/Extensions/ServiceProviderExtensions.cs b/src/core/Elsa/Extensions/ServiceProviderExtensions.cs new file mode 100644 index 000000000..7932a203b --- /dev/null +++ b/src/core/Elsa/Extensions/ServiceProviderExtensions.cs @@ -0,0 +1,22 @@ +using System; +using Elsa.Contracts; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Extensions; + +public static class ServiceProviderExtensions +{ + public static IServiceProvider ConfigureDefaultActivityExecutionPipeline(this IServiceProvider services, Action setup) + { + var pipeline = services.GetRequiredService(); + pipeline.Setup(setup); + return services; + } + + public static IServiceProvider ConfigureDefaultWorkflowExecutionPipeline(this IServiceProvider services, Action setup) + { + var pipeline = services.GetRequiredService(); + pipeline.Setup(setup); + return services; + } +} \ No newline at end of file diff --git a/src/designer/elsa-workflows-designer/.editorconfig b/src/designer/elsa-workflows-designer/.editorconfig new file mode 100644 index 000000000..f1cc3ad32 --- /dev/null +++ b/src/designer/elsa-workflows-designer/.editorconfig @@ -0,0 +1,15 @@ +# http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/src/designer/elsa-workflows-designer/.gitignore b/src/designer/elsa-workflows-designer/.gitignore new file mode 100644 index 000000000..c3ea58a61 --- /dev/null +++ b/src/designer/elsa-workflows-designer/.gitignore @@ -0,0 +1,26 @@ +dist/ +www/ +loader/ + +*~ +*.sw[mnpcod] +*.log +*.lock +*.tmp +*.tmp.* +log.txt +*.sublime-project +*.sublime-workspace + +.stencil/ +.idea/ +.vscode/ +.sass-cache/ +.versions/ +node_modules/ +$RECYCLE.BIN/ + +.DS_Store +Thumbs.db +UserInterfaceState.xcuserstate +.env diff --git a/src/designer/elsa-workflows-designer/.prettierrc.json b/src/designer/elsa-workflows-designer/.prettierrc.json new file mode 100644 index 000000000..7ca3a28a9 --- /dev/null +++ b/src/designer/elsa-workflows-designer/.prettierrc.json @@ -0,0 +1,13 @@ +{ + "arrowParens": "avoid", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "jsxSingleQuote": false, + "quoteProps": "consistent", + "printWidth": 180, + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "all", + "useTabs": false +} diff --git a/src/designer/elsa-workflows-designer/LICENSE b/src/designer/elsa-workflows-designer/LICENSE new file mode 100644 index 000000000..b442934b7 --- /dev/null +++ b/src/designer/elsa-workflows-designer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/designer/elsa-workflows-designer/package-lock.json b/src/designer/elsa-workflows-designer/package-lock.json new file mode 100644 index 000000000..186859d16 --- /dev/null +++ b/src/designer/elsa-workflows-designer/package-lock.json @@ -0,0 +1,1181 @@ +{ + "name": "elsa-workflows-designer", + "version": "3.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@antv/x6": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@antv/x6/-/x6-1.29.1.tgz", + "integrity": "sha512-XSCtW1T/CIC0QUUUYW04EUQ5VnBMsShiNz9NP64oN8fAW14Jw99NKUhzToKpHGqQtVDg6qBX1UjFsfi0SUL4lg==", + "requires": { + "csstype": "^3.0.3", + "jquery": "^3.5.1", + "jquery-mousewheel": "^3.1.13", + "lodash-es": "^4.17.15", + "mousetrap": "^1.6.5", + "utility-types": "^3.10.0" + } + }, + "@babel/code-frame": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", + "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.15.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@fullhuman/postcss-purgecss": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-4.1.3.tgz", + "integrity": "sha512-jqcsyfvq09VOsMXxJMPLRF6Fhg/NNltzWKnC9qtzva+QKTxerCO4esG6je7hbnmkpZtaDyPTwMBj9bzfWorsrw==", + "dev": true, + "requires": { + "purgecss": "^4.1.3" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@stencil/core": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.12.0.tgz", + "integrity": "sha512-hQlQKh5CUJe8g3L5avLLsfgVu95HMS2LToTtS7gpvvP0eKes1VvAC56uhI+vH4u44GZl9ck/g1rJBVRmMWu0LA==" + }, + "@stencil/postcss": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@stencil/postcss/-/postcss-2.1.0.tgz", + "integrity": "sha512-/e4TYEXErGaHxH0ocg620YqEMLuMLpK/Wg4MJsiJglrLZCZhU4XCX1N0SwxaIOUbEZ1Zh+AqQ++yMI92ilndEA==", + "dev": true, + "requires": { + "autoprefixer": "^10.3.6", + "postcss": "~8.3.8" + }, + "dependencies": { + "postcss": { + "version": "8.3.11", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.11.tgz", + "integrity": "sha512-hCmlUAIlUiav8Xdqw3Io4LcpA1DOt7h3LSTAC4G6JGHFFaWzI6qvFt9oilvl8BmkbBRX1IhM90ZAmpk68zccQA==", + "dev": true, + "requires": { + "nanoid": "^3.1.30", + "picocolors": "^1.0.0", + "source-map-js": "^0.6.2" + } + }, + "source-map-js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", + "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "dev": true + } + } + }, + "@stencil/sass": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-1.5.2.tgz", + "integrity": "sha512-nJ93pUSylsGsMX0eLmhxh1oEljcUjj5mYjhB9ziCdoaydAdjukrUoqDCC7tdVbOcBo2hKptQyWqCtETnBGYsXQ==", + "dev": true + }, + "@stencil/state-tunnel": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stencil/state-tunnel/-/state-tunnel-1.0.1.tgz", + "integrity": "sha512-DYG8uROgL9hkjVTCtCfRBb0d3FwpiFB0muRrNZQ2X1Qo5hxMuNNji76/ILddqeq0AfgkKCW82xrMPDpy+rNIhQ==" + }, + "@stencil/store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@stencil/store/-/store-1.5.0.tgz", + "integrity": "sha512-fe5fCF6dgVlDM1iLRkkJUyUh0Tfx305asVGgMAJjIs7Q+x/b1pGgTLROm9Ibr53PZuFwr5Kg+4h9p4FLbYqHgA==" + }, + "@tailwindcss/forms": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.4.0.tgz", + "integrity": "sha512-DeaQBx6EgEeuZPQACvC+mKneJsD8am1uiJugjgQK1+/Vt+Ai0GpFBC2T2fqnUad71WgOxyrZPE6BG1VaI6YqfQ==", + "dev": true, + "requires": { + "mini-svg-data-uri": "^1.2.3" + } + }, + "@tailwindcss/typography": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.0.tgz", + "integrity": "sha512-1p/3C6C+JJziS/ghtG8ACYalbA2SyLJY27Pm33cVTlAoY6VQ7zfm2H64cPxUMBkVIlWXTtWHhZcZJPobMRmQAA==", + "dev": true, + "requires": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0" + } + }, + "@types/jquery": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.11.tgz", + "integrity": "sha512-lYZGdfOtUa0XFjIATQgiogqeTY5PNNMOmp3Jq48ghmJALL8t/IqABRqlEwdHfuUdA8iIE1uGD1HoI4a7Tiy6OA==", + "dev": true, + "requires": { + "@types/sizzle": "*" + } + }, + "@types/jquery-mousewheel": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@types/jquery-mousewheel/-/jquery-mousewheel-3.1.9.tgz", + "integrity": "sha512-1lcbzFEFWF05Q66D6H0qH3cheI/KNJKpYmEknYSjzppRh2h4UPNsr+5TsEYSwzFg7ia37Dd5iEK3b03SS6OCjQ==", + "dev": true, + "requires": { + "@types/jquery": "*" + } + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/sizzle": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", + "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", + "dev": true + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz", + "integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==", + "dev": true + }, + "autoprefixer": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.0.tgz", + "integrity": "sha512-7FdJ1ONtwzV1G43GDD0kpVMn/qbiNqyOPMFTX5nRffI+7vgWoFEc6DcXOxHJxrWNDXrZh18eDsZjvZGUljSRGA==", + "dev": true, + "requires": { + "browserslist": "^4.17.5", + "caniuse-lite": "^1.0.30001272", + "fraction.js": "^4.1.1", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.1.0" + } + }, + "axios": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", + "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "requires": { + "follow-redirects": "^1.14.4" + } + }, + "axios-middleware": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/axios-middleware/-/axios-middleware-0.3.1.tgz", + "integrity": "sha512-DuPLxZgqBYnRgEhEUulNzPspf0j9jlH5iWOJHEfuLFfsxWLc7vjeOxR903dp+G+nrU3TYWbg2COLZ4PDUwh87g==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.5.tgz", + "integrity": "sha512-I3ekeB92mmpctWBoLXe0d5wPS2cBuRvvW0JyyJHMrk9/HmP2ZjrTboNAZ8iuGqaEIlKguljbQY32OkOJIRrgoA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001271", + "electron-to-chromium": "^1.3.878", + "escalade": "^3.1.1", + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001272", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001272.tgz", + "integrity": "sha512-DV1j9Oot5dydyH1v28g25KoVm7l8MTxazwuiH3utWiAS6iL/9Nh//TGwqFEeqqN8nnWYQ8HHhUq+o4QPt9kvYw==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "csstype": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "detective": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", + "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", + "dev": true, + "requires": { + "acorn-node": "^1.6.1", + "defined": "^1.0.0", + "minimist": "^1.1.1" + } + }, + "didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "el-transition": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/el-transition/-/el-transition-0.0.7.tgz", + "integrity": "sha512-a23pEa7ahqLmBIEqtxi8CdL6CRLF52ZoAwawDFTD1IDkegrL4iValQAj6IMEgIEXPSrXZtx/Nr/ej6ebov4uoA==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.883", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.883.tgz", + "integrity": "sha512-goyjNx4wB9j911PBteb+AXNbErug7rJVkmDXWdw5SCVn2JlARBwsqucPkvp1h5mXWxHUbBRK3bwXTrqSxSiAIQ==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "follow-redirects": { + "version": "1.14.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", + "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==" + }, + "fraction.js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.1.tgz", + "integrity": "sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "import-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", + "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", + "dev": true, + "requires": { + "import-from": "^3.0.0" + } + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", + "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "jquery": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", + "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" + }, + "jquery-mousewheel": { + "version": "3.1.13", + "resolved": "https://registry.npmjs.org/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz", + "integrity": "sha1-BvAzXxbjU6aV5yBr9QUDy1I6buU=" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "lilconfig": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", + "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha1-wCUTUV4wna3dTCTGDP3c9ZdtkRU=", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "mini-svg-data-uri": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.3.tgz", + "integrity": "sha512-gSfqpMRC8IxghvMcxzzmMnWpXAChSA+vy4cia33RgerMS8Fex95akUyQZPbxJJmeBGiGmK7n/1OpUX8ksRjIdA==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "dev": true + }, + "mousetrap": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", + "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==" + }, + "nanoid": { + "version": "3.1.30", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", + "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==", + "dev": true + }, + "node-releases": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "postcss": { + "version": "8.4.5", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz", + "integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==", + "dev": true, + "requires": { + "nanoid": "^3.1.30", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.1" + } + }, + "postcss-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-3.0.3.tgz", + "integrity": "sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw==", + "dev": true, + "requires": { + "camelcase-css": "^2.0.1", + "postcss": "^8.1.6" + } + }, + "postcss-load-config": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.0.tgz", + "integrity": "sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==", + "dev": true, + "requires": { + "import-cwd": "^3.0.0", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + } + }, + "postcss-nested": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", + "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.6" + } + }, + "postcss-selector-parser": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.8.tgz", + "integrity": "sha512-D5PG53d209Z1Uhcc0qAZ5U3t5HagH3cxu+WLZ22jt3gLUpXM4eXXfiO14jiDWST3NNooX/E8wISfOhZ9eIjGTQ==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true + }, + "purgecss": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-4.1.3.tgz", + "integrity": "sha512-99cKy4s+VZoXnPxaoM23e5ABcP851nC2y2GROkkjS8eJaJtlciGavd7iYAw2V84WeBqggZ12l8ef44G99HmTaw==", + "dev": true, + "requires": { + "commander": "^8.0.0", + "glob": "^7.1.7", + "postcss": "^8.3.5", + "postcss-selector-parser": "^6.0.6" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "source-map-js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz", + "integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tailwindcss": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.0.7.tgz", + "integrity": "sha512-rZdKNHtC64jcQncLoWOuCzj4lQDTAgLtgK3WmQS88tTdpHh9OwLqULTQxI3tw9AMJsqSpCKlmcjW/8CSnni6zQ==", + "dev": true, + "requires": { + "arg": "^5.0.1", + "chalk": "^4.1.2", + "chokidar": "^3.5.2", + "color-name": "^1.1.4", + "cosmiconfig": "^7.0.1", + "detective": "^5.2.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.7", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "normalize-path": "^3.0.0", + "object-hash": "^2.2.0", + "postcss-js": "^3.0.3", + "postcss-load-config": "^3.1.0", + "postcss-nested": "5.0.6", + "postcss-selector-parser": "^6.0.7", + "postcss-value-parser": "^4.2.0", + "quick-lru": "^5.1.1", + "resolve": "^1.20.0", + "tmp": "^0.2.1" + }, + "dependencies": { + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + } + } + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "typedi": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/typedi/-/typedi-0.10.0.tgz", + "integrity": "sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + } + } +} diff --git a/src/designer/elsa-workflows-designer/package.json b/src/designer/elsa-workflows-designer/package.json new file mode 100644 index 000000000..8f55fc031 --- /dev/null +++ b/src/designer/elsa-workflows-designer/package.json @@ -0,0 +1,52 @@ +{ + "name": "elsa-workflows-designer", + "version": "3.0.0", + "description": "Stencil Component Starter", + "main": "dist/index.cjs.js", + "module": "dist/custom-elements/index.js", + "es2015": "dist/esm/index.mjs", + "es2017": "dist/esm/index.mjs", + "types": "dist/custom-elements/index.d.ts", + "collection": "dist/collection/collection-manifest.json", + "collection:main": "dist/collection/index.js", + "unpkg": "dist/elsa-workflows-designer/elsa-workflows-designer.esm.js", + "files": [ + "dist/", + "loader/" + ], + "scripts": { + "build": "stencil build", + "build.dev": "stencil build --dev", + "start": "stencil build --dev --watch --serve", + "test": "stencil test --spec --e2e", + "test.watch": "stencil test --spec --e2e --watchAll", + "generate": "stencil generate" + }, + "dependencies": { + "@antv/x6": "^1.29.1", + "@stencil/core": "^2.12.0", + "@stencil/state-tunnel": "^1.0.1", + "@stencil/store": "^1.5.0", + "axios": "^0.24.0", + "axios-middleware": "^0.3.1", + "reflect-metadata": "^0.1.13", + "typedi": "^0.10.0", + "lodash": "^4.17.21", + "uuid": "^8.3.2", + "el-transition": "^0.0.7", + "moment": "^2.29.1" + }, + "devDependencies": { + "@fullhuman/postcss-purgecss": "^4.1.3", + "@stencil/postcss": "^2.1.0", + "@stencil/sass": "^1.5.2", + "@tailwindcss/forms": "^0.4.0", + "@tailwindcss/typography": "^0.5.0", + "@types/jquery": "^3.5.11", + "@types/jquery-mousewheel": "^3.1.9", + "autoprefixer": "^10.4.0", + "postcss": "^8.4.5", + "tailwindcss": "^3.0.7" + }, + "license": "MIT" +} diff --git a/src/designer/elsa-workflows-designer/readme.md b/src/designer/elsa-workflows-designer/readme.md new file mode 100644 index 000000000..3db484fb0 --- /dev/null +++ b/src/designer/elsa-workflows-designer/readme.md @@ -0,0 +1,75 @@ +![Built With Stencil](https://img.shields.io/badge/-Built%20With%20Stencil-16161d.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI%2BCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI%2BCgkuc3Qwe2ZpbGw6I0ZGRkZGRjt9Cjwvc3R5bGU%2BCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik00MjQuNywzNzMuOWMwLDM3LjYtNTUuMSw2OC42LTkyLjcsNjguNkgxODAuNGMtMzcuOSwwLTkyLjctMzAuNy05Mi43LTY4LjZ2LTMuNmgzMzYuOVYzNzMuOXoiLz4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTQyNC43LDI5Mi4xSDE4MC40Yy0zNy42LDAtOTIuNy0zMS05Mi43LTY4LjZ2LTMuNkgzMzJjMzcuNiwwLDkyLjcsMzEsOTIuNyw2OC42VjI5Mi4xeiIvPgo8cGF0aCBjbGFzcz0ic3QwIiBkPSJNNDI0LjcsMTQxLjdIODcuN3YtMy42YzAtMzcuNiw1NC44LTY4LjYsOTIuNy02OC42SDMzMmMzNy45LDAsOTIuNywzMC43LDkyLjcsNjguNlYxNDEuN3oiLz4KPC9zdmc%2BCg%3D%3D&colorA=16161d&style=flat-square) + +# Stencil Component Starter + +This is a starter project for building a standalone Web Component using Stencil. + +Stencil is also great for building entire apps. For that, use the [stencil-app-starter](https://github.com/ionic-team/stencil-app-starter) instead. + +# Stencil + +Stencil is a compiler for building fast web apps using Web Components. + +Stencil combines the best concepts of the most popular frontend frameworks into a compile-time rather than run-time tool. Stencil takes TypeScript, JSX, a tiny virtual DOM layer, efficient one-way data binding, an asynchronous rendering pipeline (similar to React Fiber), and lazy-loading out of the box, and generates 100% standards-based Web Components that run in any browser supporting the Custom Elements v1 spec. + +Stencil components are just Web Components, so they work in any major framework or with no framework at all. + +## Getting Started + +To start building a new web component using Stencil, clone this repo to a new directory: + +```bash +git clone https://github.com/ionic-team/stencil-component-starter.git my-component +cd my-component +git remote rm origin +``` + +and run: + +```bash +npm install +npm start +``` + +To build the component for production, run: + +```bash +npm run build +``` + +To run the unit tests for the components, run: + +```bash +npm test +``` + +Need help? Check out our docs [here](https://stenciljs.com/docs/my-first-component). + + +## Naming Components + +When creating new component tags, we recommend _not_ using `stencil` in the component name (ex: ``). This is because the generated component has little to nothing to do with Stencil; it's just a web component! + +Instead, use a prefix that fits your company or any name for a group of related components. For example, all of the Ionic generated web components use the prefix `ion`. + + +## Using this component + +There are three strategies we recommend for using web components built with Stencil. + +The first step for all three of these strategies is to [publish to NPM](https://docs.npmjs.com/getting-started/publishing-npm-packages). + +### Script tag + +- Put a script tag similar to this `` in the head of your index.html +- Then you can use the element anywhere in your template, JSX, html etc + +### Node Modules +- Run `npm install my-component --save` +- Put a script tag similar to this `` in the head of your index.html +- Then you can use the element anywhere in your template, JSX, html etc + +### In a stencil-starter app +- Run `npm install my-component --save` +- Add an import to the npm packages `import my-component;` +- Then you can use the element anywhere in your template, JSX, html etc diff --git a/src/designer/elsa-workflows-designer/src/components.d.ts b/src/designer/elsa-workflows-designer/src/components.d.ts new file mode 100644 index 000000000..0c09d918e --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components.d.ts @@ -0,0 +1,527 @@ +/* eslint-disable */ +/* tslint:disable */ +/** + * This is an autogenerated file created by the Stencil compiler. + * It contains typing information for all components that exist in this project. + */ +import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; +import { ActionDefinition, ActionInvokedArgs, Activity, ActivityDescriptor, ActivitySelectedArgs, ContainerSelectedArgs, GraphUpdatedArgs, TabChangedArgs, TabDefinition, Trigger, TriggerDescriptor, Workflow, WorkflowInstance, WorkflowInstanceSummary, WorkflowSummary } from "./models"; +import { ActivityUpdatedArgs, DeleteActivityRequestedArgs } from "./components/designer/activity-properties-editor/activity-properties-editor"; +import { AddActivityArgs } from "./components/designer/canvas/canvas"; +import { NodeInputContext } from "./services/node-input-driver"; +import { MenuItem } from "./components/shared/context-menu/models"; +import { DropdownButtonItem, DropdownButtonOrigin } from "./components/shared/dropdown-button/models"; +import { AddActivityArgs as AddActivityArgs1 } from "./components/designer/canvas/canvas"; +import { PagerData } from "./components/shared/pager/pager"; +import { PanelPosition, PanelStateChangedArgs } from "./components/designer/panel/models"; +import { Graph } from "@antv/x6"; +import { TriggerDeselectedArgs, TriggerSelectedArgs, TriggersUpdatedArgs } from "./components/designer/trigger-container/trigger-container"; +import { DeleteTriggerRequestedArgs, TriggerUpdatedArgs } from "./components/designer/trigger-properties-editor/trigger-properties-editor"; +import { WorkflowUpdatedArgs } from "./components/designer/workflow-editor/workflow-editor"; +import { ActivityDriverRegistry } from "./services"; +import { WorkflowPropsUpdatedArgs } from "./components/designer/workflow-properties-editor/workflow-properties-editor"; +import { PublishClickedArgs } from "./components/toolbar/workflow-publish-button/workflow-publish-button"; +export namespace Components { + interface ElsaActivityPropertiesEditor { + "activity"?: Activity; + "activityDescriptors": Array; + "hide": () => Promise; + "show": () => Promise; + } + interface ElsaCanvas { + "addActivity": (args: AddActivityArgs) => Promise; + "exportGraph": () => Promise; + "importGraph": (root: Activity) => Promise; + "interactiveMode": boolean; + "updateLayout": () => Promise; + } + interface ElsaCheckListInput { + "inputContext": NodeInputContext; + } + interface ElsaContextMenu { + "menuItems": Array; + } + interface ElsaDropdownButton { + "icon"?: any; + "items": Array; + "origin": DropdownButtonOrigin; + "text": string; + } + interface ElsaDropdownInput { + "inputContext": NodeInputContext; + } + interface ElsaFlowchart { + "activityDescriptors": Array; + "addActivity": (args: AddActivityArgs) => Promise; + "exportRoot": () => Promise; + "importRoot": (root: Activity) => Promise; + "interactiveMode": boolean; + "root"?: Activity; + "updateLayout": () => Promise; + } + interface ElsaFormPanel { + "actions": Array; + "headerText": string; + "selectedTabIndex"?: number; + "tabs": Array; + } + interface ElsaModalDialog { + "actions": Array; + "hide": (animate?: boolean) => Promise; + "show": (animate?: boolean) => Promise; + "size": string; + } + interface ElsaPager { + "page": number; + "pageSize": number; + "totalCount": number; + } + interface ElsaPanel { + "position": PanelPosition; + } + interface ElsaSingleLineInput { + "inputContext": NodeInputContext; + } + interface ElsaSlideOverPanel { + "actions": Array; + "expand": boolean; + "headerText": string; + "hide": () => Promise; + "selectedTab"?: TabDefinition; + "show": () => Promise; + "tabs": Array; + } + interface ElsaStudio { + "serverUrl": string; + } + interface ElsaToolbox { + "graph": Graph; + } + interface ElsaToolboxActivities { + "activityDescriptors": Array; + "graph": Graph; + "triggerDescriptors": Array; + } + interface ElsaToolboxTriggers { + "activityDescriptors": Array; + "graph": Graph; + "triggerDescriptors": Array; + } + interface ElsaTriggerContainer { + "deselectAll": () => Promise; + "interactiveMode": boolean; + "triggerDescriptors": Array; + "triggers": Array; + } + interface ElsaTriggerPropertiesEditor { + "hide": () => Promise; + "show": () => Promise; + "trigger"?: Trigger; + "triggerDescriptors": Array; + } + interface ElsaWorkflowBrowser { + "hide": () => Promise; + "show": () => Promise; + } + interface ElsaWorkflowEditor { + "activityDescriptors": Array; + "getWorkflow": () => Promise; + "importWorkflow": (workflow: Workflow, workflowInstance?: WorkflowInstance) => Promise; + "importWorkflowMetadata": (workflow: Workflow) => Promise; + "registerActivityDrivers": (register: (registry: ActivityDriverRegistry) => void) => Promise; + "triggerDescriptors": Array; + } + interface ElsaWorkflowInstanceBrowser { + "hide": () => Promise; + "show": () => Promise; + } + interface ElsaWorkflowPropertiesEditor { + "hide": () => Promise; + "show": () => Promise; + "workflow"?: Workflow; + } + interface ElsaWorkflowPublishButton { + "publishing": boolean; + } + interface ElsaWorkflowToolbar { + } + interface ElsaWorkflowToolbarMenu { + } +} +declare global { + interface HTMLElsaActivityPropertiesEditorElement extends Components.ElsaActivityPropertiesEditor, HTMLStencilElement { + } + var HTMLElsaActivityPropertiesEditorElement: { + prototype: HTMLElsaActivityPropertiesEditorElement; + new (): HTMLElsaActivityPropertiesEditorElement; + }; + interface HTMLElsaCanvasElement extends Components.ElsaCanvas, HTMLStencilElement { + } + var HTMLElsaCanvasElement: { + prototype: HTMLElsaCanvasElement; + new (): HTMLElsaCanvasElement; + }; + interface HTMLElsaCheckListInputElement extends Components.ElsaCheckListInput, HTMLStencilElement { + } + var HTMLElsaCheckListInputElement: { + prototype: HTMLElsaCheckListInputElement; + new (): HTMLElsaCheckListInputElement; + }; + interface HTMLElsaContextMenuElement extends Components.ElsaContextMenu, HTMLStencilElement { + } + var HTMLElsaContextMenuElement: { + prototype: HTMLElsaContextMenuElement; + new (): HTMLElsaContextMenuElement; + }; + interface HTMLElsaDropdownButtonElement extends Components.ElsaDropdownButton, HTMLStencilElement { + } + var HTMLElsaDropdownButtonElement: { + prototype: HTMLElsaDropdownButtonElement; + new (): HTMLElsaDropdownButtonElement; + }; + interface HTMLElsaDropdownInputElement extends Components.ElsaDropdownInput, HTMLStencilElement { + } + var HTMLElsaDropdownInputElement: { + prototype: HTMLElsaDropdownInputElement; + new (): HTMLElsaDropdownInputElement; + }; + interface HTMLElsaFlowchartElement extends Components.ElsaFlowchart, HTMLStencilElement { + } + var HTMLElsaFlowchartElement: { + prototype: HTMLElsaFlowchartElement; + new (): HTMLElsaFlowchartElement; + }; + interface HTMLElsaFormPanelElement extends Components.ElsaFormPanel, HTMLStencilElement { + } + var HTMLElsaFormPanelElement: { + prototype: HTMLElsaFormPanelElement; + new (): HTMLElsaFormPanelElement; + }; + interface HTMLElsaModalDialogElement extends Components.ElsaModalDialog, HTMLStencilElement { + } + var HTMLElsaModalDialogElement: { + prototype: HTMLElsaModalDialogElement; + new (): HTMLElsaModalDialogElement; + }; + interface HTMLElsaPagerElement extends Components.ElsaPager, HTMLStencilElement { + } + var HTMLElsaPagerElement: { + prototype: HTMLElsaPagerElement; + new (): HTMLElsaPagerElement; + }; + interface HTMLElsaPanelElement extends Components.ElsaPanel, HTMLStencilElement { + } + var HTMLElsaPanelElement: { + prototype: HTMLElsaPanelElement; + new (): HTMLElsaPanelElement; + }; + interface HTMLElsaSingleLineInputElement extends Components.ElsaSingleLineInput, HTMLStencilElement { + } + var HTMLElsaSingleLineInputElement: { + prototype: HTMLElsaSingleLineInputElement; + new (): HTMLElsaSingleLineInputElement; + }; + interface HTMLElsaSlideOverPanelElement extends Components.ElsaSlideOverPanel, HTMLStencilElement { + } + var HTMLElsaSlideOverPanelElement: { + prototype: HTMLElsaSlideOverPanelElement; + new (): HTMLElsaSlideOverPanelElement; + }; + interface HTMLElsaStudioElement extends Components.ElsaStudio, HTMLStencilElement { + } + var HTMLElsaStudioElement: { + prototype: HTMLElsaStudioElement; + new (): HTMLElsaStudioElement; + }; + interface HTMLElsaToolboxElement extends Components.ElsaToolbox, HTMLStencilElement { + } + var HTMLElsaToolboxElement: { + prototype: HTMLElsaToolboxElement; + new (): HTMLElsaToolboxElement; + }; + interface HTMLElsaToolboxActivitiesElement extends Components.ElsaToolboxActivities, HTMLStencilElement { + } + var HTMLElsaToolboxActivitiesElement: { + prototype: HTMLElsaToolboxActivitiesElement; + new (): HTMLElsaToolboxActivitiesElement; + }; + interface HTMLElsaToolboxTriggersElement extends Components.ElsaToolboxTriggers, HTMLStencilElement { + } + var HTMLElsaToolboxTriggersElement: { + prototype: HTMLElsaToolboxTriggersElement; + new (): HTMLElsaToolboxTriggersElement; + }; + interface HTMLElsaTriggerContainerElement extends Components.ElsaTriggerContainer, HTMLStencilElement { + } + var HTMLElsaTriggerContainerElement: { + prototype: HTMLElsaTriggerContainerElement; + new (): HTMLElsaTriggerContainerElement; + }; + interface HTMLElsaTriggerPropertiesEditorElement extends Components.ElsaTriggerPropertiesEditor, HTMLStencilElement { + } + var HTMLElsaTriggerPropertiesEditorElement: { + prototype: HTMLElsaTriggerPropertiesEditorElement; + new (): HTMLElsaTriggerPropertiesEditorElement; + }; + interface HTMLElsaWorkflowBrowserElement extends Components.ElsaWorkflowBrowser, HTMLStencilElement { + } + var HTMLElsaWorkflowBrowserElement: { + prototype: HTMLElsaWorkflowBrowserElement; + new (): HTMLElsaWorkflowBrowserElement; + }; + interface HTMLElsaWorkflowEditorElement extends Components.ElsaWorkflowEditor, HTMLStencilElement { + } + var HTMLElsaWorkflowEditorElement: { + prototype: HTMLElsaWorkflowEditorElement; + new (): HTMLElsaWorkflowEditorElement; + }; + interface HTMLElsaWorkflowInstanceBrowserElement extends Components.ElsaWorkflowInstanceBrowser, HTMLStencilElement { + } + var HTMLElsaWorkflowInstanceBrowserElement: { + prototype: HTMLElsaWorkflowInstanceBrowserElement; + new (): HTMLElsaWorkflowInstanceBrowserElement; + }; + interface HTMLElsaWorkflowPropertiesEditorElement extends Components.ElsaWorkflowPropertiesEditor, HTMLStencilElement { + } + var HTMLElsaWorkflowPropertiesEditorElement: { + prototype: HTMLElsaWorkflowPropertiesEditorElement; + new (): HTMLElsaWorkflowPropertiesEditorElement; + }; + interface HTMLElsaWorkflowPublishButtonElement extends Components.ElsaWorkflowPublishButton, HTMLStencilElement { + } + var HTMLElsaWorkflowPublishButtonElement: { + prototype: HTMLElsaWorkflowPublishButtonElement; + new (): HTMLElsaWorkflowPublishButtonElement; + }; + interface HTMLElsaWorkflowToolbarElement extends Components.ElsaWorkflowToolbar, HTMLStencilElement { + } + var HTMLElsaWorkflowToolbarElement: { + prototype: HTMLElsaWorkflowToolbarElement; + new (): HTMLElsaWorkflowToolbarElement; + }; + interface HTMLElsaWorkflowToolbarMenuElement extends Components.ElsaWorkflowToolbarMenu, HTMLStencilElement { + } + var HTMLElsaWorkflowToolbarMenuElement: { + prototype: HTMLElsaWorkflowToolbarMenuElement; + new (): HTMLElsaWorkflowToolbarMenuElement; + }; + interface HTMLElementTagNameMap { + "elsa-activity-properties-editor": HTMLElsaActivityPropertiesEditorElement; + "elsa-canvas": HTMLElsaCanvasElement; + "elsa-check-list-input": HTMLElsaCheckListInputElement; + "elsa-context-menu": HTMLElsaContextMenuElement; + "elsa-dropdown-button": HTMLElsaDropdownButtonElement; + "elsa-dropdown-input": HTMLElsaDropdownInputElement; + "elsa-flowchart": HTMLElsaFlowchartElement; + "elsa-form-panel": HTMLElsaFormPanelElement; + "elsa-modal-dialog": HTMLElsaModalDialogElement; + "elsa-pager": HTMLElsaPagerElement; + "elsa-panel": HTMLElsaPanelElement; + "elsa-single-line-input": HTMLElsaSingleLineInputElement; + "elsa-slide-over-panel": HTMLElsaSlideOverPanelElement; + "elsa-studio": HTMLElsaStudioElement; + "elsa-toolbox": HTMLElsaToolboxElement; + "elsa-toolbox-activities": HTMLElsaToolboxActivitiesElement; + "elsa-toolbox-triggers": HTMLElsaToolboxTriggersElement; + "elsa-trigger-container": HTMLElsaTriggerContainerElement; + "elsa-trigger-properties-editor": HTMLElsaTriggerPropertiesEditorElement; + "elsa-workflow-browser": HTMLElsaWorkflowBrowserElement; + "elsa-workflow-editor": HTMLElsaWorkflowEditorElement; + "elsa-workflow-instance-browser": HTMLElsaWorkflowInstanceBrowserElement; + "elsa-workflow-properties-editor": HTMLElsaWorkflowPropertiesEditorElement; + "elsa-workflow-publish-button": HTMLElsaWorkflowPublishButtonElement; + "elsa-workflow-toolbar": HTMLElsaWorkflowToolbarElement; + "elsa-workflow-toolbar-menu": HTMLElsaWorkflowToolbarMenuElement; + } +} +declare namespace LocalJSX { + interface ElsaActivityPropertiesEditor { + "activity"?: Activity; + "activityDescriptors"?: Array; + "onActivityUpdated"?: (event: CustomEvent) => void; + "onDeleteActivityRequested"?: (event: CustomEvent) => void; + } + interface ElsaCanvas { + "interactiveMode"?: boolean; + } + interface ElsaCheckListInput { + "inputContext"?: NodeInputContext; + } + interface ElsaContextMenu { + "menuItems"?: Array; + } + interface ElsaDropdownButton { + "icon"?: any; + "items"?: Array; + "onItemSelected"?: (event: CustomEvent) => void; + "origin"?: DropdownButtonOrigin; + "text"?: string; + } + interface ElsaDropdownInput { + "inputContext"?: NodeInputContext; + } + interface ElsaFlowchart { + "activityDescriptors"?: Array; + "interactiveMode"?: boolean; + "onActivitySelected"?: (event: CustomEvent) => void; + "onContainerSelected"?: (event: CustomEvent) => void; + "onGraphUpdated"?: (event: CustomEvent) => void; + "root"?: Activity; + } + interface ElsaFormPanel { + "actions"?: Array; + "headerText"?: string; + "onActionInvoked"?: (event: CustomEvent) => void; + "onSelectedTabIndexChanged"?: (event: CustomEvent) => void; + "onSubmitted"?: (event: CustomEvent) => void; + "selectedTabIndex"?: number; + "tabs"?: Array; + } + interface ElsaModalDialog { + "actions"?: Array; + "onActionInvoked"?: (event: CustomEvent) => void; + "onHidden"?: (event: CustomEvent) => void; + "onShown"?: (event: CustomEvent) => void; + "size"?: string; + } + interface ElsaPager { + "onPaginated"?: (event: CustomEvent) => void; + "page"?: number; + "pageSize"?: number; + "totalCount"?: number; + } + interface ElsaPanel { + "onExpandedStateChanged"?: (event: CustomEvent) => void; + "position"?: PanelPosition; + } + interface ElsaSingleLineInput { + "inputContext"?: NodeInputContext; + } + interface ElsaSlideOverPanel { + "actions"?: Array; + "expand"?: boolean; + "headerText"?: string; + "onCollapsed"?: (event: CustomEvent) => void; + "onSubmitted"?: (event: CustomEvent) => void; + "selectedTab"?: TabDefinition; + "tabs"?: Array; + } + interface ElsaStudio { + "serverUrl"?: string; + } + interface ElsaToolbox { + "graph"?: Graph; + } + interface ElsaToolboxActivities { + "activityDescriptors"?: Array; + "graph"?: Graph; + "triggerDescriptors"?: Array; + } + interface ElsaToolboxTriggers { + "activityDescriptors"?: Array; + "graph"?: Graph; + "triggerDescriptors"?: Array; + } + interface ElsaTriggerContainer { + "interactiveMode"?: boolean; + "onTriggerDeselected"?: (event: CustomEvent) => void; + "onTriggerSelected"?: (event: CustomEvent) => void; + "onTriggersUpdated"?: (event: CustomEvent) => void; + "triggerDescriptors"?: Array; + "triggers"?: Array; + } + interface ElsaTriggerPropertiesEditor { + "onDeleteTriggerRequested"?: (event: CustomEvent) => void; + "onTriggerUpdated"?: (event: CustomEvent) => void; + "trigger"?: Trigger; + "triggerDescriptors"?: Array; + } + interface ElsaWorkflowBrowser { + "onWorkflowDefinitionSelected"?: (event: CustomEvent) => void; + } + interface ElsaWorkflowEditor { + "activityDescriptors"?: Array; + "onWorkflowUpdated"?: (event: CustomEvent) => void; + "triggerDescriptors"?: Array; + } + interface ElsaWorkflowInstanceBrowser { + "onWorkflowInstanceSelected"?: (event: CustomEvent) => void; + } + interface ElsaWorkflowPropertiesEditor { + "onWorkflowPropsUpdated"?: (event: CustomEvent) => void; + "workflow"?: Workflow; + } + interface ElsaWorkflowPublishButton { + "onExportClicked"?: (event: CustomEvent) => void; + "onImportClicked"?: (event: CustomEvent) => void; + "onNewClicked"?: (event: CustomEvent) => void; + "onPublishClicked"?: (event: CustomEvent) => void; + "onUnPublishClicked"?: (event: CustomEvent) => void; + "publishing"?: boolean; + } + interface ElsaWorkflowToolbar { + } + interface ElsaWorkflowToolbarMenu { + } + interface IntrinsicElements { + "elsa-activity-properties-editor": ElsaActivityPropertiesEditor; + "elsa-canvas": ElsaCanvas; + "elsa-check-list-input": ElsaCheckListInput; + "elsa-context-menu": ElsaContextMenu; + "elsa-dropdown-button": ElsaDropdownButton; + "elsa-dropdown-input": ElsaDropdownInput; + "elsa-flowchart": ElsaFlowchart; + "elsa-form-panel": ElsaFormPanel; + "elsa-modal-dialog": ElsaModalDialog; + "elsa-pager": ElsaPager; + "elsa-panel": ElsaPanel; + "elsa-single-line-input": ElsaSingleLineInput; + "elsa-slide-over-panel": ElsaSlideOverPanel; + "elsa-studio": ElsaStudio; + "elsa-toolbox": ElsaToolbox; + "elsa-toolbox-activities": ElsaToolboxActivities; + "elsa-toolbox-triggers": ElsaToolboxTriggers; + "elsa-trigger-container": ElsaTriggerContainer; + "elsa-trigger-properties-editor": ElsaTriggerPropertiesEditor; + "elsa-workflow-browser": ElsaWorkflowBrowser; + "elsa-workflow-editor": ElsaWorkflowEditor; + "elsa-workflow-instance-browser": ElsaWorkflowInstanceBrowser; + "elsa-workflow-properties-editor": ElsaWorkflowPropertiesEditor; + "elsa-workflow-publish-button": ElsaWorkflowPublishButton; + "elsa-workflow-toolbar": ElsaWorkflowToolbar; + "elsa-workflow-toolbar-menu": ElsaWorkflowToolbarMenu; + } +} +export { LocalJSX as JSX }; +declare module "@stencil/core" { + export namespace JSX { + interface IntrinsicElements { + "elsa-activity-properties-editor": LocalJSX.ElsaActivityPropertiesEditor & JSXBase.HTMLAttributes; + "elsa-canvas": LocalJSX.ElsaCanvas & JSXBase.HTMLAttributes; + "elsa-check-list-input": LocalJSX.ElsaCheckListInput & JSXBase.HTMLAttributes; + "elsa-context-menu": LocalJSX.ElsaContextMenu & JSXBase.HTMLAttributes; + "elsa-dropdown-button": LocalJSX.ElsaDropdownButton & JSXBase.HTMLAttributes; + "elsa-dropdown-input": LocalJSX.ElsaDropdownInput & JSXBase.HTMLAttributes; + "elsa-flowchart": LocalJSX.ElsaFlowchart & JSXBase.HTMLAttributes; + "elsa-form-panel": LocalJSX.ElsaFormPanel & JSXBase.HTMLAttributes; + "elsa-modal-dialog": LocalJSX.ElsaModalDialog & JSXBase.HTMLAttributes; + "elsa-pager": LocalJSX.ElsaPager & JSXBase.HTMLAttributes; + "elsa-panel": LocalJSX.ElsaPanel & JSXBase.HTMLAttributes; + "elsa-single-line-input": LocalJSX.ElsaSingleLineInput & JSXBase.HTMLAttributes; + "elsa-slide-over-panel": LocalJSX.ElsaSlideOverPanel & JSXBase.HTMLAttributes; + "elsa-studio": LocalJSX.ElsaStudio & JSXBase.HTMLAttributes; + "elsa-toolbox": LocalJSX.ElsaToolbox & JSXBase.HTMLAttributes; + "elsa-toolbox-activities": LocalJSX.ElsaToolboxActivities & JSXBase.HTMLAttributes; + "elsa-toolbox-triggers": LocalJSX.ElsaToolboxTriggers & JSXBase.HTMLAttributes; + "elsa-trigger-container": LocalJSX.ElsaTriggerContainer & JSXBase.HTMLAttributes; + "elsa-trigger-properties-editor": LocalJSX.ElsaTriggerPropertiesEditor & JSXBase.HTMLAttributes; + "elsa-workflow-browser": LocalJSX.ElsaWorkflowBrowser & JSXBase.HTMLAttributes; + "elsa-workflow-editor": LocalJSX.ElsaWorkflowEditor & JSXBase.HTMLAttributes; + "elsa-workflow-instance-browser": LocalJSX.ElsaWorkflowInstanceBrowser & JSXBase.HTMLAttributes; + "elsa-workflow-properties-editor": LocalJSX.ElsaWorkflowPropertiesEditor & JSXBase.HTMLAttributes; + "elsa-workflow-publish-button": LocalJSX.ElsaWorkflowPublishButton & JSXBase.HTMLAttributes; + "elsa-workflow-toolbar": LocalJSX.ElsaWorkflowToolbar & JSXBase.HTMLAttributes; + "elsa-workflow-toolbar-menu": LocalJSX.ElsaWorkflowToolbarMenu & JSXBase.HTMLAttributes; + } + } +} diff --git a/src/designer/elsa-workflows-designer/src/components/activities/container-activity-component.tsx b/src/designer/elsa-workflows-designer/src/components/activities/container-activity-component.tsx new file mode 100644 index 000000000..0a8f9810c --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/activities/container-activity-component.tsx @@ -0,0 +1,9 @@ +import {AddActivityArgs} from '../designer/canvas/canvas'; +import {Activity} from '../../models'; + +export interface ContainerActivityComponent { + updateLayout(): Promise; + addActivity(args: AddActivityArgs): Promise; + exportRoot(): Promise + importRoot(root: Activity): Promise; +} diff --git a/src/designer/elsa-workflows-designer/src/components/activities/flowchart/activity-walker.ts b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/activity-walker.ts new file mode 100644 index 000000000..977c98626 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/activity-walker.ts @@ -0,0 +1,80 @@ +import _, {camelCase} from "lodash"; +import {Activity, ActivityDescriptor, Container} from "../../../models"; + +export interface ActivityNode { + activity: Activity; + parents: Array; + children: Array; + port?: string; +} + +export interface ActivityPort { + activity: Activity; + port: string; +} + +export function walkActivities(root: Activity, descriptors: Array): ActivityNode { + const collectedActivities = new Set([root]); + const graph: ActivityNode = {activity: root, parents: [], children: []}; + const collectedNodes = new Set([graph]); + walkRecursive(graph, root, collectedActivities, collectedNodes, descriptors); + return graph; +} + +export function flatten(root: ActivityNode): Array { + return flattenList([root]); +} + +export function flattenList(activities: Array): Array { + const list: Array = [...activities]; + + for (const activity of activities) { + for (const child of activity.children) + list.push(child); + } + + return list; +} + +function walkRecursive(node: ActivityNode, activity: Activity, collectedActivities: Set, collectedNodes: Set, descriptors: Array) { + const ports = getPorts(node, activity, descriptors); + + for (const port of ports) { + const collectedNodesArray = Array.from(collectedNodes); + let childNode = collectedNodesArray.find(x => x.activity == port.activity); + + if (!childNode) { + childNode = {activity: port.activity, children: [], parents: [], port: port.port}; + collectedNodes.add(childNode); + } + + childNode.parents.push(node); + node.children.push(childNode); + collectedActivities.add(port.activity); + walkRecursive(childNode, port.activity, collectedActivities, collectedNodes, descriptors); + } +} + +function getPorts(node: ActivityNode, activity: Activity, descriptors: Array): Array { + const descriptor = descriptors.find(x => x.nodeType == activity.nodeType); + + if (!descriptor) + return []; + + let ports: Array = []; + + for (const outPort of descriptor.outPorts) { + const outPortName = _.camelCase(outPort.name); + const outbound: Activity | Array = activity[outPortName]; + + if (!!outbound) { + if (Array.isArray(outbound)) { + for (const a of outbound) + ports.push({activity: a, port: outPort.name}); + } else + ports.push({activity: outbound, port: outPort.name}); + } + } + + return ports; +} diff --git a/src/designer/elsa-workflows-designer/src/components/activities/flowchart/flowchart.scss b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/flowchart.scss new file mode 100644 index 000000000..503f5eac1 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/flowchart.scss @@ -0,0 +1,25 @@ +.x6-graph-scroller { + &.x6-graph-scroller-pannable[data-panning='false'] { + cursor: default; + } +} + +.x6-widget-selection-box { + box-sizing: content-box !important; + margin-top: -4px; + margin-left: -4px; + padding-right: 4px; + padding-bottom: 4px; + border: 2px dashed #34D399; + box-shadow: none; +} + +.x6-widget-selection-inner { + box-sizing: content-box !important; + margin-top: -8px; + margin-left: -8px; + padding-right: 12px; + padding-bottom: 12px; + border: 2px solid #34D399; + box-shadow: none; +} diff --git a/src/designer/elsa-workflows-designer/src/components/activities/flowchart/flowchart.tsx b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/flowchart.tsx new file mode 100644 index 000000000..27f2b123c --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/flowchart.tsx @@ -0,0 +1,324 @@ +import {Component, Element, Event, EventEmitter, h, Method, Prop, Watch} from '@stencil/core'; +import {Edge, Graph, Model, Node, NodeView} from '@antv/x6'; +import {v4 as uuid} from 'uuid'; +import {camelCase, first} from 'lodash'; +import './shapes'; +import './ports'; +import {ContainerActivityComponent} from '../container-activity-component'; +import {AddActivityArgs} from '../../designer/canvas/canvas'; +import {Activity, ActivityDescriptor, ActivitySelectedArgs, ContainerSelectedArgs, GraphUpdatedArgs} from '../../../models'; +import {createGraph} from './graph-factory'; +import {createNode} from './node-factory'; +import {Connection, Flowchart} from './models'; +import WorkflowEditorTunnel from '../../designer/state'; +import {ActivityNode, flattenList, walkActivities} from "./activity-walker"; +import PositionEventArgs = NodeView.PositionEventArgs; +import FromJSONData = Model.FromJSONData; + +@Component({ + tag: 'elsa-flowchart', + styleUrl: 'flowchart.scss', +}) +export class FlowchartComponent implements ContainerActivityComponent { + private rootId: string = uuid(); + + @Prop({mutable: true}) public activityDescriptors: Array = []; + @Prop({mutable: true}) public root?: Activity; + @Prop() public interactiveMode: boolean = true; + + @Element() el: HTMLElement; + container: HTMLElement; + graph: Graph; + target: Node; + + @Event() activitySelected: EventEmitter; + @Event() containerSelected: EventEmitter; + @Event() graphUpdated: EventEmitter; + + @Method() + public async updateLayout(): Promise { + const width = this.el.clientWidth; + const height = this.el.clientHeight; + this.graph.resize(width, height); + this.graph.updateBackground(); + } + + @Method() + public async addActivity(args: AddActivityArgs): Promise { + const graph = this.graph; + const {descriptor, x, y} = args; + + const activity: Activity = { + id: uuid(), + nodeType: descriptor.nodeType, + metadata: { + designer: { + position: { + x, + y + } + } + }, + }; + + const node = createNode(descriptor, activity, x, y); + graph.addNode(node); + } + + @Method() + public async exportRoot(): Promise { + return this.exportRootInternal(); + } + + @Method() + public async importRoot(root: Activity): Promise { + return this.importRootInternal(root); + } + + public async componentDidLoad() { + await this.createAndInitializeGraph(); + } + + public render() { + return ( +
this.container = el}/> + ); + } + + private createAndInitializeGraph = async () => { + const graph = this.graph = createGraph(this.container, { + nodeMovable: () => this.interactiveMode, + edgeMovable: () => this.interactiveMode, + arrowheadMovable: () => this.interactiveMode, + edgeLabelMovable: () => this.interactiveMode, + magnetConnectable: () => this.interactiveMode, + useEdgeTools: () => this.interactiveMode, + toolsAddable: () => this.interactiveMode, + stopDelegateOnDragging: () => this.interactiveMode, + vertexAddable: () => this.interactiveMode, + vertexDeletable: () => this.interactiveMode, + vertexMovable: () => this.interactiveMode, + }); + + graph.on('blank:click', this.onGraphClick); + graph.on('node:click', this.onNodeClick); + graph.on('edge:connected', this.onEdgeConnected); + graph.on('node:moved', this.onNodeMoved); + + graph.on('node:change:*', this.onGraphChanged); + graph.on('node:added', this.onGraphChanged); + graph.on('node:removed', this.onGraphChanged); + graph.on('edge:added', this.onGraphChanged); + graph.on('edge:removed', this.onGraphChanged); + graph.on('edge:connected', this.onGraphChanged); + + await this.updateLayout(); + } + + private exportRootInternal = (): Activity => { + const graph = this.graph; + const graphModel = graph.toJSON(); + const activities = graphModel.cells.filter(x => x.shape == 'activity').map(x => x.data as Activity); + const connections = graphModel.cells.filter(x => x.shape == 'elsa-edge' && !!x.data).map(x => x.data as Connection); + const remainingConnections: Array = []; // The connections remaining after transposition. + let remainingActivities: Array = [...activities]; // The activities remaining after transposition. + const activityDescriptors = this.activityDescriptors; + + // Transpose connections to activity outbound port properties. + for (const connection of connections) { + const source = activities.find(x => x.id == connection.source); + const target = activities.find(x => x.id == connection.target); + const sourceDescriptor = activityDescriptors.find(x => x.nodeType == source.nodeType); + const matchingTargetPort = sourceDescriptor.outPorts.find(x => x.name == connection.sourcePort); + + if (!!matchingTargetPort) { + // Assign the target activity directly to the outbound port of the source activity. + const outPortPropName = camelCase(connection.sourcePort); + source[outPortPropName] = target; + + // Remove the target activity from the list. + remainingActivities = remainingActivities.filter(x => x != target); + } else { + // Keep this connection. + remainingConnections.push(connection); + } + } + + return { + nodeType: 'Workflows.Flowchart', + activities: remainingActivities, + connections: remainingConnections, + id: this.rootId, + start: first(activities)?.id, + metadata: {}, + variables: [] + } as Flowchart; + } + + private importRootInternal = async (root: Activity) => { + this.rootId = root.id; + const descriptors = this.activityDescriptors; + const flowchart = root as Flowchart; + const flowchartGraph = walkActivities(root, this.activityDescriptors); + const flowchartNodes = flattenList(flowchartGraph.children); + const nodes: Array = []; + let edges: Array = []; + + // Create an X6 node for each activity. + for (const activityNode of flowchartNodes) { + const activity = activityNode.activity; + const position = activity.metadata.designer?.position || {x: 100, y: 100}; + const {x, y} = position; + const descriptor = descriptors.find(x => x.nodeType == activity.nodeType) + const node = createNode(descriptor, activity, x, y); + + nodes.push(node); + + // Create X6 edges for each child activity. + const childEdges = this.createEdges(activityNode); + edges = [...childEdges]; + } + + // Create X6 edges for each connection in the flowchart. + for (const connection of flowchart.connections) { + const edge: Edge.Metadata = this.createEdge(connection); + edges.push(edge); + } + + const model: FromJSONData = {nodes, edges}; + + // Freeze then unfreeze prevents an error from occurring when importing JSON a second time (e.g. after loading a new workflow. + this.graph.freeze(); + this.graph.fromJSON(model, {silent: false}); + this.graph.unfreeze(); + }; + + private createEdges = (activityNode: ActivityNode): Array => { + let edges: Array = []; + + for (const childNode of activityNode.children) { + const edge = this.createEdge({ + source: activityNode.activity.id, + sourcePort: activityNode.port, + target: childNode.activity.id, + targetPort: childNode.port + }); + + edges.push(edge); + } + + return edges; + } + + private createEdge = (connection: Connection): Edge.Metadata => { + return { + shape: 'elsa-edge', + zIndex: -1, + data: connection, + source: connection.source, + target: connection.target, + sourcePort: connection.sourcePort, + targetPort: connection.targetPort + }; + } + + private syncEdgeData = (cachedActivityId: string, updatedActivity: Activity) => { + const graph = this.graph; + const edges = graph.model.getEdges().filter(x => x.shape == 'elsa-edge' && !!x.data); + + for (const edge of edges) { + const connection: Connection = edge.data; + + if (connection.target != cachedActivityId && connection.source != cachedActivityId) + continue; + + if (connection.target == cachedActivityId) + connection.target = updatedActivity.id; + + if (connection.source == cachedActivityId) + connection.source = updatedActivity.id; + + edge.data = connection; + } + }; + + @Watch('root') + private async onRootChange(value: Activity) { + await this.importRootInternal(value); + } + + @Watch('interactiveMode') + private async onInteractiveModeChange(value: boolean) { + const graph = this.graph; + + if (!value) { + graph.disableSelectionMovable(); + graph.disableKeyboard(); + } else { + graph.enableSelectionMovable(); + graph.enableKeyboard(); + } + } + + private onGraphClick = async (e: PositionEventArgs) => this.containerSelected.emit({}); + + private onNodeClick = async (e: PositionEventArgs) => { + const node = e.node; + const activity = node.data as Activity; + const activityId = activity.id; + + const args: ActivitySelectedArgs = { + activity: activity, + applyChanges: a => { + node.data = a; + + // If the ID of the activity changed, we need to update connection references (X6 stores deep copies of data). + if (a.id !== activityId) + this.syncEdgeData(activityId, a); + }, + deleteActivity: a => node.remove({deep: true}) + }; + + this.activitySelected.emit(args); + }; + + private onNodeMoved = (e: PositionEventArgs) => { + const {node, x, y} = e; + const activity = node.data as Activity; + + activity.metadata = { + ...activity.metadata, + designer: { + position: { + x, + y + } + } + } + } + + private onEdgeConnected = (e: { isNew: boolean, edge: Edge }) => { + const edge = e.edge; + const sourceNode = edge.getSourceNode(); + const targetNode = edge.getTargetNode(); + const sourceActivity = edge.getSourceNode().data as Activity; + const targetActivity = targetNode.data as Activity; + const sourcePort = sourceNode.getPort(edge.getSourcePortId()).id; + const targetPort = targetNode.getPort(edge.getTargetPortId()).id; + + edge.data = { + source: sourceActivity.id, + sourcePort: sourcePort, + target: targetActivity.id, + targetPort: targetPort + } as Connection; + } + + private onGraphChanged = async (e: any) => { + this.graphUpdated.emit({exportGraph: this.exportRootInternal}); + } +} + +WorkflowEditorTunnel.injectProps(FlowchartComponent, ['activityDescriptors']); diff --git a/src/designer/elsa-workflows-designer/src/components/activities/flowchart/graph-factory.ts b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/graph-factory.ts new file mode 100644 index 000000000..923a180d5 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/graph-factory.ts @@ -0,0 +1,215 @@ +import {CellView, Graph, Node, Shape} from '@antv/x6'; +import './ports'; + +export function createGraph(container: HTMLElement, interacting: CellView.Interacting): Graph { + const graph = new Graph({ + container: container, + interacting: interacting, + grid: { + type: 'mesh', + size: 20, + visible: true, + args: { + color: '#e0e0e0' + } + }, + height: 5000, + width: 5000, + async: true, + autoResize: true, + keyboard: { + enabled: true, + global: false, + }, + clipboard: { + enabled: true, + useLocalStorage: true, + }, + selecting: { + enabled: true, + showNodeSelectionBox: true, + rubberband: true, + }, + scroller: { + enabled: true, + pannable: true, + pageVisible: false, + pageBreak: false, + padding: 0, + modifiers: ['ctrl', 'meta'], + }, + connecting: { + allowBlank: false, + allowMulti: true, + allowLoop: true, + allowNode: true, + allowEdge: false, + allowPort: true, + highlight: true, + //router: 'manhattan', + router: { + name: 'metro', + args: { + startDirections: ['bottom'], + endDirections: ['top'], + }, + }, + connector: 'elsa-connector', + // connector: { + // name: 'rounded', + // args: { + // radius: 20 + // }, + // }, + snap: { + radius: 20, + }, + validateMagnet({magnet}) { + return magnet.getAttribute('port-group') !== 'in' + }, + validateConnection({sourceView, targetView, sourceMagnet, targetMagnet}) { + if (!sourceMagnet || sourceMagnet.getAttribute('port-group') === 'in') { + return false + } + + if (!targetMagnet || targetMagnet.getAttribute('port-group') !== 'in') { + return false + } + + const portId = targetMagnet.getAttribute('port')! + const node = targetView.cell as Node + const port = node.getPort(portId) + return !(port && port.connected); + + }, + createEdge() { + return graph.createEdge({ + shape: 'elsa-edge', + zIndex: -1, + }) + } + }, + onPortRendered(args) { + const selectors = args.contentSelectors + const container = selectors && selectors.foContent + if (container) { + const port = document.createElement('div'); + port.className = 'rounded-full border border-2 border-blue h-8 w-8'; + port.innerHTML = `

done

`; + (container as HTMLElement).append(port); + } + }, + mousewheel: { + enabled: true, + modifiers: ['ctrl', 'meta'], + }, + history: { + enabled: true, + beforeAddCommand: (e: string, args: any) => { + + if (args.key == 'tools') + return false; + + const supportedEvents = ['cell:added', 'cell:removed', 'cell:change:*']; + + return supportedEvents.indexOf(e) >= 0; + }, + }, + // Todo: + // minimap: { + // enabled: true, + // container: this.container, + // }, + //interacting: () => state.interactingMap, + }); + + graph.on('edge:mouseenter', ({edge}) => { + edge.addTools([ + 'source-arrowhead', + 'target-arrowhead', + { + name: 'button-remove', + args: { + distance: -30, + }, + }, + ]) + }); + + graph.on('edge:mouseleave', ({edge}) => { + edge.removeTools() + }); + + graph.bindKey(['meta+c', 'ctrl+c'], () => { + const cells = graph.getSelectedCells() + if (cells.length) { + graph.copy(cells) + } + return false + }); + + graph.bindKey(['meta+x', 'ctrl+x'], () => { + const cells = graph.getSelectedCells() + if (cells.length) { + graph.cut(cells) + } + return false + }); + + graph.bindKey(['meta+v', 'ctrl+v'], () => { + if (!graph.isClipboardEmpty()) { + const cells = graph.paste({offset: 32}) + graph.cleanSelection() + graph.select(cells) + } + return false + }); + + //undo redo + graph.bindKey(['meta+z', 'ctrl+z'], () => { + if (graph.history.canUndo()) { + graph.history.undo() + } + return false + }); + + graph.bindKey(['meta+y', 'ctrl+y'], () => { + if (graph.history.canRedo()) { + graph.history.redo() + } + return false + }); + + // select all; + graph.bindKey(['meta+a', 'ctrl+a'], () => { + const nodes = graph.getNodes() + if (nodes) { + graph.select(nodes) + } + }); + + //delete + graph.bindKey('del', () => { + const cells = graph.getSelectedCells() + if (cells.length) { + graph.removeCells(cells) + } + }); + + // zoom + graph.bindKey(['ctrl+1', 'meta+1'], () => { + const zoom = graph.zoom() + if (zoom < 1.5) { + graph.zoom(0.1) + } + }); + + graph.bindKey(['ctrl+2', 'meta+2'], () => { + const zoom = graph.zoom() + if (zoom > 0.5) { + graph.zoom(-0.1) + } + }); + + return graph; +}; diff --git a/src/designer/elsa-workflows-designer/src/components/activities/flowchart/models.ts b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/models.ts new file mode 100644 index 000000000..d41f97db3 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/models.ts @@ -0,0 +1,13 @@ +import {Container} from '../../../models'; + +export interface Flowchart extends Container { + start: string; + connections: Array; +} + +export interface Connection { + source: string; + target: string; + sourcePort: string; + targetPort: string; +} diff --git a/src/designer/elsa-workflows-designer/src/components/activities/flowchart/node-factory.ts b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/node-factory.ts new file mode 100644 index 000000000..5d20e9429 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/node-factory.ts @@ -0,0 +1,53 @@ +import {Graph, Node} from '@antv/x6'; +import {Activity, ActivityDescriptor, Port} from '../../../models'; + +export function createNode(activityDescriptor: ActivityDescriptor, activity: Activity, x: number, y: number): Node.Metadata { + + let inPorts: Array = [...activityDescriptor.inPorts]; + let outPorts: Array = [...activityDescriptor.outPorts]; + + if (inPorts.length == 0) + inPorts = [{name: 'In', displayName: 'In'}]; + + if (inPorts.length == 1) + inPorts[0].displayName = null; + + if (outPorts.length == 0) + outPorts = [{name: 'Done', displayName: 'Done'}]; + + if (outPorts.length == 1) + outPorts[0].displayName = null; + + const inPortModels = inPorts.map(x => ({ + id: x.name, + group: 'in', + attrs: !!x.displayName ? { + text: { + text: x.displayName + } + } : null + })); + + const outPortModels = outPorts.map(x => ({ + id: x.name, + group: 'out', + attrs: { + text: { + text: x.displayName + } + } + })); + + const portModels = [...inPortModels, ...outPortModels]; + + return { + id: activity.id, + shape: 'activity', + activity: activity, + activityDescriptor: activityDescriptor, + x: x, + y: y, + data: activity, + ports: portModels + } as Node.Metadata; +} diff --git a/src/designer/elsa-workflows-designer/src/components/activities/flowchart/ports.tsx b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/ports.tsx new file mode 100644 index 000000000..408599f33 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/ports.tsx @@ -0,0 +1,68 @@ +import {Graph, Line, Path} from "@antv/x6"; +import {toResult} from "@antv/x6/lib/registry/port-layout/util"; + +Graph.registerPortLayout('dynamicOut', (portsPositionArgs, elemBBox) => { + return portsPositionArgs.map((_, index) => { + + const portCount = portsPositionArgs.length; + const ratio = (index + 0.5) / portCount; + const p1 = portCount <= 3 ? elemBBox.getTopRight() : elemBBox.getBottomLeft(); + const p2 = portCount <= 3 ? elemBBox.getBottomRight() : elemBBox.getBottomRight(); + const line = new Line(p1, p2) + const p = line.pointAt(ratio); + + return toResult(p.round(), 0, {}); + + }); +}); + +Graph.registerPortLayout('dynamicIn', (portsPositionArgs, elemBBox) => { + return portsPositionArgs.map((_, index) => { + + const portCount = portsPositionArgs.length; + const ratio = (index + 0.5) / portCount; + const p1 = portCount <= 3 ? elemBBox.getTopLeft() : elemBBox.getTopLeft(); + const p2 = portCount <= 3 ? elemBBox.getBottomLeft() : elemBBox.getTopRight(); + const line = new Line(p1, p2) + const p = line.pointAt(ratio); + + return toResult(p.round(), 0, {}); + + }); +}); + +Graph.registerConnector( + 'elsa-connector', + (s, e) => { + const offset = 0; + const deltaY = Math.abs(e.y - s.y) + const control = Math.floor((deltaY / 3) * 2) + + const v1 = {x: s.x, y: s.y + offset + control} + const v2 = {x: e.x, y: e.y - offset - control} + + return Path.normalize( + `M ${s.x} ${s.y} + L ${s.x} ${s.y + offset} + C ${v1.x} ${v1.y} ${v2.x} ${v2.y} ${e.x} ${e.y - offset} + L ${e.x} ${e.y} + `, + ) + }, + true, +); + +Graph.registerEdge( + 'elsa-edge', + { + inherit: 'edge', + attrs: { + line: { + stroke: '#C2C8D5', + strokeWidth: 1, + targetMarker: null, + }, + }, + }, + true, +) diff --git a/src/designer/elsa-workflows-designer/src/components/activities/flowchart/shapes.ts b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/shapes.ts new file mode 100644 index 000000000..333a1c843 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/shapes.ts @@ -0,0 +1,138 @@ +import {Cell, Graph, Shape} from '@antv/x6'; +import {Container} from 'typedi'; +import {Activity, ActivityDescriptor} from '../../../models'; +import {ActivityDriverRegistry, ActivityDisplayContext} from '../../../services'; + +export class ActivityNode extends Shape.HTML { + get text() { + return this.store.get('text'); + } + + set text(value: string) { + this.store.set('text', value); + } + + get activity() { + return this.store.get('activity'); + } + + set activity(value: Activity) { + this.store.set('activity', value); + } + + get activityDescriptor() { + return this.store.get('activityDescriptor'); + } + + set activityDescriptor(value: ActivityDescriptor) { + this.store.set('activityDescriptor', value); + } + + init() { + super.init(); + this.updateSize(); + } + + setup() { + const self = this; + super.setup(); + this.on('change:text', this.updateSize, this); + + this.html = { + render() { + + return self.createHtml(); + + }, + shouldComponentUpdate(node: Cell) { + return node.hasChanged('text'); + }, + }; + } + + updateSize() { + const activityDescriptor = this.activityDescriptor as ActivityDescriptor; + + if (!activityDescriptor) + return; + + const wrapper = document.createElement('div'); + wrapper.className = 'w-full flex items-center pl-10 pr-2 py-2'; + wrapper.innerHTML = this.createHtml(); + document.body.append(wrapper); + const rect = wrapper.firstElementChild.getBoundingClientRect(); + wrapper.remove(); + + const width = rect.width; + const height = rect.height; + this.prop({size: {width, height}}); + } + + createHtml() { + const activityDescriptor = this.activityDescriptor as ActivityDescriptor; + const activity = this.activity as Activity; + const activityType = activityDescriptor.nodeType; + const driverRegistry = Container.get(ActivityDriverRegistry); + const driver = driverRegistry.createDriver(activityType); + + const displayContext: ActivityDisplayContext = { + activity: activity, + activityDescriptor: activityDescriptor, + displayType: "designer" + }; + + return driver.display(displayContext); + } +} + +ActivityNode.config({ + //portMarkup: [Markup.getForeignObjectMarkup()], + ports: { + groups: { + in: { + position: 'dynamicIn', + attrs: { + circle: { + r: 6, + magnet: true, + stroke: '#3c82f6', + strokeWidth: 2, + fill: '#fff', + }, + text: { + fontSize: 12, + fill: '#888', + }, + }, + label: { + position: { + name: 'outside', + }, + }, + }, + out: { + position: 'dynamicOut', + attrs: { + circle: { + r: 6, + magnet: true, + stroke: '#fff', + strokeWidth: 2, + fill: '#3c82f6', + }, + text: { + fontSize: 12, + fill: '#888', + }, + }, + label: { + position: { + name: 'outside', + }, + }, + }, + }, + }, +}); + +Graph.registerNode('activity', ActivityNode, true); diff --git a/src/designer/elsa-workflows-designer/src/components/designer/activity-properties-editor/activity-properties-editor.tsx b/src/designer/elsa-workflows-designer/src/components/designer/activity-properties-editor/activity-properties-editor.tsx new file mode 100644 index 000000000..57f8ca4b8 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/designer/activity-properties-editor/activity-properties-editor.tsx @@ -0,0 +1,160 @@ +import {Component, Event, EventEmitter, h, Method, Prop, State} from '@stencil/core'; +import {camelCase} from 'lodash'; +import WorkflowEditorTunnel from '../state'; +import { + Activity, + ActivityDescriptor, + DefaultActions, InputDescriptor, + RenderNodePropContext, + RenderNodePropsContext, + TabChangedArgs, + TabDefinition +} from '../../../models'; +import {InputDriverRegistry} from "../../../services/input-driver-registry"; +import {Container} from "typedi"; +import {NodeInputContext} from "../../../services/node-input-driver"; +import {FormEntry} from "../../shared/forms/form-entry"; + +export interface ActivityUpdatedArgs { + activity: Activity; +} + +export interface DeleteActivityRequestedArgs { + activity: Activity; +} + +@Component({ + tag: 'elsa-activity-properties-editor', +}) +export class ActivityPropertiesEditor { + private slideOverPanel: HTMLElsaSlideOverPanelElement; + private renderContext: RenderNodePropsContext; + private readonly inputDriverRegistry: InputDriverRegistry; + + constructor() { + this.inputDriverRegistry = Container.get(InputDriverRegistry); + } + + @Prop({mutable: true}) public activityDescriptors: Array = []; + @Prop({mutable: true}) public activity?: Activity; + + @Event() public activityUpdated: EventEmitter; + @Event() public deleteActivityRequested: EventEmitter; + @State() private selectedTabIndex: number = 0; + + @Method() + public async show(): Promise { + await this.slideOverPanel.show(); + } + + @Method() + public async hide(): Promise { + await this.slideOverPanel.hide(); + } + + public componentWillRender() { + const activity = this.activity; + const activityDescriptor = this.findActivityDescriptor(); + const title = activityDescriptor?.displayName ?? activityDescriptor?.nodeType ?? 'Unknown Activity'; + const driverRegistry = this.inputDriverRegistry; + + const renderPropertyContexts: Array = activityDescriptor.inputProperties.map(inputDescriptor => { + const renderInputContext: NodeInputContext = { + node: activity, + nodeDescriptor: activityDescriptor, + inputDescriptor, + inputChanged: (v, s) => this.onPropertyEditorChanged(inputDescriptor, v, s) + }; + + const driver = driverRegistry.get(renderInputContext); + const control = driver?.renderInput(renderInputContext); + + return { + inputContext: renderInputContext, + inputControl: control, + } + }); + + this.renderContext = { + node: activity, + nodeDescriptor: activityDescriptor, + title, + properties: renderPropertyContexts + } + } + + public render() { + const {nodeDescriptor, title} = this.renderContext; + + const propertiesTab: TabDefinition = { + displayText: 'Properties', + content: () => this.renderPropertiesTab() + }; + + const commonTab: TabDefinition = { + displayText: 'Common', + content: () => this.renderCommonTab() + }; + + const tabs = !!nodeDescriptor ? [propertiesTab, commonTab] : []; + const actions = [DefaultActions.Delete(this.onDeleteActivity)]; + + return ( + this.onSelectedTabIndexChanged(e)} + actions={actions}/> + ); + } + + private findActivityDescriptor = (): ActivityDescriptor => !!this.activity ? this.activityDescriptors.find(x => x.nodeType == this.activity.nodeType) : null; + private onSelectedTabIndexChanged = (e: CustomEvent) => this.selectedTabIndex = e.detail.selectedTabIndex + + private onActivityIdChanged = (e: any) => { + const activity = this.activity; + const inputElement = e.target as HTMLInputElement; + + activity.id = inputElement.value; + this.activityUpdated.emit({activity: activity}); + } + + private onPropertyEditorChanged = (inputDescriptor: InputDescriptor, propertyValue: any, syntax: string) => { + const activity = this.activity; + const propertyName = inputDescriptor.name; + const camelCasePropertyName = camelCase(propertyName); + + activity[camelCasePropertyName] = { + type: inputDescriptor.type, + expression: { + type: syntax, + value: propertyValue + } + }; + + this.activityUpdated.emit({activity: activity}); + } + + private onDeleteActivity = () => this.deleteActivityRequested.emit({activity: this.activity}); + + private renderPropertiesTab = () => { + const {node, properties} = this.renderContext; + const activityId = node.id; + + return
+ + this.onActivityIdChanged(e)}/> + + + {properties.filter(x => !!x.inputControl).map(propertyContext => { + return propertyContext.inputControl; + })} +
+ }; + + private renderCommonTab = () => { + return
+
+ }; +} + +WorkflowEditorTunnel.injectProps(ActivityPropertiesEditor, ['activityDescriptors']); diff --git a/src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.scss b/src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.scss new file mode 100644 index 000000000..503f5eac1 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.scss @@ -0,0 +1,25 @@ +.x6-graph-scroller { + &.x6-graph-scroller-pannable[data-panning='false'] { + cursor: default; + } +} + +.x6-widget-selection-box { + box-sizing: content-box !important; + margin-top: -4px; + margin-left: -4px; + padding-right: 4px; + padding-bottom: 4px; + border: 2px dashed #34D399; + box-shadow: none; +} + +.x6-widget-selection-inner { + box-sizing: content-box !important; + margin-top: -8px; + margin-left: -8px; + padding-right: 12px; + padding-bottom: 12px; + border: 2px solid #34D399; + box-shadow: none; +} diff --git a/src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.tsx b/src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.tsx new file mode 100644 index 000000000..c72ac3a2e --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.tsx @@ -0,0 +1,48 @@ +import {Component, h, Method, Prop} from '@stencil/core'; +import {ContainerActivityComponent} from '../../activities/container-activity-component'; +import {Activity, ActivityDescriptor} from '../../../models'; + +export interface AddActivityArgs { + descriptor: ActivityDescriptor; + x?: number; + y?: number; +} + +@Component({ + tag: 'elsa-canvas', + styleUrl: 'canvas.scss', +}) +export class Canvas { + + private root: ContainerActivityComponent; + + @Prop() public interactiveMode: boolean = true; + + @Method() + public async addActivity(args: AddActivityArgs): Promise { + await this.root.addActivity(args); + } + + @Method() + public async updateLayout(): Promise { + await this.root.updateLayout(); + } + + @Method() + public async exportGraph(): Promise { + return await this.root.exportRoot(); + } + + @Method() + public async importGraph(root: Activity): Promise { + return await this.root.importRoot(root); + } + + render() { + return ( +
+ this.root = el} interactiveMode={this.interactiveMode} class="absolute left-0 top-0 right-0 bottom-0"/> +
+ ); + } +} diff --git a/src/designer/elsa-workflows-designer/src/components/designer/form-panel/form-panel.tsx b/src/designer/elsa-workflows-designer/src/components/designer/form-panel/form-panel.tsx new file mode 100644 index 000000000..bc722a759 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/designer/form-panel/form-panel.tsx @@ -0,0 +1,114 @@ +import {Component, Event, EventEmitter, h, Prop} from '@stencil/core'; +import {ActionDefinition, ActionInvokedArgs, ActionType, TabChangedArgs, TabDefinition} from '../../../models'; + +@Component({ + tag: 'elsa-form-panel' +}) +export class FormPanel { + private formElement: HTMLFormElement; + + @Prop() public headerText: string; + @Prop() public tabs: Array = []; + @Prop({mutable: true}) public selectedTabIndex?: number; + @Prop() public actions: Array = []; + + @Event() public submitted: EventEmitter; + @Event() public selectedTabIndexChanged: EventEmitter; + @Event() public actionInvoked: EventEmitter; + + public render() { + return this.renderPanel(); + } + + private onTabClick(e: Event, tab: TabDefinition) { + e.preventDefault(); + const selectedTabIndex = this.selectedTabIndex = this.tabs.findIndex(x => tab == x); + this.selectedTabIndexChanged.emit({selectedTabIndex: selectedTabIndex}); + } + + private onSubmit(e: Event) { + e.preventDefault(); + const formData = new FormData(e.target as HTMLFormElement); + this.submitted.emit(formData); + } + + private renderPanel() { + const tabs = this.tabs; + const selectedTabIndex = this.selectedTabIndex; + const actions = this.actions; + + return ( +
+
this.onSubmit(e)} + ref={el => this.formElement = el} method="post"> +
+ +
+
+
+

+ {this.headerText} +

+
+
+
+ +
+ +
+ +
+ +
+ {tabs.map((tab, tabIndex) => { + const cssClass = tabIndex == selectedTabIndex ? '' : 'hidden'; + return
+ {tab.content()} +
+ })} +
+
+
+ +
+
+ {actions.map(action => { + + if (action.display) + return action.display(action); + + const cssClass = action.isPrimary + ? 'text-white bg-blue-600 hover:bg-blue-700 border-transparent focus:ring-blue-500' + : action.isDangerous ? 'text-white bg-red-600 hover:bg-red-700 border-transparent focus:ring-red-500' + : 'bg-white border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-blue-500'; + + const buttonType = action.type == ActionType.Submit ? 'submit' : 'button'; + + const cancelHandler = () => { + }; + + const defaultHandler = (e: any, action: ActionDefinition) => this.actionInvoked.emit({action: action}); + const clickHandler = !!action.onClick ? action.onClick : action.type == ActionType.Cancel ? cancelHandler : defaultHandler; + + return + })} +
+
+
+
+ ); + } +} diff --git a/src/designer/elsa-workflows-designer/src/components/designer/panel/models.ts b/src/designer/elsa-workflows-designer/src/components/designer/panel/models.ts new file mode 100644 index 000000000..912ba24f1 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/designer/panel/models.ts @@ -0,0 +1,10 @@ +export interface PanelStateChangedArgs { + expanded: boolean; +} + +export enum PanelPosition { + Left, + Top, + Right, + Bottom +} diff --git a/src/designer/elsa-workflows-designer/src/components/designer/panel/panel.scss b/src/designer/elsa-workflows-designer/src/components/designer/panel/panel.scss new file mode 100644 index 000000000..294867193 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/designer/panel/panel.scss @@ -0,0 +1,19 @@ +.panel-state-closed { + + .panel { + + &panel-position-top { + width: 0; + } + + &panel-position-left { + height: 0; + } + } + + .panel-content-container { + left: 0; + top: 0; + display: none; + } +} diff --git a/src/designer/elsa-workflows-designer/src/components/designer/panel/panel.tsx b/src/designer/elsa-workflows-designer/src/components/designer/panel/panel.tsx new file mode 100644 index 000000000..49743824b --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/designer/panel/panel.tsx @@ -0,0 +1,55 @@ +import {Component, Event, EventEmitter, h, Host, Prop, State} from '@stencil/core'; +import {PanelPosition, PanelStateChangedArgs} from './models'; + +@Component({ + tag: 'elsa-panel', + styleUrl: 'panel.scss', +}) +export class Panel { + @Prop() position: PanelPosition = PanelPosition.Left; + @Event() expandedStateChanged: EventEmitter; + @State() isExpanded: boolean = true; + + private onToggleClick = () => { + this.isExpanded = !this.isExpanded; + this.expandedStateChanged.emit({expanded: this.isExpanded}); + }; + + render() { + const isExpanded = this.isExpanded; + const position = this.position; + const stateClass = isExpanded ? 'panel-state-expanded' : 'panel-state-collapsed'; + + const containerClassMap = []; + containerClassMap[PanelPosition.Left] = 'panel-position-left left-0 top-0 bottom-0 border-r'; + containerClassMap[PanelPosition.Top] = 'panel-position-top left-0 top-0 right-0 border-b'; + containerClassMap[PanelPosition.Right] = 'panel-position-right right-0 top-0 bottom-0 border-l'; + const containerCssClass = containerClassMap[position]; + + const toggleClassMap = {}; + toggleClassMap[PanelPosition.Left] = 'panel-toggle-left'; + toggleClassMap[PanelPosition.Top] = 'panel-toggle-top'; + toggleClassMap[PanelPosition.Right] = 'panel-toggle-right'; + toggleClassMap[PanelPosition.Bottom] = 'panel-toggle-bottom'; + + const toggleCssClass = toggleClassMap[position]; + + return ( + + +
+ +
+ +
this.onToggleClick()}> + + + + +
+
+ ); + } +} diff --git a/src/designer/elsa-workflows-designer/src/components/designer/slide-over-panel/slide-over-panel.tsx b/src/designer/elsa-workflows-designer/src/components/designer/slide-over-panel/slide-over-panel.tsx new file mode 100644 index 000000000..a05e8b8c5 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/designer/slide-over-panel/slide-over-panel.tsx @@ -0,0 +1,194 @@ +import {Component, Event, EventEmitter, h, Method, Prop, State, Watch} from '@stencil/core'; +import {ActionDefinition, ActionType, TabDefinition} from '../../../models'; + +@Component({ + tag: 'elsa-slide-over-panel' +}) +export class SlideOverPanel { + private overlayElement: HTMLElement; + private formElement: HTMLFormElement; + + @Prop() public headerText: string; + @Prop() public tabs: Array = []; + @Prop({mutable: true}) public selectedTab?: TabDefinition; + @Prop() public actions: Array = []; + @Prop() public expand: boolean; + + @Event() public collapsed: EventEmitter; + @Event() public submitted: EventEmitter; + + + @Method() + public async show(): Promise { + this.isShowing = true; + this.isHiding = false; + this.isVisible = true; + } + + @Method() + public async hide(): Promise { + this.isHiding = true; + this.isShowing = false; + } + + @State() public isHiding: boolean = false; + @State() public isShowing: boolean = false; + @State() public isVisible: boolean = false; + + @Watch('expand') + private handleExpanded(value: boolean) { + this.isShowing = value; + this.isHiding = !value; + + if (value) + this.isVisible = true; + } + + public render() { + return this.renderPanel(); + } + + private onCloseClick = async () => { + await this.hide(); + }; + + private onOverlayClick = async (e: MouseEvent) => { + if (e.target != this.overlayElement) + return; + + // Hide panel. + await this.hide(); + + // Raise Form Submitted event to apply changes. + const formData = new FormData(this.formElement); + + this.submitted.emit(formData); + }; + + private onTransitionEnd = (e: TransitionEvent) => { + if (this.isHiding) { + this.isVisible = false; + this.isHiding = false; + this.collapsed.emit(); + } + }; + + private onTabClick(e: Event, tab: TabDefinition) { + e.preventDefault(); + this.selectedTab = tab; + } + + private onSubmit(e: Event) { + e.preventDefault(); + const formData = new FormData(e.target as HTMLFormElement); + this.submitted.emit(formData); + } + + private renderPanel() { + const isVisible = this.isVisible; + const isHiding = this.isHiding; + const wrapperClass = isVisible ? 'block' : 'hidden'; + const backdropClass = !isHiding && isVisible ? 'opacity-50' : 'opacity-0'; + const panelClass = !isHiding && isVisible ? 'max-w-2xl w-2xl' : 'max-w-0 w-0'; + const tabs = this.tabs; + const selectedTab = this.selectedTab; + const actions = this.actions; + + return ( +