From 8420bd6acfd577a2670b51bb7cc5c28b5dff5b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Tue, 6 Dec 2022 12:39:17 +0100 Subject: [PATCH 01/34] removed not used interface elements --- src/WorkflowEngine.Core/IOutputsRepository.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/WorkflowEngine.Core/IOutputsRepository.cs b/src/WorkflowEngine.Core/IOutputsRepository.cs index 4afcd1f..4d8aeb4 100644 --- a/src/WorkflowEngine.Core/IOutputsRepository.cs +++ b/src/WorkflowEngine.Core/IOutputsRepository.cs @@ -10,10 +10,6 @@ public interface IOutputsRepository ValueTask GetTriggerData(Guid id); ValueTask AddInput(IRunContext context, IWorkflow workflow, IAction action); ValueTask GetOutputData(Guid id, string v); - ValueTask AddArrayItemAsync(IRunContext run, IWorkflow workflow, string key, IActionResult result); - ValueTask AddArrayInput(IRunContext context, IWorkflow workflow, IAction action); - ValueTask StartScope(IRunContext context, IWorkflow workflow, IAction action); - ValueTask AddScopeItem(IRunContext context, IWorkflow workflow, IAction action, IActionResult result); ValueTask EndScope(IRunContext run, IWorkflow workflow, IAction action); } From 2a31c7bccfaa2bd261536903afce5099ea0a1fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Tue, 6 Dec 2022 12:52:30 +0100 Subject: [PATCH 02/34] fix: updated deps --- apps/WorkflowEngine.DemoApp/WorkflowEngine.DemoApp.csproj | 4 ++-- src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/WorkflowEngine.DemoApp/WorkflowEngine.DemoApp.csproj b/apps/WorkflowEngine.DemoApp/WorkflowEngine.DemoApp.csproj index 6db342b..c1650e9 100644 --- a/apps/WorkflowEngine.DemoApp/WorkflowEngine.DemoApp.csproj +++ b/apps/WorkflowEngine.DemoApp/WorkflowEngine.DemoApp.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -7,7 +7,7 @@ - + diff --git a/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj b/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj index 49c3498..239f5ad 100644 --- a/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj +++ b/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1;net6.0 @@ -11,7 +11,7 @@ - + From 79c6e61b0f4db4744417ee54cd0c8ce00ea42e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Sat, 14 Jan 2023 07:15:54 +0100 Subject: [PATCH 03/34] fix: use scopedactionmetadata to control if a scope should be used to find next action --- src/WorkflowEngine.Core/WorkflowActions.cs | 1 + src/WorkflowEngine.Core/WorkflowExecutor.cs | 7 ++----- .../HangfireWorkflowExecutor.cs | 17 ++++++----------- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/WorkflowEngine.Core/WorkflowActions.cs b/src/WorkflowEngine.Core/WorkflowActions.cs index 7a551b4..56a1e60 100644 --- a/src/WorkflowEngine.Core/WorkflowActions.cs +++ b/src/WorkflowEngine.Core/WorkflowActions.cs @@ -14,6 +14,7 @@ public class ForLoopActionMetadata : ActionMetadata, IScopedActionMetadata public interface IScopedActionMetadata { WorkflowActions Actions { get; set; } + string Type { get; set; } } public class WorkflowActions : Dictionary { diff --git a/src/WorkflowEngine.Core/WorkflowExecutor.cs b/src/WorkflowEngine.Core/WorkflowExecutor.cs index 04e957a..80527ee 100644 --- a/src/WorkflowEngine.Core/WorkflowExecutor.cs +++ b/src/WorkflowEngine.Core/WorkflowExecutor.cs @@ -28,12 +28,9 @@ public WorkflowExecutor(ILogger logger, IOutputsRepository ou public ValueTask GetNextAction(IRunContext context, IWorkflow workflow, IActionResult priorResult) { logger.LogInformation("Finding Next Action for {WorkflowId} and prior {@result} ", workflow.Id, priorResult); - //var action = workflow.Manifest.Actions.Single(c => c.Key == priorResult.Key); - - + var next = workflow.Manifest.Actions.FindNextAction(priorResult.Key); - //var parent = workflow.Manifest.Actions.FindParentAction(priorResult.Key) is ForLoopActionMetadata; - + if (next.IsDefault()) return new ValueTask(); diff --git a/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs b/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs index f7c7101..5e49760 100644 --- a/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs +++ b/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs @@ -98,17 +98,12 @@ public async ValueTask ExecuteAsync(IRunContext run, IWorkflow workflow, { var result = await actionExecutor.ExecuteAsync(run, workflow, action); - - - - + if (result != null) - { - + { var next = await executor.GetNextAction(run, workflow, result); - - + await hangfireActionExecutorResultHandler.InspectAsync(run, workflow, result, next); if (next != null) @@ -116,13 +111,13 @@ public async ValueTask ExecuteAsync(IRunContext run, IWorkflow workflow, var a = backgroundJobClient.Enqueue( (executor) => executor.ExecuteAsync(run, workflow, next, null)); } - else if (workflow.Manifest.Actions.FindParentAction(action.Key) is ForLoopActionMetadata scope) + else if (workflow.Manifest.Actions.FindParentAction(action.Key) is IScopedActionMetadata scope) { var scopeaction = run.CopyTo(new Action { ScopeMoveNext = true, Type = scope.Type, Key = action.Key.Substring(0, action.Key.LastIndexOf('.')), ScheduledTime = DateTimeOffset.UtcNow }); - - var a = backgroundJobClient.Enqueue( + + var a = backgroundJobClient.ContinueJobWith(context.BackgroundJob.Id, (executor) => executor.ExecuteAsync(run, workflow, scopeaction, null)); //await outputRepository.EndScope(run, workflow, action); From 147fb3a2812ccac3e696dc62a0ff07eed9ab2f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Sat, 14 Jan 2023 07:50:33 +0100 Subject: [PATCH 04/34] fix: used node 18 for build --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84d969f..d8f0426 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v1 with: - node-version: 16 + node-version: 18 - name: Add plugin for conventional commits run: npm install conventional-changelog-conventionalcommits From 13e2bd4c34bd5862b1dd71559d2a1f2f3fdccefa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Sat, 14 Jan 2023 13:03:16 +0100 Subject: [PATCH 05/34] fix: added git forwindows in pipelines --- .github/workflows/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d8f0426..d2a0a2b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,10 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v2 - + + - name: Setup Git for Windows' minimal SDK + uses: git-for-windows/setup-git-for-windows-sdk@v1 + - uses: actions/setup-dotnet@v1 with: dotnet-version: '6.0.x' From 7be4e888f84587666d6c2842ea3e263a6699fafc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Sat, 14 Jan 2023 13:43:40 +0100 Subject: [PATCH 06/34] test path --- .github/workflows/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d2a0a2b..c940a5d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,6 +37,9 @@ jobs: - name: Setup Git for Windows' minimal SDK uses: git-for-windows/setup-git-for-windows-sdk@v1 + - name: Print GIT verison + run: git --version + - uses: actions/setup-dotnet@v1 with: dotnet-version: '6.0.x' @@ -65,7 +68,7 @@ jobs: - name: Print release verison run: echo ${env:RELEASE_VERSION} - + - name: Cleaning run: dotnet clean From c295d6e190041bb7463758333c9972d9c29f9ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Sat, 14 Jan 2023 14:00:25 +0100 Subject: [PATCH 07/34] old version? --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c940a5d..7c304d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,7 +64,7 @@ jobs: GIT_AUTHOR_NAME: thygesteffensen;pksorensen GIT_AUTHOR_EMAIL: 31892312+thygesteffensen@users.noreply.github.com run: | - echo "RELEASE_VERSION=$((npx semantic-release --dry-run).Where({ $_ -like '*Release note*' }) | Out-String | Select-String '[0-9]+\.[0-9]+\.[0-9]+([-][a-zA-z]+[.][0-9]*)?' | % { $_.Matches } | % { $_.Value })" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + echo "RELEASE_VERSION=$((npx semantic-release@19.0.5 --dry-run).Where({ $_ -like '*Release note*' }) | Out-String | Select-String '[0-9]+\.[0-9]+\.[0-9]+([-][a-zA-z]+[.][0-9]*)?' | % { $_.Matches } | % { $_.Value })" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Print release verison run: echo ${env:RELEASE_VERSION} From 3443315e48dead1aaba57af293c9bbf110d48218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Sat, 14 Jan 2023 14:09:10 +0100 Subject: [PATCH 08/34] fix: using old version --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7c304d6..63dd516 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,4 +86,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GIT_AUTHOR_NAME: thygesteffensen;pksorensen GIT_AUTHOR_EMAIL: 31892312+thygesteffensen@users.noreply.github.com;poul@kjeldager.com - run: npx semantic-release \ No newline at end of file + run: npx semantic-release@19.0.5 \ No newline at end of file From 538d13f62d97bfa9a6a5dcb52f9956e3a3d271d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thyge=20Sk=C3=B8dt=20Steffensen?= <31892312+thygesteffensen@users.noreply.github.com> Date: Fri, 13 Oct 2023 17:34:05 +0200 Subject: [PATCH 09/34] feat: Add events to output repository Events are emitted for each action result and when the workflow is done. This can be used to: * Render workflow runs and their outputs * Detect when a workflow is done * Process mining * and more --- .../ActionCompletedEvent.cs | 13 +++++ .../DefaultOutputsRepository.cs | 14 ++--- src/WorkflowEngine.Core/Event.cs | 12 +++++ src/WorkflowEngine.Core/EventType.cs | 8 +++ src/WorkflowEngine.Core/IOutputsRepository.cs | 3 +- .../WorkflowFinishedEvent.cs | 9 ++++ .../HangfireWorkflowExecutor.cs | 51 ++++++++----------- 7 files changed, 73 insertions(+), 37 deletions(-) create mode 100644 src/WorkflowEngine.Core/ActionCompletedEvent.cs create mode 100644 src/WorkflowEngine.Core/Event.cs create mode 100644 src/WorkflowEngine.Core/EventType.cs create mode 100644 src/WorkflowEngine.Core/WorkflowFinishedEvent.cs diff --git a/src/WorkflowEngine.Core/ActionCompletedEvent.cs b/src/WorkflowEngine.Core/ActionCompletedEvent.cs new file mode 100644 index 0000000..6469fd3 --- /dev/null +++ b/src/WorkflowEngine.Core/ActionCompletedEvent.cs @@ -0,0 +1,13 @@ +namespace WorkflowEngine.Core +{ + public class ActionCompletedEvent : Event + { + public ActionCompletedEvent() : base(EventType.ActionCompleted) + { + } + + public string JobId { get; set; } + public string ActionKey { get; set; } + public string ResultPath { get; set; } + } +} diff --git a/src/WorkflowEngine.Core/DefaultOutputsRepository.cs b/src/WorkflowEngine.Core/DefaultOutputsRepository.cs index 48a7bd4..8b580e6 100644 --- a/src/WorkflowEngine.Core/DefaultOutputsRepository.cs +++ b/src/WorkflowEngine.Core/DefaultOutputsRepository.cs @@ -170,7 +170,14 @@ public ValueTask EndScope(IRunContext context, IWorkflow workflow, IAction actio return new ValueTask(); } - public ValueTask StartScope(IRunContext context, IWorkflow workflow, IAction action) + + public ValueTask AddEvent(IRunContext run, IWorkflow workflow, IAction action, Event @event) + { + // Defaults to nothing to not create noise and breaks backwards compatibility + return new ValueTask(); + } + + public ValueTask StartScope(IRunContext context, IWorkflow workflow, IAction action) { JToken run = GetOrCreateRun(context); @@ -197,10 +204,5 @@ public ValueTask AddInput(IRunContext context, IWorkflow workflow, IAction actio return new ValueTask(); } - - - } - - } diff --git a/src/WorkflowEngine.Core/Event.cs b/src/WorkflowEngine.Core/Event.cs new file mode 100644 index 0000000..14b3200 --- /dev/null +++ b/src/WorkflowEngine.Core/Event.cs @@ -0,0 +1,12 @@ +namespace WorkflowEngine.Core +{ + public abstract class Event + { + public EventType EventType { get; } + + protected Event(EventType eventType) + { + EventType = eventType; + } + } +} diff --git a/src/WorkflowEngine.Core/EventType.cs b/src/WorkflowEngine.Core/EventType.cs new file mode 100644 index 0000000..1aa15f9 --- /dev/null +++ b/src/WorkflowEngine.Core/EventType.cs @@ -0,0 +1,8 @@ +namespace WorkflowEngine.Core +{ + public enum EventType + { + WorkflowFinished = 0, + ActionCompleted = 1 + } +} diff --git a/src/WorkflowEngine.Core/IOutputsRepository.cs b/src/WorkflowEngine.Core/IOutputsRepository.cs index 4afcd1f..4b73456 100644 --- a/src/WorkflowEngine.Core/IOutputsRepository.cs +++ b/src/WorkflowEngine.Core/IOutputsRepository.cs @@ -15,7 +15,6 @@ public interface IOutputsRepository ValueTask StartScope(IRunContext context, IWorkflow workflow, IAction action); ValueTask AddScopeItem(IRunContext context, IWorkflow workflow, IAction action, IActionResult result); ValueTask EndScope(IRunContext run, IWorkflow workflow, IAction action); + ValueTask AddEvent(IRunContext run, IWorkflow workflow, IAction action, Event @event); } - - } diff --git a/src/WorkflowEngine.Core/WorkflowFinishedEvent.cs b/src/WorkflowEngine.Core/WorkflowFinishedEvent.cs new file mode 100644 index 0000000..384c07b --- /dev/null +++ b/src/WorkflowEngine.Core/WorkflowFinishedEvent.cs @@ -0,0 +1,9 @@ +namespace WorkflowEngine.Core +{ + public class WorkflowFinishedEvent : Event + { + public WorkflowFinishedEvent() : base(EventType.WorkflowFinished) + { + } + } +} diff --git a/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs b/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs index f7c7101..81866bd 100644 --- a/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs +++ b/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs @@ -1,7 +1,6 @@ using Hangfire; using Hangfire.Server; using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -89,58 +88,54 @@ public async ValueTask ExecuteAsync(IRunContext run, IWorkflow workflow, //TODO - avoid sending all workflow over hangfire, so we should lookup the manifest here if not set on workflow form its ID. workflow.Manifest ??= await workflowAccessor.GetWorkflowManifestAsync(workflow); - runContextAccessor.RunContext = run; arrayContext.JobId = context.BackgroundJob.Id; - try { - var result = await actionExecutor.ExecuteAsync(run, workflow, action); - - - - + + await outputRepository.AddEvent(run, workflow, action, new ActionCompletedEvent + { + // TODO: Add path to action in state + // assignees: thygesteffensen + JobId = context.BackgroundJob.Id, + ActionKey = action.Key + }); if (result != null) { - var next = await executor.GetNextAction(run, workflow, result); - + await hangfireActionExecutorResultHandler.InspectAsync(run, workflow, result, next); if (next != null) { - var a = backgroundJobClient.Enqueue( + // This is the hangfire ID thingy, this we would like to save + var workflowRunId = backgroundJobClient.Enqueue( (executor) => executor.ExecuteAsync(run, workflow, next, null)); + // result. } else if (workflow.Manifest.Actions.FindParentAction(action.Key) is ForLoopActionMetadata scope) { + var scopeAction = run.CopyTo(new Action { ScopeMoveNext = true, Type = scope.Type, Key = action.Key.Substring(0, action.Key.LastIndexOf('.')), ScheduledTime = DateTimeOffset.UtcNow }); - var scopeaction = run.CopyTo(new Action { ScopeMoveNext = true, Type = scope.Type, Key = action.Key.Substring(0, action.Key.LastIndexOf('.')), ScheduledTime = DateTimeOffset.UtcNow }); - - - var a = backgroundJobClient.Enqueue( - (executor) => executor.ExecuteAsync(run, workflow, scopeaction, null)); - + var workflowRunId = backgroundJobClient.Enqueue( + (executor) => executor.ExecuteAsync(run, workflow, scopeAction, null)); //await outputRepository.EndScope(run, workflow, action); } else if (result.Status == "Failed" && result.ReThrow) { - + await outputRepository.AddEvent(run, workflow, action, new WorkflowFinishedEvent()); throw new InvalidOperationException("Action failed: " + result.FailedReason) { Data = { ["ActionResult"] = result } }; } - - - - - - + else + { + await outputRepository.AddEvent(run, workflow, action, new WorkflowFinishedEvent()); + } } - return result; } catch (InvalidOperationException ex) @@ -148,9 +143,8 @@ public async ValueTask ExecuteAsync(IRunContext run, IWorkflow workflow, context.SetJobParameter("RetryCount", 999); throw; } - - } + /// /// Runs on the background process in hangfire /// @@ -171,12 +165,11 @@ public async ValueTask TriggerAsync(ITriggerContext context) //TODO - avoid sending all workflow over hangfire, so we should wipe the workflow.manifest before scheduling and restore it after. context.Workflow.Manifest = null; - var a = backgroundJobClient.Enqueue( (executor) => executor.ExecuteAsync(context, context.Workflow, action, null)); } return action; } } - } + From a4e48f9faaf47451aa66a3231aff19f37fb9badc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Tue, 17 Oct 2023 11:53:20 +0200 Subject: [PATCH 10/34] fix: Added JsonProperties and used abstract property for EventType and deserialization based on property. --- .../ActionCompletedEvent.cs | 26 ++++++++++-- src/WorkflowEngine.Core/Event.cs | 42 +++++++++++++++++-- src/WorkflowEngine.Core/EventType.cs | 11 ++++- .../WorkflowFinishedEvent.cs | 36 ++++++++++++++-- 4 files changed, 103 insertions(+), 12 deletions(-) diff --git a/src/WorkflowEngine.Core/ActionCompletedEvent.cs b/src/WorkflowEngine.Core/ActionCompletedEvent.cs index 6469fd3..64bb803 100644 --- a/src/WorkflowEngine.Core/ActionCompletedEvent.cs +++ b/src/WorkflowEngine.Core/ActionCompletedEvent.cs @@ -1,13 +1,31 @@ +using Newtonsoft.Json; + namespace WorkflowEngine.Core { public class ActionCompletedEvent : Event { - public ActionCompletedEvent() : base(EventType.ActionCompleted) - { - } - + public override EventType EventType => EventType.ActionCompleted; + + [JsonProperty("jobId")] public string JobId { get; set; } + [JsonProperty("actionKey")] public string ActionKey { get; set; } + [JsonProperty("resultPath")] public string ResultPath { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } + + public static ActionCompletedEvent FromAction(IActionResult result,IAction action,string jobId) + { + + return new ActionCompletedEvent + { + + JobId = jobId, + ActionKey = action.Key, + Status = result.Status, + }; + } } } diff --git a/src/WorkflowEngine.Core/Event.cs b/src/WorkflowEngine.Core/Event.cs index 14b3200..942305e 100644 --- a/src/WorkflowEngine.Core/Event.cs +++ b/src/WorkflowEngine.Core/Event.cs @@ -1,12 +1,48 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System; + namespace WorkflowEngine.Core { + public interface IHaveFinisningStatus + { + IActionResult Result { get; } + } + [JsonConverter(typeof(BaseClassConverter))] public abstract class Event { - public EventType EventType { get; } + [JsonProperty("eventType")] + [Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))] + public abstract EventType EventType { get; } + + + } + + public class BaseClassConverter : CustomCreationConverter + { + private EventType _currentObjectType; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var jobj = JObject.ReadFrom(reader); + _currentObjectType = jobj["eventType"].ToObject(); + return base.ReadJson(jobj.CreateReader(), objectType, existingValue, serializer); + } - protected Event(EventType eventType) + public override Event Create(Type objectType) { - EventType = eventType; + switch (_currentObjectType) + { + case EventType.ActionCompleted: + return new ActionCompletedEvent(); + case EventType.WorkflowStarted: + return new WorkflowStarteddEvent(); + case EventType.WorkflowFinished: + return new WorkflowFinishedEvent(); + default: + throw new NotImplementedException(); + } } } } diff --git a/src/WorkflowEngine.Core/EventType.cs b/src/WorkflowEngine.Core/EventType.cs index 1aa15f9..84fc932 100644 --- a/src/WorkflowEngine.Core/EventType.cs +++ b/src/WorkflowEngine.Core/EventType.cs @@ -1,8 +1,15 @@ +using Newtonsoft.Json.Linq; +using System.Runtime.Serialization; + namespace WorkflowEngine.Core { public enum EventType { - WorkflowFinished = 0, - ActionCompleted = 1 + [EnumMember(Value = "workflow_started")] + WorkflowStarted = 0, + [EnumMember(Value = "workflow_finished")] + WorkflowFinished = 1, + [EnumMember(Value = "action_completed")] + ActionCompleted = 2 } } diff --git a/src/WorkflowEngine.Core/WorkflowFinishedEvent.cs b/src/WorkflowEngine.Core/WorkflowFinishedEvent.cs index 384c07b..830a78a 100644 --- a/src/WorkflowEngine.Core/WorkflowFinishedEvent.cs +++ b/src/WorkflowEngine.Core/WorkflowFinishedEvent.cs @@ -1,9 +1,39 @@ +using Newtonsoft.Json; + namespace WorkflowEngine.Core { - public class WorkflowFinishedEvent : Event - { - public WorkflowFinishedEvent() : base(EventType.WorkflowFinished) + public abstract class WorkflowEvent : Event + { + [JsonProperty("status")] + public string Status { get; set; } + + [JsonProperty("jobId")] + public string JobId { get; set; } + + + public static WorkflowFinishedEvent CreateFinishedEvent(string jobid, IActionResult result) + { + return new WorkflowFinishedEvent() { JobId = jobid, Status = result.Status, Result = result }; + } + public static WorkflowFinishedEvent CreateStartedEvent(string jobid, string status) { + return new WorkflowFinishedEvent() { JobId = jobid, Status = status }; } } + public class WorkflowFinishedEvent : WorkflowEvent, IHaveFinisningStatus + { + public override EventType EventType => EventType.WorkflowFinished; + + + [JsonIgnore] + public IActionResult Result { get; set; } + + + } + public class WorkflowStarteddEvent : WorkflowEvent + { + public override EventType EventType => EventType.WorkflowStarted; + + + } } From f888bf351e81e1b07cd11df6a5a268ff89270054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Tue, 17 Oct 2023 11:54:26 +0200 Subject: [PATCH 11/34] fix: fixed naming violation --- src/WorkflowEngine.Core/ActionExecutor.cs | 78 ++++++++++++----------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/src/WorkflowEngine.Core/ActionExecutor.cs b/src/WorkflowEngine.Core/ActionExecutor.cs index d2457d0..f09483c 100644 --- a/src/WorkflowEngine.Core/ActionExecutor.cs +++ b/src/WorkflowEngine.Core/ActionExecutor.cs @@ -1,4 +1,4 @@ -using ExpressionEngine; +using ExpressionEngine; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; @@ -18,32 +18,32 @@ public class ScopeContext : IScopeContext } public class ActionExecutor : IActionExecutor { - private readonly IOutputsRepository outputsRepository; - private readonly IServiceProvider serviceProvider; - private readonly ILogger logger; - private readonly IScopeContext scopeContext; - private readonly IExpressionEngine expressionEngine; + private readonly IOutputsRepository _outputsRepository; + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + private readonly IScopeContext _scopeContext; + private readonly IExpressionEngine _expressionEngine; private Dictionary _implementations; public ActionExecutor( - IEnumerable implementations, + IEnumerable implementations, IOutputsRepository outputsRepository, IServiceProvider serviceProvider, ILogger logger, IScopeContext scopeContext, IExpressionEngine expressionEngine) { - - if(implementations.GroupBy(k=>k.Type).Any(c=>c.Count() > 1)) + + if (implementations.GroupBy(k => k.Type).Any(c => c.Count() > 1)) { - throw new ArgumentException("Double registration of " + String.Join(",", implementations.GroupBy(k => k.Type).Where(c => c.Count() > 1).Select(c=>c.Key))); + throw new ArgumentException("Double registration of " + String.Join(",", implementations.GroupBy(k => k.Type).Where(c => c.Count() > 1).Select(c => c.Key))); } _implementations = implementations?.ToDictionary(k => k.Type) ?? throw new ArgumentNullException(nameof(implementations)); - this.outputsRepository=outputsRepository??throw new ArgumentNullException(nameof(outputsRepository)); - this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - this.logger=logger??throw new ArgumentNullException(nameof(logger)); - this.scopeContext=scopeContext; - this.expressionEngine=expressionEngine??throw new ArgumentNullException(nameof(expressionEngine)); + _outputsRepository = outputsRepository ?? throw new ArgumentNullException(nameof(outputsRepository)); + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _scopeContext = scopeContext; + _expressionEngine = expressionEngine ?? throw new ArgumentNullException(nameof(expressionEngine)); } public async ValueTask ExecuteAsync(IRunContext context, IWorkflow workflow, IAction action) { @@ -52,13 +52,13 @@ public async ValueTask ExecuteAsync(IRunContext context, IWorkflo if (action.ScopeMoveNext) { - await outputsRepository.EndScope(context, workflow, action); + await _outputsRepository.EndScope(context, workflow, action); } var actionMetadata = workflow.Manifest.Actions.FindAction(action.Key); - scopeContext.Scope=action.Key; - action.Inputs = await expressionEngine.ResolveInputs(actionMetadata,logger); - + _scopeContext.Scope = action.Key; + action.Inputs = await _expressionEngine.ResolveInputs(actionMetadata, _logger); + { //if (workflow.Manifest.Actions.FindParentAction(action.Key) is ForLoopActionMetadata parent) //{ @@ -71,37 +71,39 @@ public async ValueTask ExecuteAsync(IRunContext context, IWorkflo //} //else { - await outputsRepository.AddInput(context, workflow, action); + await _outputsRepository.AddInput(context, workflow, action); } } - - var actionImplementation = serviceProvider.GetRequiredService(_implementations[actionMetadata.Type].Implementation) as IActionImplementation; - + + var actionImplementation = _serviceProvider.GetRequiredService(_implementations[actionMetadata.Type].Implementation) as IActionImplementation; - var result = new ActionResult { - Key = action.Key, - Status = "Succeded", - Result = await actionImplementation.ExecuteAsync(context,workflow, action) + + var result = new ActionResult + { + Key = action.Key, + Status = "Succeded", + Result = await actionImplementation.ExecuteAsync(context, workflow, action) }; - - - await outputsRepository.AddAsync(context, workflow, action, result); - + + + await _outputsRepository.AddAsync(context, workflow, action, result); + return result; - - - }catch(Exception ex) + + + } + catch (Exception ex) { - var result= new ActionResult { Key = action.Key, Status = "Failed", FailedReason=ex.ToString(), ReThrow = (ex is InvalidOperationException) }; + var result = new ActionResult { Key = action.Key, Status = "Failed", FailedReason = ex.ToString(), ReThrow = (ex is InvalidOperationException) }; try { - await outputsRepository.AddAsync(context, workflow, action, result); + await _outputsRepository.AddAsync(context, workflow, action, result); } - catch (Exception ) + catch (Exception exx) { } @@ -109,7 +111,7 @@ public async ValueTask ExecuteAsync(IRunContext context, IWorkflo } } } - + } From 85c00e02963cf20d267f8a14cec84d0aed7a2f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Tue, 17 Oct 2023 11:55:33 +0200 Subject: [PATCH 12/34] fix: refactored output repository with better context --- src/WorkflowEngine.Core/DefaultOutputsRepository.cs | 4 ++-- src/WorkflowEngine.Core/IOutputsRepository.cs | 6 +++--- src/WorkflowEngine.Core/ITriggerContext.cs | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/WorkflowEngine.Core/DefaultOutputsRepository.cs b/src/WorkflowEngine.Core/DefaultOutputsRepository.cs index 8b580e6..c7b9a02 100644 --- a/src/WorkflowEngine.Core/DefaultOutputsRepository.cs +++ b/src/WorkflowEngine.Core/DefaultOutputsRepository.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; @@ -64,7 +64,7 @@ private JToken GetOrCreateRun(IRunContext context) return Runs.GetOrAdd(context.RunId, (id) => new JObject(new JProperty("actions", new JObject()), new JProperty("triggers", new JObject()))); } - public ValueTask AddAsync(IRunContext context, IWorkflow workflow, ITrigger trigger) + public ValueTask AddTrigger(ITriggerContext context, IWorkflow workflow, ITrigger trigger) { JToken run = GetOrCreateRun(context); diff --git a/src/WorkflowEngine.Core/IOutputsRepository.cs b/src/WorkflowEngine.Core/IOutputsRepository.cs index 4b73456..0f1393f 100644 --- a/src/WorkflowEngine.Core/IOutputsRepository.cs +++ b/src/WorkflowEngine.Core/IOutputsRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace WorkflowEngine.Core @@ -6,13 +6,13 @@ namespace WorkflowEngine.Core public interface IOutputsRepository { ValueTask AddAsync(IRunContext context, IWorkflow workflow, IAction action, IActionResult result); - ValueTask AddAsync(IRunContext context, IWorkflow workflow, ITrigger trigger); + ValueTask AddTrigger(ITriggerContext context, IWorkflow workflow, ITrigger trigger); ValueTask GetTriggerData(Guid id); ValueTask AddInput(IRunContext context, IWorkflow workflow, IAction action); ValueTask GetOutputData(Guid id, string v); ValueTask AddArrayItemAsync(IRunContext run, IWorkflow workflow, string key, IActionResult result); ValueTask AddArrayInput(IRunContext context, IWorkflow workflow, IAction action); - ValueTask StartScope(IRunContext context, IWorkflow workflow, IAction action); + // ValueTask StartScope(IRunContext context, IWorkflow workflow, IAction action); ValueTask AddScopeItem(IRunContext context, IWorkflow workflow, IAction action, IActionResult result); ValueTask EndScope(IRunContext run, IWorkflow workflow, IAction action); ValueTask AddEvent(IRunContext run, IWorkflow workflow, IAction action, Event @event); diff --git a/src/WorkflowEngine.Core/ITriggerContext.cs b/src/WorkflowEngine.Core/ITriggerContext.cs index e14658d..a44c763 100644 --- a/src/WorkflowEngine.Core/ITriggerContext.cs +++ b/src/WorkflowEngine.Core/ITriggerContext.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace WorkflowEngine.Core { @@ -7,6 +7,7 @@ public interface ITriggerContext:IRunContext IWorkflow Workflow { get; } ITrigger Trigger { get; set; } + string JobId { get; set; } } From a5b868f48159cf61841c3ebf0fdc2ccb651bdab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Tue, 17 Oct 2023 11:56:30 +0200 Subject: [PATCH 13/34] fix: added nested input resolution on actions --- .../ExpressionEngineExtensions.cs | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/WorkflowEngine.Core/ExpressionEngineExtensions.cs b/src/WorkflowEngine.Core/ExpressionEngineExtensions.cs index 5410c4c..7ce6833 100644 --- a/src/WorkflowEngine.Core/ExpressionEngineExtensions.cs +++ b/src/WorkflowEngine.Core/ExpressionEngineExtensions.cs @@ -1,7 +1,8 @@ -using ExpressionEngine; +using ExpressionEngine; using ExpressionEngine.Functions.Base; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; @@ -24,30 +25,39 @@ public static IServiceCollection AddFunctions(this IServiceCollection services) return services; } - public static async ValueTask> ResolveInputs(this IExpressionEngine engine, ActionMetadata actionMetadata, ILogger logger) + public static async ValueTask> ResolveInputs(this IExpressionEngine engine, IDictionary inputs, ILogger logger) { var resolvedInputs = new Dictionary(); - foreach (var input in actionMetadata.Inputs) + foreach (var input in inputs) { if (input.Value is string str && str.Contains("@")) { - resolvedInputs[input.Key] = await engine.ParseToValueContainer(str); + resolvedInputs[input.Key] = await engine.ParseToValueContainer(input.Value.ToString()); } else { - resolvedInputs[input.Key] = input.Value; - } - //else - //{ + if (input.Value is IDictionary obj) + { + resolvedInputs[input.Key] = await engine.ResolveInputs(obj, logger); + } + else + { + resolvedInputs[input.Key] = input.Value; + } - // logger.LogWarning("{Key}: {Type}", input, inputs[input].GetType()); - //} - // inputs[input] = inputs[input]; + } + } return resolvedInputs; + + } + public static ValueTask> ResolveInputs(this IExpressionEngine engine, ActionMetadata actionMetadata, ILogger logger) + { + return engine.ResolveInputs(actionMetadata.Inputs, logger); + } } } From 4027dd2e19058140fb40070598508e0b8f96c573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Tue, 17 Oct 2023 11:56:51 +0200 Subject: [PATCH 14/34] fix: Added jobid on trigger --- src/WorkflowEngine.Core/TriggerContext.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WorkflowEngine.Core/TriggerContext.cs b/src/WorkflowEngine.Core/TriggerContext.cs index 1b0668a..e5330cf 100644 --- a/src/WorkflowEngine.Core/TriggerContext.cs +++ b/src/WorkflowEngine.Core/TriggerContext.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -32,7 +32,7 @@ public class TriggerContext : ITriggerContext, IFormattable public string PrincipalId { get; set; } public Guid RunId { get; set; } - + public string JobId { get; set; } public string ToString(string format, IFormatProvider formatProvider) { From 046996c1d560ecafe481d22242975291fdb483f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Tue, 17 Oct 2023 11:57:19 +0200 Subject: [PATCH 15/34] fix: renamed to AddTrigger for output respository --- src/WorkflowEngine.Core/WorkflowExecutor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/WorkflowEngine.Core/WorkflowExecutor.cs b/src/WorkflowEngine.Core/WorkflowExecutor.cs index 04e957a..bc65b07 100644 --- a/src/WorkflowEngine.Core/WorkflowExecutor.cs +++ b/src/WorkflowEngine.Core/WorkflowExecutor.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Threading.Tasks; @@ -49,8 +49,8 @@ public ValueTask GetNextAction(IRunContext context, IWorkflow workflow, public async ValueTask Trigger(ITriggerContext context) { - - await outputsRepository.AddAsync(context,context.Workflow, context.Trigger); + + await outputsRepository.AddTrigger(context,context.Workflow, context.Trigger); var action = context.Workflow.Manifest.Actions.SingleOrDefault(c => c.Value.RunAfter?.Count == 0); From 4a77e92e5501635ab65bef94158bd62bd7063515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Tue, 17 Oct 2023 11:58:17 +0200 Subject: [PATCH 16/34] fix: added jobid to trigger --- .../HangfireWorkflowExecutor.cs | 35 ++++++++----------- .../IHangfireWorkflowExecutor.cs | 5 +-- .../ScheduledWorkflowTrigger.cs | 4 +-- .../WorkflowStarterBackgroundJob.cs | 4 +-- 4 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs b/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs index 81866bd..4c6772d 100644 --- a/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs +++ b/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs @@ -1,4 +1,4 @@ -using Hangfire; +using Hangfire; using Hangfire.Server; using System; using System.Collections.Generic; @@ -36,7 +36,7 @@ public static string TriggerAsync(this IBackgroundJobClient bac where TTriggerContext : TriggerContext { var job = backgroundJobClient.Enqueue( - (executor) => executor.TriggerAsync(trigger)); + (executor) => executor.TriggerAsync(trigger,null)); return job; @@ -95,13 +95,7 @@ public async ValueTask ExecuteAsync(IRunContext run, IWorkflow workflow, { var result = await actionExecutor.ExecuteAsync(run, workflow, action); - await outputRepository.AddEvent(run, workflow, action, new ActionCompletedEvent - { - // TODO: Add path to action in state - // assignees: thygesteffensen - JobId = context.BackgroundJob.Id, - ActionKey = action.Key - }); + await outputRepository.AddEvent(run, workflow, action, ActionCompletedEvent.FromAction(result,action,context.BackgroundJob.Id)); if (result != null) { @@ -127,12 +121,12 @@ public async ValueTask ExecuteAsync(IRunContext run, IWorkflow workflow, } else if (result.Status == "Failed" && result.ReThrow) { - await outputRepository.AddEvent(run, workflow, action, new WorkflowFinishedEvent()); + await outputRepository.AddEvent(run, workflow, action, WorkflowEvent.CreateFinishedEvent(context.BackgroundJob.Id,result)); throw new InvalidOperationException("Action failed: " + result.FailedReason) { Data = { ["ActionResult"] = result } }; } else { - await outputRepository.AddEvent(run, workflow, action, new WorkflowFinishedEvent()); + await outputRepository.AddEvent(run, workflow, action, WorkflowEvent.CreateFinishedEvent(context.BackgroundJob.Id, result)); } } @@ -148,25 +142,26 @@ public async ValueTask ExecuteAsync(IRunContext run, IWorkflow workflow, /// /// Runs on the background process in hangfire /// - /// + /// /// - public async ValueTask TriggerAsync(ITriggerContext context) + public async ValueTask TriggerAsync(ITriggerContext triggerContext, PerformContext context) { //TODO - avoid sending all workflow over hangfire, - context.Workflow.Manifest ??= await workflowAccessor.GetWorkflowManifestAsync(context.Workflow); - - context.RunId = context.RunId == Guid.Empty ? Guid.NewGuid() : context.RunId; + triggerContext.Workflow.Manifest ??= await workflowAccessor.GetWorkflowManifestAsync(triggerContext.Workflow); - runContextAccessor.RunContext = context; - var action = await executor.Trigger(context); + triggerContext.RunId = triggerContext.RunId == Guid.Empty ? Guid.NewGuid() : triggerContext.RunId; + triggerContext.JobId = context.BackgroundJob.Id; + + runContextAccessor.RunContext = triggerContext; + var action = await executor.Trigger(triggerContext); if (action != null) { //TODO - avoid sending all workflow over hangfire, so we should wipe the workflow.manifest before scheduling and restore it after. - context.Workflow.Manifest = null; + triggerContext.Workflow.Manifest = null; var a = backgroundJobClient.Enqueue( - (executor) => executor.ExecuteAsync(context, context.Workflow, action, null)); + (executor) => executor.ExecuteAsync(triggerContext, triggerContext.Workflow, action, null)); } return action; } diff --git a/src/WorkflowEngine.Hangfire/IHangfireWorkflowExecutor.cs b/src/WorkflowEngine.Hangfire/IHangfireWorkflowExecutor.cs index b31d60d..a205d74 100644 --- a/src/WorkflowEngine.Hangfire/IHangfireWorkflowExecutor.cs +++ b/src/WorkflowEngine.Hangfire/IHangfireWorkflowExecutor.cs @@ -1,4 +1,5 @@ -using Hangfire; +using Hangfire; +using Hangfire.Server; using System.ComponentModel; using System.Threading.Tasks; using WorkflowEngine.Core; @@ -8,7 +9,7 @@ namespace WorkflowEngine public interface IHangfireWorkflowExecutor { [JobDisplayName("Trigger: {0:Workflow} RunId={0:Id}")] - public ValueTask TriggerAsync(ITriggerContext context); + public ValueTask TriggerAsync(ITriggerContext triggercontext, PerformContext context); } } diff --git a/src/WorkflowEngine.Hangfire/ScheduledWorkflowTrigger.cs b/src/WorkflowEngine.Hangfire/ScheduledWorkflowTrigger.cs index 8475afb..9466997 100644 --- a/src/WorkflowEngine.Hangfire/ScheduledWorkflowTrigger.cs +++ b/src/WorkflowEngine.Hangfire/ScheduledWorkflowTrigger.cs @@ -1,4 +1,4 @@ -using Hangfire; +using Hangfire; using Hangfire.Client; using Hangfire.Common; using Hangfire.Storage; @@ -55,7 +55,7 @@ public Task Trigger(string externalid, bool create, DateTimeOffset time, var job = _backgroundJobClient.Schedule((executor) => executor.TriggerAsync( new TriggerContext { Workflow = workflow, Trigger = trigger , - }), time); + },null), time); _logger.LogInformation("Created scheduled workflow job {JobID}", job); diff --git a/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs b/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs index 4002660..7b449c3 100644 --- a/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs +++ b/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs @@ -1,4 +1,4 @@ -using Hangfire; +using Hangfire; using Microsoft.Extensions.Hosting; using System; using System.Collections.Generic; @@ -50,7 +50,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) Type = trigger.Value.Type, Key = trigger.Key }, - }), trigger.Value.Inputs["cronExpression"] as string); + },null), trigger.Value.Inputs["cronExpression"] as string); if (first && trigger.Value.Inputs.ContainsKey("runAtStartup") && (bool)trigger.Value.Inputs["runAtStartup"]) jobs.Trigger(workflow.Id.ToString()); From 1fe21108c184c99c910f3e387e289dab3eb94b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Wed, 18 Oct 2023 09:43:20 +0200 Subject: [PATCH 17/34] fix: added null, this is set by hangfire when running --- apps/WorkflowEngine.DemoApp/Startup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/WorkflowEngine.DemoApp/Startup.cs b/apps/WorkflowEngine.DemoApp/Startup.cs index 7574659..dfe4fcf 100644 --- a/apps/WorkflowEngine.DemoApp/Startup.cs +++ b/apps/WorkflowEngine.DemoApp/Startup.cs @@ -303,7 +303,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IService (executor) => executor.TriggerAsync(new TriggerContext { Workflow = workflows.First(), Trigger = new Trigger { ScheduledTime = DateTimeOffset.UtcNow, Key = workflows.First().Manifest.Triggers.First().Key, - Type =workflows.First().Manifest.Triggers.First().Value.Type }, RunId = Guid.NewGuid() })); + Type =workflows.First().Manifest.Triggers.First().Value.Type }, RunId = Guid.NewGuid() },null)); await c.Response.WriteAsync("Background JOb:" + a); @@ -333,7 +333,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IService Key = workflows.First(w => w.Id.ToString() == c.GetRouteValue("id") as string).Manifest.Triggers.FirstOrDefault().Key }, Workflow = workflows.First(w=>w.Id.ToString() == c.GetRouteValue("id") as string) - })); + },null)); await c.Response.WriteAsync("Background JOb:" + a); From c5e919633a11ad43e47a402179b73d128bff8e22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Wed, 18 Oct 2023 12:35:30 +0200 Subject: [PATCH 18/34] Update release.yml --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84d969f..40ac643 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v1 with: - node-version: 16 + node-version: 20 - name: Add plugin for conventional commits run: npm install conventional-changelog-conventionalcommits @@ -80,4 +80,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GIT_AUTHOR_NAME: thygesteffensen;pksorensen GIT_AUTHOR_EMAIL: 31892312+thygesteffensen@users.noreply.github.com;poul@kjeldager.com - run: npx semantic-release \ No newline at end of file + run: npx semantic-release From 6e1bbc5c6b2e709a48bda5104f6d361306a1727c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Thu, 2 Nov 2023 13:06:06 +0100 Subject: [PATCH 19/34] fix: added multiple trigger support for timed triggers --- .../ExpressionEngineExtensions.cs | 2 +- .../DependencyInjectionExtensions.cs | 2 +- .../WorkflowEngine.Hangfire.csproj | 9 ++-- .../WorkflowStarterBackgroundJob.cs | 50 +++++++++++-------- 4 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/WorkflowEngine.Core/ExpressionEngineExtensions.cs b/src/WorkflowEngine.Core/ExpressionEngineExtensions.cs index 5410c4c..8a62f0d 100644 --- a/src/WorkflowEngine.Core/ExpressionEngineExtensions.cs +++ b/src/WorkflowEngine.Core/ExpressionEngineExtensions.cs @@ -28,7 +28,7 @@ public static async ValueTask> ResolveInputs(this IEx { var resolvedInputs = new Dictionary(); - + if(actionMetadata.Inputs!=null) foreach (var input in actionMetadata.Inputs) { if (input.Value is string str && str.Contains("@")) diff --git a/src/WorkflowEngine.Hangfire/DependencyInjectionExtensions.cs b/src/WorkflowEngine.Hangfire/DependencyInjectionExtensions.cs index c941db5..15d5cb2 100644 --- a/src/WorkflowEngine.Hangfire/DependencyInjectionExtensions.cs +++ b/src/WorkflowEngine.Hangfire/DependencyInjectionExtensions.cs @@ -25,7 +25,7 @@ public static IServiceCollection AddWorkflowEngine(this ISer services.AddFunctions(); services.AddScoped(); - services.AddSingleton(); + services.AddTransient(); services.AddHostedService(); diff --git a/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj b/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj index 239f5ad..1e1d50b 100644 --- a/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj +++ b/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj @@ -21,11 +21,8 @@ - - - - - - + + + diff --git a/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs b/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs index 4002660..c134042 100644 --- a/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs +++ b/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs @@ -32,28 +32,31 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) foreach (var workflow in await workflows.GetAllWorkflows()) { - var trigger = workflow.Manifest.Triggers.FirstOrDefault(t => t.Value.Type == "TimerTrigger"); - if (!trigger.Equals(default(KeyValuePair))) + foreach (var trigger in workflow.Manifest.Triggers.Where(t => t.Value.Type == "TimerTrigger")) { - - workflow.Manifest = null; - - jobs.AddOrUpdate(workflow.Id.ToString(), - (executor) => executor.TriggerAsync(new TriggerContext - { - Workflow = workflow, - Trigger = new Trigger + + if (!trigger.Equals(default(KeyValuePair))) + { + + workflow.Manifest = null; + + jobs.AddOrUpdate(workflow.Id.ToString() + trigger.Key, + (System.Linq.Expressions.Expression>)((executor) => executor.TriggerAsync(new TriggerContext { - Inputs = trigger.Value.Inputs, - ScheduledTime = DateTimeOffset.UtcNow, - Type = trigger.Value.Type, - Key = trigger.Key - }, - }), trigger.Value.Inputs["cronExpression"] as string); - - if (first && trigger.Value.Inputs.ContainsKey("runAtStartup") && (bool)trigger.Value.Inputs["runAtStartup"]) - jobs.Trigger(workflow.Id.ToString()); + Workflow = workflow, + Trigger = new Trigger + { + Inputs = trigger.Value.Inputs, + ScheduledTime = DateTimeOffset.UtcNow, + Type = trigger.Value.Type, + Key = trigger.Key + }, + })), trigger.Value.Inputs["cronExpression"] as string, GetTimeZone(trigger)); + + if (first && trigger.Value.Inputs.ContainsKey("runAtStartup") && (bool)trigger.Value.Inputs["runAtStartup"]) + jobs.Trigger(workflow.Id.ToString()); + } } } @@ -66,7 +69,14 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) } - + static TimeZoneInfo GetTimeZone(KeyValuePair trigger) + { + + if (trigger.Value.Inputs.ContainsKey("timezone") && trigger.Value.Inputs["timezone"] is string zone && !string.IsNullOrWhiteSpace(zone)) + return TimeZoneInfo.FindSystemTimeZoneById(zone) ?? TimeZoneInfo.Utc; + return TimeZoneInfo.Utc; + + } } } } From 85dd3736a9e277cfa9005da21f7ecaec437bdb90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Thu, 2 Nov 2023 13:30:15 +0100 Subject: [PATCH 20/34] fix: added wait support --- src/WorkflowEngine.Core/Action.cs | 2 +- src/WorkflowEngine.Core/ActionExecutor.cs | 18 +++- src/WorkflowEngine.Core/ActionResult.cs | 5 +- src/WorkflowEngine.Core/IAction.cs | 3 +- src/WorkflowEngine.Core/IActionResult.cs | 8 +- src/WorkflowEngine.Core/IWorkflowExecutor.cs | 4 +- src/WorkflowEngine.Core/WorkflowActions.cs | 3 +- src/WorkflowEngine.Core/WorkflowExecutor.cs | 4 +- .../DependencyInjectionExtensions.cs | 6 +- src/WorkflowEngine.Hangfire/ForloopAction.cs | 34 ++++--- .../HangfireExtensions.cs | 73 ++++++++++++++- .../HangfireWorkflowExecutor.cs | 93 ++++++++++++------- .../IHangfireActionExecutor.cs | 3 +- .../IHangfireWorkflowExecutor.cs | 4 + .../ScheduledWorkflowTrigger.cs | 4 - .../WorkflowStarterBackgroundJob.cs | 2 +- 16 files changed, 194 insertions(+), 72 deletions(-) diff --git a/src/WorkflowEngine.Core/Action.cs b/src/WorkflowEngine.Core/Action.cs index 1ae767b..9612581 100644 --- a/src/WorkflowEngine.Core/Action.cs +++ b/src/WorkflowEngine.Core/Action.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace WorkflowEngine.Core diff --git a/src/WorkflowEngine.Core/ActionExecutor.cs b/src/WorkflowEngine.Core/ActionExecutor.cs index f09483c..0e41de1 100644 --- a/src/WorkflowEngine.Core/ActionExecutor.cs +++ b/src/WorkflowEngine.Core/ActionExecutor.cs @@ -15,6 +15,10 @@ public interface IScopeContext public class ScopeContext : IScopeContext { public string Scope { get; set; } + } + public interface IWaitAction + { + } public class ActionExecutor : IActionExecutor { @@ -47,6 +51,7 @@ public ActionExecutor( } public async ValueTask ExecuteAsync(IRunContext context, IWorkflow workflow, IAction action) { + try { @@ -56,6 +61,10 @@ public async ValueTask ExecuteAsync(IRunContext context, IWorkflo } var actionMetadata = workflow.Manifest.Actions.FindAction(action.Key); + if (actionMetadata == null) + { + throw new InvalidOperationException($"The action '{action.Key}' was not found. Found keys are: '{string.Join(",",workflow.Manifest.Actions.Keys)}'"); + } _scopeContext.Scope = action.Key; action.Inputs = await _expressionEngine.ResolveInputs(actionMetadata, _logger); @@ -78,17 +87,16 @@ public async ValueTask ExecuteAsync(IRunContext context, IWorkflo var actionImplementation = _serviceProvider.GetRequiredService(_implementations[actionMetadata.Type].Implementation) as IActionImplementation; - + var actionResult = await actionImplementation.ExecuteAsync(context, workflow, action); var result = new ActionResult { Key = action.Key, Status = "Succeded", - Result = await actionImplementation.ExecuteAsync(context, workflow, action) + Result = actionResult, + DelayNextAction = (actionImplementation is IWaitAction) ? (TimeSpan)actionResult: null }; - - - + await _outputsRepository.AddAsync(context, workflow, action, result); diff --git a/src/WorkflowEngine.Core/ActionResult.cs b/src/WorkflowEngine.Core/ActionResult.cs index 0b6e5b9..6f8f0d1 100644 --- a/src/WorkflowEngine.Core/ActionResult.cs +++ b/src/WorkflowEngine.Core/ActionResult.cs @@ -1,4 +1,6 @@ -namespace WorkflowEngine.Core +using System; + +namespace WorkflowEngine.Core { public class ActionResult : IActionResult { @@ -8,6 +10,7 @@ public class ActionResult : IActionResult public object Result { get; set; } public string FailedReason { get; set; } public bool ReThrow { get; set; } + public TimeSpan? DelayNextAction { get; set; } } diff --git a/src/WorkflowEngine.Core/IAction.cs b/src/WorkflowEngine.Core/IAction.cs index 09ced53..3fa02dd 100644 --- a/src/WorkflowEngine.Core/IAction.cs +++ b/src/WorkflowEngine.Core/IAction.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace WorkflowEngine.Core @@ -12,6 +12,7 @@ public interface IAction : IRunContext public int Index { get; set; } public bool ScopeMoveNext { get; set; } + } diff --git a/src/WorkflowEngine.Core/IActionResult.cs b/src/WorkflowEngine.Core/IActionResult.cs index 6c06c02..a30bf7b 100644 --- a/src/WorkflowEngine.Core/IActionResult.cs +++ b/src/WorkflowEngine.Core/IActionResult.cs @@ -1,4 +1,6 @@ -namespace WorkflowEngine.Core +using System; + +namespace WorkflowEngine.Core { //Har info om hvad der skal schedules next i hangfire public interface IActionResult @@ -7,7 +9,9 @@ public interface IActionResult string Status { get; } object Result { get; } string FailedReason { get; } - public bool ReThrow { get; } + bool ReThrow { get; } + + TimeSpan? DelayNextAction { get; } } diff --git a/src/WorkflowEngine.Core/IWorkflowExecutor.cs b/src/WorkflowEngine.Core/IWorkflowExecutor.cs index 693fe49..05ee87c 100644 --- a/src/WorkflowEngine.Core/IWorkflowExecutor.cs +++ b/src/WorkflowEngine.Core/IWorkflowExecutor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace WorkflowEngine.Core @@ -20,7 +20,7 @@ public T CopyTo(T other) public interface IWorkflowExecutor { public ValueTask Trigger(ITriggerContext context); - public ValueTask GetNextAction(IRunContext context, IWorkflow workflow, IActionResult priorResult); + public ValueTask GetNextAction(IRunContext context, IWorkflow workflow, IAction action, IActionResult priorResult); } diff --git a/src/WorkflowEngine.Core/WorkflowActions.cs b/src/WorkflowEngine.Core/WorkflowActions.cs index 7a551b4..faa8dd9 100644 --- a/src/WorkflowEngine.Core/WorkflowActions.cs +++ b/src/WorkflowEngine.Core/WorkflowActions.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace WorkflowEngine.Core @@ -14,6 +14,7 @@ public class ForLoopActionMetadata : ActionMetadata, IScopedActionMetadata public interface IScopedActionMetadata { WorkflowActions Actions { get; set; } + string Type { get; set; } } public class WorkflowActions : Dictionary { diff --git a/src/WorkflowEngine.Core/WorkflowExecutor.cs b/src/WorkflowEngine.Core/WorkflowExecutor.cs index bc65b07..3c418bc 100644 --- a/src/WorkflowEngine.Core/WorkflowExecutor.cs +++ b/src/WorkflowEngine.Core/WorkflowExecutor.cs @@ -25,7 +25,7 @@ public WorkflowExecutor(ILogger logger, IOutputsRepository ou this.logger = logger??throw new ArgumentNullException(nameof(logger)); this.outputsRepository=outputsRepository??throw new ArgumentNullException(nameof(outputsRepository)); } - public ValueTask GetNextAction(IRunContext context, IWorkflow workflow, IActionResult priorResult) + public ValueTask GetNextAction(IRunContext context, IWorkflow workflow, IAction action, IActionResult priorResult) { logger.LogInformation("Finding Next Action for {WorkflowId} and prior {@result} ", workflow.Id, priorResult); //var action = workflow.Manifest.Actions.Single(c => c.Key == priorResult.Key); @@ -39,7 +39,7 @@ public ValueTask GetNextAction(IRunContext context, IWorkflow workflow, if (next.Value.ShouldRun(priorResult.Key,priorResult.Status)) // .RunAfter[priorResult.Key].Contains(priorResult.Status)) { - return new ValueTask(context.CopyTo(new Action { Type = next.Value.Type, Key=next.Key, ScheduledTime=DateTimeOffset.UtcNow })); + return new ValueTask(context.CopyTo(new Action { Type = next.Value.Type, Key=next.Key, ScheduledTime=DateTimeOffset.UtcNow, Index = action.Index })); } return new ValueTask(); diff --git a/src/WorkflowEngine.Hangfire/DependencyInjectionExtensions.cs b/src/WorkflowEngine.Hangfire/DependencyInjectionExtensions.cs index c941db5..645a748 100644 --- a/src/WorkflowEngine.Hangfire/DependencyInjectionExtensions.cs +++ b/src/WorkflowEngine.Hangfire/DependencyInjectionExtensions.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Sprache; using WorkflowEngine; using WorkflowEngine.Core; using WorkflowEngine.Core.Actions; @@ -25,12 +26,13 @@ public static IServiceCollection AddWorkflowEngine(this ISer services.AddFunctions(); services.AddScoped(); - services.AddSingleton(); + services.AddTransient(); services.AddHostedService(); services.AddTransient(); services.AddTransient(); + return services; } diff --git a/src/WorkflowEngine.Hangfire/ForloopAction.cs b/src/WorkflowEngine.Hangfire/ForloopAction.cs index b39a9a9..767f219 100644 --- a/src/WorkflowEngine.Hangfire/ForloopAction.cs +++ b/src/WorkflowEngine.Hangfire/ForloopAction.cs @@ -1,4 +1,4 @@ -using ExpressionEngine; +using ExpressionEngine; using Hangfire; using System; using System.Collections.Generic; @@ -10,7 +10,7 @@ namespace WorkflowEngine.Core.Actions { public interface IArrayContext { - public string JobId { get; set; } + public string JobId { get; set; } } public class ArrayContext : IArrayContext { @@ -22,15 +22,18 @@ public class ForeachAction : IActionImplementation private readonly IExpressionEngine expressionEngine; private readonly IBackgroundJobClient backgroundJobClient; private readonly IArrayContext arrayContext; + private readonly IWorkflowAccessor _workflowAccessor; - public ForeachAction(IExpressionEngine expressionEngine, IBackgroundJobClient backgroundJobClient, IArrayContext arrayContext) + public ForeachAction(IExpressionEngine expressionEngine, IBackgroundJobClient backgroundJobClient, IArrayContext arrayContext, IWorkflowAccessor workflowAccessor) { - this.expressionEngine=expressionEngine??throw new ArgumentNullException(nameof(expressionEngine)); - this.backgroundJobClient=backgroundJobClient; - this.arrayContext=arrayContext??throw new ArgumentNullException(nameof(arrayContext)); + this.expressionEngine = expressionEngine ?? throw new ArgumentNullException(nameof(expressionEngine)); + this.backgroundJobClient = backgroundJobClient; + this.arrayContext = arrayContext ?? throw new ArgumentNullException(nameof(arrayContext)); + _workflowAccessor = workflowAccessor ?? throw new ArgumentNullException(nameof(workflowAccessor)); } public async ValueTask ExecuteAsync(IRunContext context, IWorkflow workflow, IAction action) { + var loop = workflow.Manifest.Actions.FindAction(action.Key) as ForLoopActionMetadata; if (loop.ForEach is string str && str.Contains("@")) @@ -44,7 +47,7 @@ public async ValueTask ExecuteAsync(IRunContext context, IWorkflow workf if (loop.ConcurrentCount > 1) { var pageSize = itemsToRunover.Count / loop.ConcurrentCount; - + foreach (var child in Enumerable.Range(0, loop.ConcurrentCount)) { var nextactionmetadata = loop.Actions.SingleOrDefault(c => c.Value.RunAfter?.Count == 0); @@ -52,7 +55,7 @@ public async ValueTask ExecuteAsync(IRunContext context, IWorkflow workf throw new Exception("No action with no runafter could be found"); var nextaction = context.CopyTo(new Action { Type = nextactionmetadata.Value.Type, Key = $"{action.Key}.{nextactionmetadata.Key}", ScheduledTime = DateTimeOffset.UtcNow, Index = action.Index }); - + var a = backgroundJobClient.ContinueJobWith(arrayContext.JobId, (executor) => executor.ExecuteAsync(context, workflow, nextaction, null)); @@ -62,15 +65,16 @@ public async ValueTask ExecuteAsync(IRunContext context, IWorkflow workf else if (action.Index < itemsToRunover.Count) { // var nextAction = new Action { Type = action.Type, Key=action.Key, ScheduledTime = DateTimeOffset.UtcNow, RunId = context.RunId, Index = action.Index+1 }; - + var nextactionmetadata = loop.Actions.SingleOrDefault(c => c.Value.RunAfter?.Count == 0); - if (nextactionmetadata.Equals( default(KeyValuePair))) + if (nextactionmetadata.Equals(default(KeyValuePair))) throw new Exception("No action with no runafter could be found"); - - var nextaction = context.CopyTo( new Action { Type = nextactionmetadata.Value.Type, Key= $"{action.Key}.{nextactionmetadata.Key}", ScheduledTime = DateTimeOffset.UtcNow, Index=action.Index }); + + var nextaction = context.CopyTo(new Action { Type = nextactionmetadata.Value.Type, Key = $"{action.Key}.{nextactionmetadata.Key}", ScheduledTime = DateTimeOffset.UtcNow, Index = action.Index }); + - var a = backgroundJobClient.ContinueJobWith(arrayContext.JobId, - (executor) => executor.ExecuteAsync(context, workflow, nextaction,null)); + var a = backgroundJobClient.ContinueJobWith(arrayContext.JobId, + (executor) => executor.ExecuteAsync(context, workflow, nextaction, null)); return new { item = itemsToRunover[action.Index] }; } @@ -81,7 +85,7 @@ public async ValueTask ExecuteAsync(IRunContext context, IWorkflow workf - return new { }; + return new { }; } } } diff --git a/src/WorkflowEngine.Hangfire/HangfireExtensions.cs b/src/WorkflowEngine.Hangfire/HangfireExtensions.cs index 7f5c4a9..5bc66be 100644 --- a/src/WorkflowEngine.Hangfire/HangfireExtensions.cs +++ b/src/WorkflowEngine.Hangfire/HangfireExtensions.cs @@ -1,8 +1,79 @@ -using Hangfire.Storage; +using Hangfire.Client; +using Hangfire.Common; +using Hangfire.Server; +using Hangfire.Storage; using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using WorkflowEngine.Core; namespace WorkflowEngine { + + + internal sealed class HangfireWorkflowManifestJobFilterAttribute : JobFilterAttribute + { + } + public sealed class HangfireWorkflowManifestJobFilter : IServerFilter, IClientFilter + { + private readonly IWorkflowAccessor _workflowAccessor; + + public HangfireWorkflowManifestJobFilter(IWorkflowAccessor workflowAccessor) + { + _workflowAccessor = workflowAccessor ?? throw new System.ArgumentNullException(nameof(workflowAccessor)); + } + public void OnCreated(CreatedContext filterContext) + { + foreach (var workflow in filterContext.BackgroundJob.Job.Args.OfType()) + { + workflow.Manifest = _workflowAccessor.GetWorkflowManifestAsync(workflow).GetAwaiter().GetResult(); + } + + foreach (var workflow in filterContext.BackgroundJob.Job.Args.OfType()) + { + + workflow.Workflow.Manifest = _workflowAccessor.GetWorkflowManifestAsync(workflow.Workflow).GetAwaiter().GetResult(); + } + } + + public void OnCreating(CreatingContext filterContext) + { + foreach(var workflow in filterContext.Job.Args.OfType()) + { + workflow.Manifest = null; + } + + foreach (var workflow in filterContext.Job.Args.OfType()) + { + workflow.Workflow.Manifest = null; + } + + + } + + public void OnPerformed(PerformedContext filterContext) + { + + foreach (var workflow in filterContext.BackgroundJob.Job.Args.Where(c => c is IWorkflow)) + { + + } + } + + public void OnPerforming(PerformingContext filterContext) + { + foreach (var workflow in filterContext.BackgroundJob.Job.Args.OfType()) + { + workflow.Manifest = _workflowAccessor.GetWorkflowManifestAsync(workflow).GetAwaiter().GetResult(); + } + + foreach (var workflow in filterContext.BackgroundJob.Job.Args.OfType()) + { + + workflow.Workflow.Manifest = _workflowAccessor.GetWorkflowManifestAsync(workflow.Workflow).GetAwaiter().GetResult(); + } + } + } public static class HangfireExtensions { diff --git a/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs b/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs index 4c6772d..665e8ba 100644 --- a/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs +++ b/src/WorkflowEngine.Hangfire/HangfireWorkflowExecutor.cs @@ -36,7 +36,7 @@ public static string TriggerAsync(this IBackgroundJobClient bac where TTriggerContext : TriggerContext { var job = backgroundJobClient.Enqueue( - (executor) => executor.TriggerAsync(trigger,null)); + (executor) => executor.TriggerAsync(trigger, null)); return job; @@ -85,43 +85,69 @@ public HangfireWorkflowExecutor(IWorkflowAccessor workflowAccessor, IHangfireAct public async ValueTask ExecuteAsync(IRunContext run, IWorkflow workflow, IAction action, PerformContext context) { - //TODO - avoid sending all workflow over hangfire, so we should lookup the manifest here if not set on workflow form its ID. - workflow.Manifest ??= await workflowAccessor.GetWorkflowManifestAsync(workflow); + try + { + runContextAccessor.RunContext = run; + arrayContext.JobId = context.BackgroundJob.Id; - runContextAccessor.RunContext = run; - arrayContext.JobId = context.BackgroundJob.Id; - try - { var result = await actionExecutor.ExecuteAsync(run, workflow, action); - - await outputRepository.AddEvent(run, workflow, action, ActionCompletedEvent.FromAction(result,action,context.BackgroundJob.Id)); + + + await outputRepository.AddEvent(run, workflow, action, ActionCompletedEvent.FromAction(result, action, context.BackgroundJob.Id)); if (result != null) { - var next = await executor.GetNextAction(run, workflow, result); - + var next = await executor.GetNextAction(run, workflow, action, result); + await hangfireActionExecutorResultHandler.InspectAsync(run, workflow, result, next); if (next != null) { - // This is the hangfire ID thingy, this we would like to save - var workflowRunId = backgroundJobClient.Enqueue( - (executor) => executor.ExecuteAsync(run, workflow, next, null)); - // result. + + if (result.DelayNextAction.HasValue) + { + + var workflowRunId = backgroundJobClient.Schedule( + (executor) => executor.ExecuteAsync(run, workflow, next, null),result.DelayNextAction.Value); + } + else + { + var workflowRunId = backgroundJobClient.Enqueue( + (executor) => executor.ExecuteAsync(run, workflow, next, null)); + } + + + } - else if (workflow.Manifest.Actions.FindParentAction(action.Key) is ForLoopActionMetadata scope) + else if (workflow.Manifest.Actions.FindParentAction(action.Key) is IScopedActionMetadata scope) { - var scopeAction = run.CopyTo(new Action { ScopeMoveNext = true, Type = scope.Type, Key = action.Key.Substring(0, action.Key.LastIndexOf('.')), ScheduledTime = DateTimeOffset.UtcNow }); - - var workflowRunId = backgroundJobClient.Enqueue( - (executor) => executor.ExecuteAsync(run, workflow, scopeAction, null)); + var scopeAction = run.CopyTo(new Action { + Index = action.Index, + ScopeMoveNext = true, + Type = scope.Type, + Key = action.Key.Substring(0, action.Key.LastIndexOf('.')), + ScheduledTime = DateTimeOffset.UtcNow +(result.DelayNextAction ?? TimeSpan.Zero) }); + + if (result.DelayNextAction != null) + { + + var workflowRunId = backgroundJobClient.Schedule( + (executor) => executor.ExecuteAsync(run, workflow, scopeAction, null),result.DelayNextAction.Value); + } + else + { + + + var workflowRunId = backgroundJobClient.Enqueue( + (executor) => executor.ExecuteAsync(run, workflow, scopeAction, null)); + } //await outputRepository.EndScope(run, workflow, action); } else if (result.Status == "Failed" && result.ReThrow) { - await outputRepository.AddEvent(run, workflow, action, WorkflowEvent.CreateFinishedEvent(context.BackgroundJob.Id,result)); + await outputRepository.AddEvent(run, workflow, action, WorkflowEvent.CreateFinishedEvent(context.BackgroundJob.Id, result)); throw new InvalidOperationException("Action failed: " + result.FailedReason) { Data = { ["ActionResult"] = result } }; } else @@ -138,28 +164,29 @@ public async ValueTask ExecuteAsync(IRunContext run, IWorkflow workflow, throw; } } - + + public async ValueTask TriggerAsync(ITriggerContext triggerContext) + { + return await TriggerAsync(triggerContext, null); + + } + /// /// Runs on the background process in hangfire /// /// /// - public async ValueTask TriggerAsync(ITriggerContext triggerContext, PerformContext context) - { - //TODO - avoid sending all workflow over hangfire, - triggerContext.Workflow.Manifest ??= await workflowAccessor.GetWorkflowManifestAsync(triggerContext.Workflow); - + public async ValueTask TriggerAsync(ITriggerContext triggerContext, PerformContext context = null) + { + triggerContext.RunId = triggerContext.RunId == Guid.Empty ? Guid.NewGuid() : triggerContext.RunId; - triggerContext.JobId = context.BackgroundJob.Id; - + triggerContext.JobId = context?.BackgroundJob.Id; + runContextAccessor.RunContext = triggerContext; var action = await executor.Trigger(triggerContext); if (action != null) - { - //TODO - avoid sending all workflow over hangfire, so we should wipe the workflow.manifest before scheduling and restore it after. - triggerContext.Workflow.Manifest = null; - + { var a = backgroundJobClient.Enqueue( (executor) => executor.ExecuteAsync(triggerContext, triggerContext.Workflow, action, null)); } diff --git a/src/WorkflowEngine.Hangfire/IHangfireActionExecutor.cs b/src/WorkflowEngine.Hangfire/IHangfireActionExecutor.cs index 0f897a6..f177d00 100644 --- a/src/WorkflowEngine.Hangfire/IHangfireActionExecutor.cs +++ b/src/WorkflowEngine.Hangfire/IHangfireActionExecutor.cs @@ -1,4 +1,4 @@ -using Hangfire; +using Hangfire; using Hangfire.Server; using System.ComponentModel; using System.Threading.Tasks; @@ -9,6 +9,7 @@ namespace WorkflowEngine public interface IHangfireActionExecutor { [JobDisplayName("Action: {2:Type}, RunId={0:Id} workflow={1:Id}")] + [HangfireWorkflowManifestJobFilter] public ValueTask ExecuteAsync(IRunContext context, IWorkflow workflow, IAction action, PerformContext hangfireContext); } diff --git a/src/WorkflowEngine.Hangfire/IHangfireWorkflowExecutor.cs b/src/WorkflowEngine.Hangfire/IHangfireWorkflowExecutor.cs index a205d74..03175ca 100644 --- a/src/WorkflowEngine.Hangfire/IHangfireWorkflowExecutor.cs +++ b/src/WorkflowEngine.Hangfire/IHangfireWorkflowExecutor.cs @@ -9,7 +9,11 @@ namespace WorkflowEngine public interface IHangfireWorkflowExecutor { [JobDisplayName("Trigger: {0:Workflow} RunId={0:Id}")] + [HangfireWorkflowManifestJobFilter] public ValueTask TriggerAsync(ITriggerContext triggercontext, PerformContext context); + [JobDisplayName("Trigger: {0:Workflow} RunId={0:Id}")] + [HangfireWorkflowManifestJobFilter] + public ValueTask TriggerAsync(ITriggerContext triggercontext); } } diff --git a/src/WorkflowEngine.Hangfire/ScheduledWorkflowTrigger.cs b/src/WorkflowEngine.Hangfire/ScheduledWorkflowTrigger.cs index 9466997..0cf1c74 100644 --- a/src/WorkflowEngine.Hangfire/ScheduledWorkflowTrigger.cs +++ b/src/WorkflowEngine.Hangfire/ScheduledWorkflowTrigger.cs @@ -47,12 +47,8 @@ public Task Trigger(string externalid, bool create, DateTimeOffset time, Type = workflow.Manifest.Triggers.FirstOrDefault().Value.Type, Key = workflow.Manifest.Triggers.FirstOrDefault().Key }; - - //TODO - avoid sending all workflow over hangfire, - workflow.Manifest = null; - var job = _backgroundJobClient.Schedule((executor) => executor.TriggerAsync( new TriggerContext { Workflow = workflow, Trigger = trigger , },null), time); diff --git a/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs b/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs index 7b449c3..9aa75de 100644 --- a/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs +++ b/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs @@ -37,7 +37,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) if (!trigger.Equals(default(KeyValuePair))) { - workflow.Manifest = null; + jobs.AddOrUpdate(workflow.Id.ToString(), (executor) => executor.TriggerAsync(new TriggerContext From 73d2afd15f71f5a98a627542e7e3852c696fbbe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Mon, 27 Nov 2023 14:29:28 +0100 Subject: [PATCH 21/34] fix: exposed HangfireWorkflowManifestJobFilter to DI --- src/WorkflowEngine.Hangfire/DependencyInjectionExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/WorkflowEngine.Hangfire/DependencyInjectionExtensions.cs b/src/WorkflowEngine.Hangfire/DependencyInjectionExtensions.cs index 645a748..8b10b64 100644 --- a/src/WorkflowEngine.Hangfire/DependencyInjectionExtensions.cs +++ b/src/WorkflowEngine.Hangfire/DependencyInjectionExtensions.cs @@ -32,6 +32,7 @@ public static IServiceCollection AddWorkflowEngine(this ISer services.AddTransient(); services.AddTransient(); + services.AddTransient(); return services; } From 9930a16c57452bc6a308a6aea3dd03ec07e23f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Mon, 27 Nov 2023 14:34:08 +0100 Subject: [PATCH 22/34] fix: cast should be to nullable timespan --- src/WorkflowEngine.Core/ActionExecutor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WorkflowEngine.Core/ActionExecutor.cs b/src/WorkflowEngine.Core/ActionExecutor.cs index 0e41de1..82fb27b 100644 --- a/src/WorkflowEngine.Core/ActionExecutor.cs +++ b/src/WorkflowEngine.Core/ActionExecutor.cs @@ -94,7 +94,7 @@ public async ValueTask ExecuteAsync(IRunContext context, IWorkflo Key = action.Key, Status = "Succeded", Result = actionResult, - DelayNextAction = (actionImplementation is IWaitAction) ? (TimeSpan)actionResult: null + DelayNextAction = (actionImplementation is IWaitAction) ? (TimeSpan?)actionResult: null }; await _outputsRepository.AddAsync(context, workflow, action, result); From b60e3f4a9bd514d04fe347349f5f01a14ce0b8de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Wed, 14 Feb 2024 09:03:53 +0100 Subject: [PATCH 23/34] fix: the runAtStartup to trigger the correct flow --- src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs b/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs index 63d311d..b793cfe 100644 --- a/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs +++ b/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs @@ -42,7 +42,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) workflow.Manifest = null; jobs.AddOrUpdate(workflow.Id.ToString() + trigger.Key, - (System.Linq.Expressions.Expression>)((executor) => executor.TriggerAsync(new TriggerContext + (System.Linq.Expressions.Expression>)((executor) => executor.TriggerAsync(new TriggerContext { Workflow = workflow, Trigger = new Trigger @@ -55,7 +55,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) }, null)), trigger.Value.Inputs["cronExpression"] as string,GetTimeZone(trigger) ); if (first && trigger.Value.Inputs.ContainsKey("runAtStartup") && (bool)trigger.Value.Inputs["runAtStartup"]) - jobs.Trigger(workflow.Id.ToString()); + jobs.Trigger(workflow.Id.ToString() + trigger.Key); } } } From 38529a67a913b5e7116e386274dd624eda593481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Wed, 14 Feb 2024 09:06:50 +0100 Subject: [PATCH 24/34] fix: added stronly typed input support --- src/WorkflowEngine.Core/Action.cs | 8 ++- src/WorkflowEngine.Core/ActionExecutor.cs | 35 +++---------- .../ActionImplementationMetadata.cs | 52 +++++++++++++++++-- src/WorkflowEngine.Core/IAction.cs | 8 ++- .../IActionImplementation.cs | 11 +++- .../IActionImplementationExtenssions.cs | 8 ++- .../IActionImplementationMetadata.cs | 10 ++-- src/WorkflowEngine.Core/WorkflowActions.cs | 4 ++ src/WorkflowEngine.Hangfire/ForloopAction.cs | 4 +- 9 files changed, 96 insertions(+), 44 deletions(-) diff --git a/src/WorkflowEngine.Core/Action.cs b/src/WorkflowEngine.Core/Action.cs index 9612581..7bf4156 100644 --- a/src/WorkflowEngine.Core/Action.cs +++ b/src/WorkflowEngine.Core/Action.cs @@ -3,14 +3,18 @@ namespace WorkflowEngine.Core { - public class Action : IAction, IFormattable + public class Action : Action>, IAction + { + + } + public class Action : IFormattable, IAction { public DateTimeOffset ScheduledTime { get; set; } public string Type { get; set; } public string Key { get; set; } public Guid RunId { get; set; } - public IDictionary Inputs { get; set; } + public TInput Inputs { get; set; } public int Index { get; set; } public bool ScopeMoveNext { get; set; } diff --git a/src/WorkflowEngine.Core/ActionExecutor.cs b/src/WorkflowEngine.Core/ActionExecutor.cs index 0e41de1..88f3375 100644 --- a/src/WorkflowEngine.Core/ActionExecutor.cs +++ b/src/WorkflowEngine.Core/ActionExecutor.cs @@ -67,35 +67,12 @@ public async ValueTask ExecuteAsync(IRunContext context, IWorkflo } _scopeContext.Scope = action.Key; action.Inputs = await _expressionEngine.ResolveInputs(actionMetadata, _logger); - - { - //if (workflow.Manifest.Actions.FindParentAction(action.Key) is ForLoopActionMetadata parent) - //{ - // await outputsRepository.AddArrayInput(context, workflow, action); - //} - //else - //if (actionMetadata is ForLoopActionMetadata) - //{ - // await outputsRepository.StartScope(context, workflow, action); - //} - //else - { - await _outputsRepository.AddInput(context, workflow, action); - } - } - - var actionImplementation = _serviceProvider.GetRequiredService(_implementations[actionMetadata.Type].Implementation) as IActionImplementation; - - - var actionResult = await actionImplementation.ExecuteAsync(context, workflow, action); - - var result = new ActionResult - { - Key = action.Key, - Status = "Succeded", - Result = actionResult, - DelayNextAction = (actionImplementation is IWaitAction) ? (TimeSpan)actionResult: null - }; + + await _outputsRepository.AddInput(context, workflow, action); + + var metadata = _implementations[actionMetadata.Type]; + var result = await metadata.ExecuteAsync(_serviceProvider, context, workflow, action); + await _outputsRepository.AddAsync(context, workflow, action, result); diff --git a/src/WorkflowEngine.Core/ActionImplementationMetadata.cs b/src/WorkflowEngine.Core/ActionImplementationMetadata.cs index ffe8ed3..c3d5209 100644 --- a/src/WorkflowEngine.Core/ActionImplementationMetadata.cs +++ b/src/WorkflowEngine.Core/ActionImplementationMetadata.cs @@ -1,14 +1,60 @@ -using System; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using System; +using System.Threading.Tasks; namespace WorkflowEngine.Core { - public class ActionImplementationMetadata : IActionImplementationMetadata + public class ActionImplementationMetadata : IActionImplementationMetadata where T: IActionImplementation { public string Type { get; set; } public Type Implementation => typeof(T); + + public async ValueTask ExecuteAsync(IServiceProvider services, IRunContext context, IWorkflow workflow, IAction action) + { + var implementation = services.GetRequiredService(Implementation) as IActionImplementation; + + var actionResult = await implementation.ExecuteAsync(context, workflow, action); + + var result = new ActionResult + { + Key = action.Key, + Status = "Succeded", + Result = actionResult, + DelayNextAction = (implementation is IWaitAction) ? (TimeSpan) actionResult : null + }; + return result; + } + } + + public class ActionImplementationMetadata : IActionImplementationMetadata + where T : IActionImplementation + { + public string Type { get; set; } + public Type Implementation => typeof(T); + + public async ValueTask ExecuteAsync(IServiceProvider services, IRunContext context,IWorkflow workflow,IAction action ) + { + + var implementation = services.GetRequiredService(Implementation) as IActionImplementation; + + var payload = JsonConvert.SerializeObject(action); + var typedAction = JsonConvert.DeserializeObject>(payload); + + var actionResult= await implementation.ExecuteAsync(context, workflow, typedAction); + + var result = new ActionResult + { + Key = action.Key, + Status = "Succeded", + Result = actionResult, + DelayNextAction = (implementation is IWaitAction) ? (TimeSpan) actionResult : null + }; + return result; + } } - + } diff --git a/src/WorkflowEngine.Core/IAction.cs b/src/WorkflowEngine.Core/IAction.cs index 3fa02dd..ed13c71 100644 --- a/src/WorkflowEngine.Core/IAction.cs +++ b/src/WorkflowEngine.Core/IAction.cs @@ -3,15 +3,19 @@ namespace WorkflowEngine.Core { - public interface IAction : IRunContext + public interface IAction: IRunContext { public DateTimeOffset ScheduledTime { get; set; } public string Type { get; set; } string Key { get; } - IDictionary Inputs { get; set; } + TInput Inputs { get; set; } public int Index { get; set; } public bool ScopeMoveNext { get; set; } + } + public interface IAction : IAction> + { + } diff --git a/src/WorkflowEngine.Core/IActionImplementation.cs b/src/WorkflowEngine.Core/IActionImplementation.cs index 418f805..e000804 100644 --- a/src/WorkflowEngine.Core/IActionImplementation.cs +++ b/src/WorkflowEngine.Core/IActionImplementation.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; namespace WorkflowEngine.Core { @@ -10,7 +10,14 @@ public interface IActionImplementation } - + public interface IActionImplementation + { + + + ValueTask ExecuteAsync(IRunContext context, IWorkflow workflow, IAction action); + + + } } diff --git a/src/WorkflowEngine.Core/IActionImplementationExtenssions.cs b/src/WorkflowEngine.Core/IActionImplementationExtenssions.cs index 1068fae..0df8d58 100644 --- a/src/WorkflowEngine.Core/IActionImplementationExtenssions.cs +++ b/src/WorkflowEngine.Core/IActionImplementationExtenssions.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; namespace WorkflowEngine.Core { @@ -10,6 +10,12 @@ public static IServiceCollection AddAction(this IServiceCollection services, return services.AddTransient() .AddSingleton< IActionImplementationMetadata>(new ActionImplementationMetadata { Type = type ?? typeof(T).Name }); } + public static IServiceCollection AddAction(this IServiceCollection services, string type = null) + where T : class, IActionImplementation + { + return services.AddTransient() + .AddSingleton(new ActionImplementationMetadata { Type = type ?? typeof(T).Name }); + } public static IServiceCollection AddWorkflow(this IServiceCollection services) where T :class, IWorkflow { diff --git a/src/WorkflowEngine.Core/IActionImplementationMetadata.cs b/src/WorkflowEngine.Core/IActionImplementationMetadata.cs index 0056f08..c4f2dff 100644 --- a/src/WorkflowEngine.Core/IActionImplementationMetadata.cs +++ b/src/WorkflowEngine.Core/IActionImplementationMetadata.cs @@ -1,12 +1,16 @@ -using System; +using System; +using System.Threading.Tasks; namespace WorkflowEngine.Core { public interface IActionImplementationMetadata{ - string Type { get; } + + string Type { get; } Type Implementation { get; } + + ValueTask ExecuteAsync(IServiceProvider services, IRunContext context, IWorkflow workflow, IAction action); } - + } diff --git a/src/WorkflowEngine.Core/WorkflowActions.cs b/src/WorkflowEngine.Core/WorkflowActions.cs index faa8dd9..7340aa2 100644 --- a/src/WorkflowEngine.Core/WorkflowActions.cs +++ b/src/WorkflowEngine.Core/WorkflowActions.cs @@ -5,6 +5,10 @@ namespace WorkflowEngine.Core { public class ForLoopActionMetadata : ActionMetadata, IScopedActionMetadata { + public ForLoopActionMetadata() + { + Type = "Foreach"; + } public object ForEach { get; set; } public int ConcurrentCount { get; set; } = 1; diff --git a/src/WorkflowEngine.Hangfire/ForloopAction.cs b/src/WorkflowEngine.Hangfire/ForloopAction.cs index 767f219..6901f99 100644 --- a/src/WorkflowEngine.Hangfire/ForloopAction.cs +++ b/src/WorkflowEngine.Hangfire/ForloopAction.cs @@ -54,7 +54,7 @@ public async ValueTask ExecuteAsync(IRunContext context, IWorkflow workf if (nextactionmetadata.Equals(default(KeyValuePair))) throw new Exception("No action with no runafter could be found"); - var nextaction = context.CopyTo(new Action { Type = nextactionmetadata.Value.Type, Key = $"{action.Key}.{nextactionmetadata.Key}", ScheduledTime = DateTimeOffset.UtcNow, Index = action.Index }); + var nextaction = context.CopyTo(new Action { Type = nextactionmetadata.Value.Type, Key = $"{action.Key}.{nextactionmetadata.Key}", ScheduledTime = DateTimeOffset.UtcNow, Index = action.Index+1 }); var a = backgroundJobClient.ContinueJobWith(arrayContext.JobId, (executor) => executor.ExecuteAsync(context, workflow, nextaction, null)); @@ -70,7 +70,7 @@ public async ValueTask ExecuteAsync(IRunContext context, IWorkflow workf if (nextactionmetadata.Equals(default(KeyValuePair))) throw new Exception("No action with no runafter could be found"); - var nextaction = context.CopyTo(new Action { Type = nextactionmetadata.Value.Type, Key = $"{action.Key}.{nextactionmetadata.Key}", ScheduledTime = DateTimeOffset.UtcNow, Index = action.Index }); + var nextaction = context.CopyTo(new Action { Type = nextactionmetadata.Value.Type, Key = $"{action.Key}.{nextactionmetadata.Key}", ScheduledTime = DateTimeOffset.UtcNow, Index = action.Index+1 }); var a = backgroundJobClient.ContinueJobWith(arrayContext.JobId, From 3407b7c361dab595511c7e9b78dee1c6482c94db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Thu, 25 Apr 2024 18:36:33 +0200 Subject: [PATCH 25/34] fix: added .net 8 --- src/WorkflowEngine.Core/WorkflowEngine.Core.csproj | 10 +++++++--- .../WorkflowEngine.Hangfire.csproj | 7 +++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj b/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj index ff14cc0..8e663cd 100644 --- a/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj +++ b/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj @@ -1,7 +1,7 @@ - + - netcoreapp3.1;net6.0 + netcoreapp3.1;net6.0;net8.0 Delegate.WorkflowEngine.Core Delegate A/S @@ -23,5 +23,9 @@ - + + + + + diff --git a/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj b/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj index d31c18c..a44661c 100644 --- a/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj +++ b/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj @@ -1,7 +1,7 @@ - + - netcoreapp3.1;net6.0 + netcoreapp3.1;net6.0;net8.0 Delegate.WorkflowEngine.Hangfire Delegate A/S @@ -21,6 +21,9 @@ + + + From 40ccc4dbb10fb013e62c62e75e5d307d7b2ba0ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Thu, 25 Apr 2024 18:47:26 +0200 Subject: [PATCH 26/34] fix: fix build for .net 8 --- .github/workflows/build.yml | 23 ++++++++++++++++------- .github/workflows/release.yml | 14 +++++++++----- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 194dadf..6cf8583 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,31 +13,40 @@ jobs: name: Testing strategy: matrix: - dotnet: [ 'net6.0' ] + include: + - framework: "net6.0" + version: 6.0.x + - framework: net8.0 + version: 8.0.x + steps: - name: Checkout code base uses: actions/checkout@v2 - uses: actions/setup-dotnet@v1 with: - dotnet-version: '6.0.x' + dotnet-version: ${{ matrix.version }} - name: Run tests - run: dotnet test --verbosity normal -f ${{ matrix.dotnet }} + run: dotnet test --verbosity normal -f ${{ matrix.framework }} build: runs-on: windows-latest name: Building strategy: matrix: - dotnet: ['net6.0'] + include: + - framework: "net6.0" + version: 6.0.x + - framework: net8.0 + version: 8.0.x steps: - name: Checkout code base uses: actions/checkout@v2 - uses: actions/setup-dotnet@v1 with: - dotnet-version: '6.0.x' + dotnet-version: ${{ matrix.version }} - name: Cleaning run: dotnet clean @@ -46,12 +55,12 @@ jobs: run: dotnet restore WorkflowEngine.sln - name: Build solution - run: dotnet build -o ../build/${{ matrix.dotnet }} -c Release --no-restore -m:1 -f ${{ matrix.dotnet }} + run: dotnet build -o ../build/${{ matrix.dotnet }} -c Release --no-restore -m:1 -f ${{ matrix.framework }} - name: Archive build to artifacts uses: actions/upload-artifact@v2 with: name: build path: | - build/${{ matrix.dotnet }}/* + build/${{ matrix.framework }}/* retention-days: 5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 176c339..3500f77 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,18 +11,22 @@ jobs: runs-on: windows-latest name: Testing strategy: - matrix: - dotnet: [ 'net6.0' ] + matrix: + include: + - framework: "net6.0" + version: 6.0.x + - framework: net8.0 + version: 8.0.x steps: - name: Checkout code base uses: actions/checkout@v2 - uses: actions/setup-dotnet@v1 with: - dotnet-version: '6.0.x' + dotnet-version: ${{ matrix.version }} - name: Run tests - run: dotnet test --verbosity normal -f ${{ matrix.dotnet }} + run: dotnet test --verbosity normal -f ${{ matrix.framework }} release: @@ -42,7 +46,7 @@ jobs: - uses: actions/setup-dotnet@v1 with: - dotnet-version: '6.0.x' + dotnet-version: '8.0.x' - name: Setup Node.js uses: actions/setup-node@v1 From 8ca2d036bb86fe2323403a5c4a78923591e95f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Thu, 25 Apr 2024 18:55:01 +0200 Subject: [PATCH 27/34] fix: use 8.0 for sdk --- .github/workflows/build.yml | 12 ++++++------ .github/workflows/release.yml | 10 +++++----- .../WorkflowEngine.DemoApp.csproj | 2 +- src/WorkflowEngine.Core/WorkflowEngine.Core.csproj | 4 ++-- .../WorkflowEngine.Hangfire.csproj | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6cf8583..461dd84 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,11 +21,11 @@ jobs: steps: - name: Checkout code base - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - uses: actions/setup-dotnet@v1 + - uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ matrix.version }} + dotnet-version: 8.0.x - name: Run tests run: dotnet test --verbosity normal -f ${{ matrix.framework }} @@ -42,11 +42,11 @@ jobs: version: 8.0.x steps: - name: Checkout code base - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - uses: actions/setup-dotnet@v1 + - uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ matrix.version }} + dotnet-version: 8.0.x - name: Cleaning run: dotnet clean diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3500f77..cbc4188 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,11 +19,11 @@ jobs: version: 8.0.x steps: - name: Checkout code base - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - uses: actions/setup-dotnet@v1 + - uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ matrix.version }} + dotnet-version: 8.0.x - name: Run tests run: dotnet test --verbosity normal -f ${{ matrix.framework }} @@ -36,7 +36,7 @@ jobs: - test steps: - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup Git for Windows' minimal SDK uses: git-for-windows/setup-git-for-windows-sdk@v1 @@ -44,7 +44,7 @@ jobs: - name: Print GIT verison run: git --version - - uses: actions/setup-dotnet@v1 + - uses: actions/setup-dotnet@v4 with: dotnet-version: '8.0.x' diff --git a/apps/WorkflowEngine.DemoApp/WorkflowEngine.DemoApp.csproj b/apps/WorkflowEngine.DemoApp/WorkflowEngine.DemoApp.csproj index 2bd5c6f..e2b75c8 100644 --- a/apps/WorkflowEngine.DemoApp/WorkflowEngine.DemoApp.csproj +++ b/apps/WorkflowEngine.DemoApp/WorkflowEngine.DemoApp.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj b/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj index 8e663cd..13be400 100644 --- a/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj +++ b/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj @@ -25,7 +25,7 @@ - - + + diff --git a/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj b/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj index a44661c..9ffd069 100644 --- a/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj +++ b/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj @@ -10,8 +10,8 @@ WorkflowEngine - - + + From f3ca4265067ee4cc6aed917655b7bf4458e12935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Thu, 25 Apr 2024 19:06:11 +0200 Subject: [PATCH 28/34] fix: removed net3.1 --- src/WorkflowEngine.Core/WorkflowEngine.Core.csproj | 2 +- src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj b/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj index 13be400..6bfcbf7 100644 --- a/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj +++ b/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1;net6.0;net8.0 + net6.0;net8.0 Delegate.WorkflowEngine.Core Delegate A/S diff --git a/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj b/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj index 9ffd069..a0c47e2 100644 --- a/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj +++ b/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1;net6.0;net8.0 + net6.0;net8.0 Delegate.WorkflowEngine.Hangfire Delegate A/S From 5be6a321b02a77a1ec0514248b45841a06237ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Wed, 29 May 2024 22:39:47 +0200 Subject: [PATCH 29/34] updated deps --- .../WorkflowEngine.Core.csproj | 18 +++--------------- .../WorkflowEngine.Hangfire.csproj | 2 +- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj b/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj index 6bfcbf7..ecd5ffe 100644 --- a/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj +++ b/src/WorkflowEngine.Core/WorkflowEngine.Core.csproj @@ -10,22 +10,10 @@ - - - - - - - - - - - - - - - + + + diff --git a/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj b/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj index a0c47e2..c27bf9e 100644 --- a/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj +++ b/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj @@ -19,7 +19,7 @@ - + From c9a6411a2265e60b3839b989c9eb97fcdcc9af2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Thu, 20 Jun 2024 20:30:13 +0200 Subject: [PATCH 30/34] feat: Added typed workflow support --- src/WorkflowEngine.Core/IWorkflow.cs | 6 +++++- src/WorkflowEngine.Core/IWorkflowExecutor.cs | 3 +++ src/WorkflowEngine.Core/Workflow.cs | 7 ++++++- .../WorkflowStarterBackgroundJob.cs | 1 + 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/WorkflowEngine.Core/IWorkflow.cs b/src/WorkflowEngine.Core/IWorkflow.cs index b9f3762..1fd1736 100644 --- a/src/WorkflowEngine.Core/IWorkflow.cs +++ b/src/WorkflowEngine.Core/IWorkflow.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace WorkflowEngine.Core { @@ -14,5 +14,9 @@ public interface IWorkflow // Documentet } + public interface IWorkflowInputs where TInput : class + { + + } } diff --git a/src/WorkflowEngine.Core/IWorkflowExecutor.cs b/src/WorkflowEngine.Core/IWorkflowExecutor.cs index 05ee87c..204a645 100644 --- a/src/WorkflowEngine.Core/IWorkflowExecutor.cs +++ b/src/WorkflowEngine.Core/IWorkflowExecutor.cs @@ -1,4 +1,5 @@ using System; +using System.Security.Claims; using System.Threading.Tasks; namespace WorkflowEngine.Core @@ -16,6 +17,8 @@ public T CopyTo(T other) other.PrincipalId = PrincipalId; return other; } + + } public interface IWorkflowExecutor { diff --git a/src/WorkflowEngine.Core/Workflow.cs b/src/WorkflowEngine.Core/Workflow.cs index 2b4fda8..98c8553 100644 --- a/src/WorkflowEngine.Core/Workflow.cs +++ b/src/WorkflowEngine.Core/Workflow.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; namespace WorkflowEngine.Core @@ -24,6 +24,11 @@ public string ToString(string format, IFormatProvider formatProvider) return string.Empty; } } + public class Workflow : Workflow, IWorkflowInputs + where TInput : class + { + + } } diff --git a/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs b/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs index b793cfe..735b93a 100644 --- a/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs +++ b/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs @@ -44,6 +44,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) jobs.AddOrUpdate(workflow.Id.ToString() + trigger.Key, (System.Linq.Expressions.Expression>)((executor) => executor.TriggerAsync(new TriggerContext { + PrincipalId = "1b714972-8d0a-4feb-b166-08d93c6ae328", Workflow = workflow, Trigger = new Trigger { From f226b11f88d3862fb66c5f0973cb36d720f9a716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Tue, 25 Jun 2024 09:30:23 +0200 Subject: [PATCH 31/34] fix: abstracted some props to common baseclass --- .../ActionImplementationMetadata.cs | 37 ++++++++++++++++--- .../IActionImplementation.cs | 8 +++- .../IActionImplementationExtenssions.cs | 29 +++++++++++++-- src/WorkflowEngine.Core/Workflow.cs | 30 +++++++++++++++ 4 files changed, 93 insertions(+), 11 deletions(-) diff --git a/src/WorkflowEngine.Core/ActionImplementationMetadata.cs b/src/WorkflowEngine.Core/ActionImplementationMetadata.cs index c3d5209..8f79e10 100644 --- a/src/WorkflowEngine.Core/ActionImplementationMetadata.cs +++ b/src/WorkflowEngine.Core/ActionImplementationMetadata.cs @@ -5,12 +5,34 @@ namespace WorkflowEngine.Core { - public class ActionImplementationMetadata : IActionImplementationMetadata - where T: IActionImplementation + public class ActionImplementationMetadata { public string Type { get; set; } - public Type Implementation => typeof(T); + public Type Implementation { get; protected set; } + + public static IActionImplementationMetadata FromType(Type type, string name) + { + var metadata = Activator.CreateInstance(typeof(ActionImplementationMetadata<>).MakeGenericType(type)) as ActionImplementationMetadata; + metadata.Type = name ?? type.Name; + + return metadata as IActionImplementationMetadata; + } + public static IActionImplementationMetadata FromType(Type type,Type inputType, string name) + { + var metadata = Activator.CreateInstance(typeof(ActionImplementationMetadata<,>).MakeGenericType(type,inputType)) as ActionImplementationMetadata; + metadata.Type = name ?? type.Name; + return metadata as IActionImplementationMetadata; + } + } + public class ActionImplementationMetadata : ActionImplementationMetadata,IActionImplementationMetadata + where T: IActionImplementation + { + public ActionImplementationMetadata() + { + Implementation = typeof(T); + } + public async ValueTask ExecuteAsync(IServiceProvider services, IRunContext context, IWorkflow workflow, IAction action) { var implementation = services.GetRequiredService(Implementation) as IActionImplementation; @@ -28,11 +50,14 @@ public async ValueTask ExecuteAsync(IServiceProvider services, IRu } } - public class ActionImplementationMetadata : IActionImplementationMetadata + public class ActionImplementationMetadata : ActionImplementationMetadata, IActionImplementationMetadata where T : IActionImplementation { - public string Type { get; set; } - public Type Implementation => typeof(T); + public ActionImplementationMetadata() + { + Implementation = typeof(T); + } + public async ValueTask ExecuteAsync(IServiceProvider services, IRunContext context,IWorkflow workflow,IAction action ) { diff --git a/src/WorkflowEngine.Core/IActionImplementation.cs b/src/WorkflowEngine.Core/IActionImplementation.cs index e000804..69cde9f 100644 --- a/src/WorkflowEngine.Core/IActionImplementation.cs +++ b/src/WorkflowEngine.Core/IActionImplementation.cs @@ -2,7 +2,11 @@ namespace WorkflowEngine.Core { - public interface IActionImplementation + public interface IActionForRegistration + { + + } + public interface IActionImplementation : IActionForRegistration { @@ -10,7 +14,7 @@ public interface IActionImplementation } - public interface IActionImplementation + public interface IActionImplementation : IActionForRegistration { diff --git a/src/WorkflowEngine.Core/IActionImplementationExtenssions.cs b/src/WorkflowEngine.Core/IActionImplementationExtenssions.cs index 0df8d58..0b8d494 100644 --- a/src/WorkflowEngine.Core/IActionImplementationExtenssions.cs +++ b/src/WorkflowEngine.Core/IActionImplementationExtenssions.cs @@ -1,20 +1,43 @@ using Microsoft.Extensions.DependencyInjection; +using System.Linq; +using System; namespace WorkflowEngine.Core { public static class IActionImplementationExtenssions { public static IServiceCollection AddAction(this IServiceCollection services, string type = null) - where T: class, IActionImplementation + where T: class, IActionForRegistration { + + // Check if T implements IActionImplementation + var interfaceType = typeof(T).GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IActionImplementation<>)); + if (interfaceType != null) + { + // Extract the TInput type argument + var inputType = interfaceType.GetGenericArguments()[0]; + + // Get the method definition for AddAction + var method = typeof(IActionImplementationExtenssions).GetMethod(nameof(AddAction), 2, + new Type[] { typeof(IServiceCollection), typeof(string) }).MakeGenericMethod(new Type[] { typeof(T), inputType }); + + // Invoke AddAction using reflection + return (IServiceCollection) method.Invoke(null, new object[] { services, type }); + } + + return services.AddTransient() - .AddSingleton< IActionImplementationMetadata>(new ActionImplementationMetadata { Type = type ?? typeof(T).Name }); + .AddSingleton(ActionImplementationMetadata.FromType(typeof(T),type)); } + + + + public static IServiceCollection AddAction(this IServiceCollection services, string type = null) where T : class, IActionImplementation { return services.AddTransient() - .AddSingleton(new ActionImplementationMetadata { Type = type ?? typeof(T).Name }); + .AddSingleton(ActionImplementationMetadata.FromType(typeof(T),typeof(TInput), type)); } public static IServiceCollection AddWorkflow(this IServiceCollection services) where T :class, IWorkflow diff --git a/src/WorkflowEngine.Core/Workflow.cs b/src/WorkflowEngine.Core/Workflow.cs index 98c8553..4d8822f 100644 --- a/src/WorkflowEngine.Core/Workflow.cs +++ b/src/WorkflowEngine.Core/Workflow.cs @@ -1,5 +1,9 @@ +using Newtonsoft.Json; using System; +using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; namespace WorkflowEngine.Core { @@ -28,6 +32,32 @@ public class Workflow : Workflow, IWorkflowInputs where TInput : class { + public static IDictionary ForwardInputs() + { + var targetkeys = typeof(TTarget).GetProperties().Select(x => x.GetCustomAttribute().PropertyName).ToHashSet(); + return typeof(TInput).GetProperties() + .Where(x => targetkeys.Contains(x.GetCustomAttribute().PropertyName)) + .ToDictionary(x => x.GetCustomAttribute().PropertyName, + v => $"@triggerBody()?.payload?.values?.{v.GetCustomAttribute().PropertyName}" as object); + } + + public static T WorkflowInput(Expression> selector) + { + throw new NotImplementedException(); + // return "@triggerBody()?.data?.values?.name"; + } + public static T Expr(Expression> selector) + { + throw new NotImplementedException(); + // return "@triggerBody()?.data?.values?.name"; + } + public static TOut Fn() + { + throw new NotImplementedException(); + // return "@triggerBody()?.data?.values?.name"; + } + + } From 8d235d67b23641af3a68e93ef46174a1f27b2f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Tue, 25 Jun 2024 09:30:44 +0200 Subject: [PATCH 32/34] fix: hangfire version updated --- src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj b/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj index c27bf9e..9f3cfe6 100644 --- a/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj +++ b/src/WorkflowEngine.Hangfire/WorkflowEngine.Hangfire.csproj @@ -11,7 +11,7 @@ - + From 6d36157d05c094d5650022b86af88f97b07baf52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Tue, 25 Jun 2024 13:31:09 +0200 Subject: [PATCH 33/34] fixed dependency update --- apps/WorkflowEngine.DemoApp/WorkflowEngine.DemoApp.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/WorkflowEngine.DemoApp/WorkflowEngine.DemoApp.csproj b/apps/WorkflowEngine.DemoApp/WorkflowEngine.DemoApp.csproj index e2b75c8..386eb64 100644 --- a/apps/WorkflowEngine.DemoApp/WorkflowEngine.DemoApp.csproj +++ b/apps/WorkflowEngine.DemoApp/WorkflowEngine.DemoApp.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -7,7 +7,7 @@ - + From fa4d37ea6c12cda6f7e833f1c40b747278c37f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Thu, 8 Aug 2024 16:02:32 +0200 Subject: [PATCH 34/34] fix: added environment variables to disable workflows,workflows__{name}=false will remove the workflow schedule --- .../WorkflowStarterBackgroundJob.cs | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs b/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs index 735b93a..8fccfc4 100644 --- a/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs +++ b/src/WorkflowEngine.Hangfire/WorkflowStarterBackgroundJob.cs @@ -1,4 +1,5 @@ using Hangfire; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using System; using System.Collections.Generic; @@ -29,6 +30,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) var workflows = sp.GetRequiredService(); var jobs = sp.GetRequiredService(); + var configuration = sp.GetRequiredService(); foreach (var workflow in await workflows.GetAllWorkflows()) { @@ -36,24 +38,37 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) foreach (var trigger in workflow.Manifest.Triggers.Where(t => t.Value.Type == "TimerTrigger")) { + + if (!trigger.Equals(default(KeyValuePair))) { + if(configuration.GetSection($"Workflows:{workflow.GetType().Name}")?.Value == "false" || + configuration.GetSection($"Workflows:{workflow.GetType().Name}:{trigger.Key}")?.Value == "false" + ) + { + jobs.RemoveIfExists(workflow.Id.ToString() + trigger.Key); + continue; + } workflow.Manifest = null; jobs.AddOrUpdate(workflow.Id.ToString() + trigger.Key, - (System.Linq.Expressions.Expression>)((executor) => executor.TriggerAsync(new TriggerContext - { - PrincipalId = "1b714972-8d0a-4feb-b166-08d93c6ae328", - Workflow = workflow, - Trigger = new Trigger - { - Inputs = trigger.Value.Inputs, - ScheduledTime = DateTimeOffset.UtcNow, - Type = trigger.Value.Type, - Key = trigger.Key - }, - }, null)), trigger.Value.Inputs["cronExpression"] as string,GetTimeZone(trigger) ); + (System.Linq.Expressions.Expression>) ((executor) => executor.TriggerAsync(new TriggerContext + { + PrincipalId = "1b714972-8d0a-4feb-b166-08d93c6ae328", + Workflow = workflow, + Trigger = new Trigger + { + Inputs = trigger.Value.Inputs, + ScheduledTime = DateTimeOffset.UtcNow, + Type = trigger.Value.Type, + Key = trigger.Key + }, + }, null)), trigger.Value.Inputs["cronExpression"] as string,new RecurringJobOptions + { + TimeZone = GetTimeZone(trigger) + }); + if (first && trigger.Value.Inputs.ContainsKey("runAtStartup") && (bool)trigger.Value.Inputs["runAtStartup"]) jobs.Trigger(workflow.Id.ToString() + trigger.Key);