├── .gitattributes
├── .gitignore
├── Images
├── MicroflowConsoleApp Nuget.PNG
├── MicroflowConsoleApp Solution.PNG
├── MicroflowFunctionApp Nuget.PNG
├── MicroflowFunctionApp Solution.PNG
└── Tests.png
├── LICENSE
├── Microflow.sln
├── MicroflowApiFunctionApp
├── .gitignore
├── MicroflowApi
│ ├── FlowControlApi.cs
│ ├── ScaleGroupsApi.cs
│ ├── StepsInProgressApi.cs
│ └── WorkflowApi.cs
├── MicroflowApiFunctionApp.csproj
├── Properties
│ ├── launchSettings.json
│ ├── serviceDependencies.json
│ └── serviceDependencies.local.json
├── host.json
└── local.settings.json
├── MicroflowConsoleApp
├── MicroFlowConsole
│ └── MicroflowConsole.csproj
├── MicroflowConsole
│ ├── MicroflowConsole.csproj
│ ├── Program.cs
│ └── TestWorkflows.cs
└── MicroflowConsoleApp.sln
├── MicroflowFunctionApp
├── .gitignore
├── API
│ ├── HttpOrchestrators
│ │ ├── HttpCallOrchestrator.cs
│ │ └── WebhookOrchestrator.cs
│ ├── Step
│ │ └── MicroflowStepApi.cs
│ └── Webhooks
│ │ ├── Webhooks.cs
│ │ └── WebhooksGet.cs
├── FlowControl
│ ├── CanStepExecuteNow.cs
│ ├── CanStepExecuteNowForScalingGroup.cs
│ ├── Interfaces
│ │ └── IMicroflowContext.cs
│ ├── MicroflowContext.cs
│ ├── MicroflowStart.cs
│ └── MicroflowStep.cs
├── Helpers
│ ├── MicroflowHelper.cs
│ ├── MicroflowHttpHelper.cs
│ ├── MicroflowOrchestrationHelper.cs
│ ├── MicroflowStartupHelper.cs
│ ├── MicroflowTableHelper.cs
│ └── MicroflowWorkflowHelper.cs
├── MicroflowApp.csproj
├── Models
│ ├── Interfaces
│ │ └── IMicroflowHttpResponse.cs
│ ├── MicroflowTableModels.cs
│ └── Models.cs
├── Optional
│ ├── ScaleGroupsApi.cs
│ └── WorkflowApi.cs
├── Properties
│ ├── ServiceDependencies
│ │ └── MicroflowApp20210615143625 - Zip Deploy
│ │ │ └── storage1.arm.json
│ ├── serviceDependencies.MicroflowApp20210615143625 - Zip Deploy.json
│ ├── serviceDependencies.json
│ └── serviceDependencies.local.json
├── TableLogging
│ ├── BlobLogResponse.cs
│ ├── TableLogErrorActivity.cs
│ ├── TableLogOrchestrationActivity.cs
│ ├── TableLogStepActivity.cs
│ └── TableLogWebhook.cs
├── cloudconfig.json
├── host.json
└── local.settings.json
├── MicroflowModels
├── Constants.cs
├── Helpers
│ └── TableHelper.cs
├── Interfaces
│ ├── IHttpCall.cs
│ ├── IHttpCallWithRetries.cs
│ ├── IMicroflowRun.cs
│ └── IStepEntity.cs
├── MicroflowModels.csproj
├── Models
│ ├── MicroflowRun.cs
│ └── Models.cs
└── TableModels
│ └── TableModels.cs
├── MicroflowSDK
├── CompilerDirectiveMaker.cs
├── HttpBlobDataManager.cs
├── MicroflowSDK.csproj
├── ScaleGroupsManager.cs
├── StepsManager.cs
├── WebhooksManager.cs
└── WorkflowManager.cs
├── MicroflowShared
├── MicroflowShared.csproj
├── TableReferences.cs
└── WorkflowHelper.cs
├── MicroflowTest
├── LogReader.cs
├── MicroflowTest.csproj
├── MicroflowTest.sln
├── Test1_WorkflowExecution.cs
├── Test2_Retries.cs
├── Test3_Webhooks.cs
├── Test4_ScaleGroups.cs
├── Test5_WaitForAllParents.cs
├── Test6_PassThroughParams.cs
├── Test7_RunFromSteps.cs
├── Test8_2TopStepsPostDataStart.cs
├── TestWorkflowHelper.cs
└── config.json
├── MicroserviceEmulator
├── .gitignore
├── Function1.cs
├── HttpClient.cs
├── MicroflowExternalAPI.cs
├── MicroserviceEmulator.csproj
├── MicroserviceEmulator
│ └── MicroserviceEmulator.sln
├── OrchestrationStateSimulations.cs
├── Properties
│ ├── launchSettings.json
│ ├── serviceDependencies.json
│ └── serviceDependencies.local.json
├── ResponseProxies
│ ├── Callbacks
│ │ └── ResponseProxyCallbackDemoFunction.cs
│ └── Inline
│ │ └── ResponseProxyInlineDemoFunction.cs
├── SleepTestOrchestrator.cs
├── host.json
└── local.settings.json
├── README.md
└── local.settings.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/Images/MicroflowConsoleApp Nuget.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andre-maree/Microflow/fec9d282e189931b2181b5705bdb26e6b0fa4810/Images/MicroflowConsoleApp Nuget.PNG
--------------------------------------------------------------------------------
/Images/MicroflowConsoleApp Solution.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andre-maree/Microflow/fec9d282e189931b2181b5705bdb26e6b0fa4810/Images/MicroflowConsoleApp Solution.PNG
--------------------------------------------------------------------------------
/Images/MicroflowFunctionApp Nuget.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andre-maree/Microflow/fec9d282e189931b2181b5705bdb26e6b0fa4810/Images/MicroflowFunctionApp Nuget.PNG
--------------------------------------------------------------------------------
/Images/MicroflowFunctionApp Solution.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andre-maree/Microflow/fec9d282e189931b2181b5705bdb26e6b0fa4810/Images/MicroflowFunctionApp Solution.PNG
--------------------------------------------------------------------------------
/Images/Tests.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andre-maree/Microflow/fec9d282e189931b2181b5705bdb26e6b0fa4810/Images/Tests.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Andre Maree
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Microflow.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.1.32228.430
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroflowApp", "MicroflowFunctionApp\MicroflowApp.csproj", "{074D09C1-2F8E-4DBA-B838-F59BCBE0D3BD}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroflowModels", "MicroflowModels\MicroflowModels.csproj", "{3182A6A0-38B4-4A4D-951A-E88B6602502E}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroflowApiFunctionApp", "MicroflowApiFunctionApp\MicroflowApiFunctionApp.csproj", "{8AB66346-AF3E-496C-BFE9-C8565BC3265F}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroflowShared", "MicroflowShared\MicroflowShared.csproj", "{C83CCA23-46D9-4819-8C45-ED01154A4121}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {074D09C1-2F8E-4DBA-B838-F59BCBE0D3BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {074D09C1-2F8E-4DBA-B838-F59BCBE0D3BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {074D09C1-2F8E-4DBA-B838-F59BCBE0D3BD}.Release|Any CPU.ActiveCfg = Debug|Any CPU
23 | {074D09C1-2F8E-4DBA-B838-F59BCBE0D3BD}.Release|Any CPU.Build.0 = Debug|Any CPU
24 | {3182A6A0-38B4-4A4D-951A-E88B6602502E}.Debug|Any CPU.ActiveCfg = DEBUG|Any CPU
25 | {3182A6A0-38B4-4A4D-951A-E88B6602502E}.Debug|Any CPU.Build.0 = DEBUG|Any CPU
26 | {3182A6A0-38B4-4A4D-951A-E88B6602502E}.Release|Any CPU.ActiveCfg = DDEBUG|Any CPU
27 | {3182A6A0-38B4-4A4D-951A-E88B6602502E}.Release|Any CPU.Build.0 = DDEBUG|Any CPU
28 | {8AB66346-AF3E-496C-BFE9-C8565BC3265F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {8AB66346-AF3E-496C-BFE9-C8565BC3265F}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {8AB66346-AF3E-496C-BFE9-C8565BC3265F}.Release|Any CPU.ActiveCfg = Debug|Any CPU
31 | {8AB66346-AF3E-496C-BFE9-C8565BC3265F}.Release|Any CPU.Build.0 = Debug|Any CPU
32 | {C83CCA23-46D9-4819-8C45-ED01154A4121}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {C83CCA23-46D9-4819-8C45-ED01154A4121}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {C83CCA23-46D9-4819-8C45-ED01154A4121}.Release|Any CPU.ActiveCfg = Debug|Any CPU
35 | {C83CCA23-46D9-4819-8C45-ED01154A4121}.Release|Any CPU.Build.0 = Debug|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {DCF5F864-D92D-4876-97D0-76F1FD7FBBD9}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/MicroflowApiFunctionApp/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # Azure Functions localsettings file
5 | local.settings.json
6 |
7 | # User-specific files
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | bld/
24 | [Bb]in/
25 | [Oo]bj/
26 | [Ll]og/
27 |
28 | # Visual Studio 2015 cache/options directory
29 | .vs/
30 | # Uncomment if you have tasks that create the project's static files in wwwroot
31 | #wwwroot/
32 |
33 | # MSTest test Results
34 | [Tt]est[Rr]esult*/
35 | [Bb]uild[Ll]og.*
36 |
37 | # NUNIT
38 | *.VisualState.xml
39 | TestResult.xml
40 |
41 | # Build Results of an ATL Project
42 | [Dd]ebugPS/
43 | [Rr]eleasePS/
44 | dlldata.c
45 |
46 | # DNX
47 | project.lock.json
48 | project.fragment.lock.json
49 | artifacts/
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # NCrunch
117 | _NCrunch_*
118 | .*crunch*.local.xml
119 | nCrunchTemp_*
120 |
121 | # MightyMoose
122 | *.mm.*
123 | AutoTest.Net/
124 |
125 | # Web workbench (sass)
126 | .sass-cache/
127 |
128 | # Installshield output folder
129 | [Ee]xpress/
130 |
131 | # DocProject is a documentation generator add-in
132 | DocProject/buildhelp/
133 | DocProject/Help/*.HxT
134 | DocProject/Help/*.HxC
135 | DocProject/Help/*.hhc
136 | DocProject/Help/*.hhk
137 | DocProject/Help/*.hhp
138 | DocProject/Help/Html2
139 | DocProject/Help/html
140 |
141 | # Click-Once directory
142 | publish/
143 |
144 | # Publish Web Output
145 | *.[Pp]ublish.xml
146 | *.azurePubxml
147 | # TODO: Comment the next line if you want to checkin your web deploy settings
148 | # but database connection strings (with potential passwords) will be unencrypted
149 | #*.pubxml
150 | *.publishproj
151 |
152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
153 | # checkin your Azure Web App publish settings, but sensitive information contained
154 | # in these scripts will be unencrypted
155 | PublishScripts/
156 |
157 | # NuGet Packages
158 | *.nupkg
159 | # The packages folder can be ignored because of Package Restore
160 | **/packages/*
161 | # except build/, which is used as an MSBuild target.
162 | !**/packages/build/
163 | # Uncomment if necessary however generally it will be regenerated when needed
164 | #!**/packages/repositories.config
165 | # NuGet v3's project.json files produces more ignoreable files
166 | *.nuget.props
167 | *.nuget.targets
168 |
169 | # Microsoft Azure Build Output
170 | csx/
171 | *.build.csdef
172 |
173 | # Microsoft Azure Emulator
174 | ecf/
175 | rcf/
176 |
177 | # Windows Store app package directories and files
178 | AppPackages/
179 | BundleArtifacts/
180 | Package.StoreAssociation.xml
181 | _pkginfo.txt
182 |
183 | # Visual Studio cache files
184 | # files ending in .cache can be ignored
185 | *.[Cc]ache
186 | # but keep track of directories ending in .cache
187 | !*.[Cc]ache/
188 |
189 | # Others
190 | ClientBin/
191 | ~$*
192 | *~
193 | *.dbmdl
194 | *.dbproj.schemaview
195 | *.jfm
196 | *.pfx
197 | *.publishsettings
198 | node_modules/
199 | orleans.codegen.cs
200 |
201 | # Since there are multiple workflows, uncomment next line to ignore bower_components
202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
203 | #bower_components/
204 |
205 | # RIA/Silverlight projects
206 | Generated_Code/
207 |
208 | # Backup & report files from converting an old project file
209 | # to a newer Visual Studio version. Backup files are not needed,
210 | # because we have git ;-)
211 | _UpgradeReport_Files/
212 | Backup*/
213 | UpgradeLog*.XML
214 | UpgradeLog*.htm
215 |
216 | # SQL Server files
217 | *.mdf
218 | *.ldf
219 |
220 | # Business Intelligence projects
221 | *.rdl.data
222 | *.bim.layout
223 | *.bim_*.settings
224 |
225 | # Microsoft Fakes
226 | FakesAssemblies/
227 |
228 | # GhostDoc plugin setting file
229 | *.GhostDoc.xml
230 |
231 | # Node.js Tools for Visual Studio
232 | .ntvs_analysis.dat
233 |
234 | # Visual Studio 6 build log
235 | *.plg
236 |
237 | # Visual Studio 6 workspace options file
238 | *.opt
239 |
240 | # Visual Studio LightSwitch build output
241 | **/*.HTMLClient/GeneratedArtifacts
242 | **/*.DesktopClient/GeneratedArtifacts
243 | **/*.DesktopClient/ModelManifest.xml
244 | **/*.Server/GeneratedArtifacts
245 | **/*.Server/ModelManifest.xml
246 | _Pvt_Extensions
247 |
248 | # Paket dependency manager
249 | .paket/paket.exe
250 | paket-files/
251 |
252 | # FAKE - F# Make
253 | .fake/
254 |
255 | # JetBrains Rider
256 | .idea/
257 | *.sln.iml
258 |
259 | # CodeRush
260 | .cr/
261 |
262 | # Python Tools for Visual Studio (PTVS)
263 | __pycache__/
264 | *.pyc
--------------------------------------------------------------------------------
/MicroflowApiFunctionApp/MicroflowApi/ScaleGroupsApi.cs:
--------------------------------------------------------------------------------
1 | #if DEBUG || RELEASE || !DEBUG_NO_FLOWCONTROL_SCALEGROUPS && !DEBUG_NO_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_SCALEGROUPS && !DEBUG_NO_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_UPSERT_FLOWCONTROL_SCALEGROUPS && !DEBUG_NO_UPSERT_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_UPSERT_SCALEGROUPS && !DEBUG_NO_UPSERT_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_FLOWCONTROL_SCALEGROUPS && !RELEASE_NO_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_SCALEGROUPS && !RELEASE_NO_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_UPSERT_FLOWCONTROL_SCALEGROUPS && !RELEASE_NO_UPSERT_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_UPSERT_SCALEGROUPS && !RELEASE_NO_UPSERT_SCALEGROUPS_STEPCOUNT
2 | using MicroflowModels;
3 | using Microsoft.Azure.WebJobs;
4 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
5 | using Microsoft.Azure.WebJobs.Extensions.Http;
6 | using Newtonsoft.Json;
7 | using Newtonsoft.Json.Linq;
8 | using System;
9 | using System.Collections.Generic;
10 | using System.Linq;
11 | using System.Net;
12 | using System.Net.Http;
13 | using System.Threading;
14 | using System.Threading.Tasks;
15 | using static MicroflowModels.Constants;
16 |
17 | namespace MicroflowApi
18 | {
19 | public class ScaleGroupsApi
20 | {
21 | ///
22 | /// Get max instance count for scale group
23 | ///
24 | [FunctionName("Get" + ScaleGroupCalls.ScaleGroup)]
25 | public static async Task GetScaleGroup([HttpTrigger(AuthorizationLevel.Anonymous, "get",
26 | Route = MicroflowPath + "/ScaleGroup/{scaleGroupId}")] HttpRequestMessage req,
27 | [DurableClient] IDurableEntityClient client, string scaleGroupId)
28 | {
29 | Dictionary result = new();
30 | EntityQueryResult res = null;
31 |
32 | using (CancellationTokenSource cts = new())
33 | {
34 | res = await client.ListEntitiesAsync(new EntityQuery()
35 | {
36 | PageSize = 99999999,
37 | EntityName = ScaleGroupCalls.ScaleGroupMaxConcurrentInstanceCount,
38 | FetchState = true
39 | }, cts.Token);
40 | }
41 |
42 | if (string.IsNullOrWhiteSpace(scaleGroupId))
43 | {
44 | foreach (DurableEntityStatus rr in res.Entities)
45 | {
46 | result.Add(rr.EntityId.EntityKey, rr.State.Value());
47 | }
48 | }
49 | else
50 | {
51 | foreach (DurableEntityStatus rr in res.Entities.Where(e => e.EntityId.EntityKey.Equals(scaleGroupId)))
52 | {
53 | result.Add(rr.EntityId.EntityKey, rr.State.Value());
54 | }
55 | }
56 |
57 | StringContent content = new(JsonConvert.SerializeObject(result));
58 |
59 | return new HttpResponseMessage(HttpStatusCode.OK)
60 | {
61 | Content = content
62 | };
63 | }
64 |
65 | ///
66 | /// Set the max instance count for scale group, default to wait for 5 seconds
67 | ///
68 | [FunctionName("Set" + ScaleGroupCalls.ScaleGroup)]
69 | public static async Task ScaleGroup([HttpTrigger(AuthorizationLevel.Anonymous, "post",
70 | Route = MicroflowPath + "/ScaleGroup/{scaleGroupId}/{maxWaitSeconds:int?}")] HttpRequestMessage req,
71 | [DurableClient] IDurableOrchestrationClient client, string scaleGroupId, int? maxWaitSeconds)
72 | {
73 | ScaleGroupState state = JsonConvert.DeserializeObject(await req.Content.ReadAsStringAsync());
74 |
75 | string instanceId = await client.StartNewAsync("SetScaleGroupMaxConcurrentCount", null, (scaleGroupId, state));
76 |
77 | return await client.WaitForCompletionOrCreateCheckStatusResponseAsync(req, instanceId, TimeSpan.FromSeconds(maxWaitSeconds is null
78 | ? 5
79 | : maxWaitSeconds.Value));
80 | }
81 |
82 | ///
83 | /// Set the scale group max concurrent instance count orchestration
84 | ///
85 | ///
86 | [Deterministic]
87 | [FunctionName("SetScaleGroupMaxConcurrentCount")]
88 | public static async Task SetScaleGroupMaxOrchestration([OrchestrationTrigger] IDurableOrchestrationContext context)
89 | {
90 | (string scaleGroupId, ScaleGroupState state) = context.GetInput<(string, ScaleGroupState)>();
91 |
92 | EntityId scaleGroupCountId = new(ScaleGroupCalls.ScaleGroupMaxConcurrentInstanceCount, scaleGroupId);
93 |
94 | await context.CallEntityAsync(scaleGroupCountId, MicroflowEntityKeys.Set, state);
95 | }
96 | }
97 | }
98 | #endif
--------------------------------------------------------------------------------
/MicroflowApiFunctionApp/MicroflowApi/StepsInProgressApi.cs:
--------------------------------------------------------------------------------
1 | #if DEBUG || RELEASE || !DEBUG_NO_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_FLOWCONTROL_STEPCOUNT && !DEBUG_NO_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_STEPCOUNT && !DEBUG_NO_UPSERT_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_UPSERT_FLOWCONTROL_STEPCOUNT && !DEBUG_NO_UPSERT_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_UPSERT_STEPCOUNT && !RELEASE_NO_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_FLOWCONTROL_STEPCOUNT && !RELEASE_NO_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_STEPCOUNT && !RELEASE_NO_UPSERT_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_UPSERT_FLOWCONTROL_STEPCOUNT && !RELEASE_NO_UPSERT_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_UPSERT_STEPCOUNT
2 | using System.Net.Http;
3 | using System.Threading.Tasks;
4 | using Microsoft.Azure.WebJobs;
5 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
6 | using Microsoft.Azure.WebJobs.Extensions.Http;
7 |
8 | namespace MicroflowApi
9 | {
10 | public class StepsInProgressApi
11 | {
12 | //[OpenApiOperation(operationId: "Run", tags: new[] { "name" })]
13 | //[OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
14 | //[OpenApiParameter(name: "name", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The **Name** parameter")]
15 | //[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(string), Description = "The OK response")]
16 | [FunctionName("GetStepsCountInProgress")]
17 | public static async Task GetStepsCountInProgress([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "GetStepsCountInProgress/{workflowNameStepNumber}")] HttpRequestMessage req,
18 | [DurableClient] IDurableEntityClient client,
19 | string workflowNameStepNumber)
20 | {
21 | EntityId countId = new("StepCount", workflowNameStepNumber);
22 |
23 | EntityStateResponse result = await client.ReadEntityStateAsync(countId);
24 |
25 | return result.EntityState;
26 | }
27 | }
28 | }
29 | #endif
--------------------------------------------------------------------------------
/MicroflowApiFunctionApp/MicroflowApi/WorkflowApi.cs:
--------------------------------------------------------------------------------
1 | #if DEBUG || RELEASE || !DEBUG_NO_UPSERT && !DEBUG_NO_UPSERT_FLOWCONTROL && !DEBUG_NO_UPSERT_FLOWCONTROL_SCALEGROUPS && !DEBUG_NO_UPSERT_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_UPSERT_FLOWCONTROL_STEPCOUNT && !DEBUG_NO_UPSERT_SCALEGROUPS && !DEBUG_NO_UPSERT_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_UPSERT_STEPCOUNT && !RELEASE_NO_UPSERT && !RELEASE_NO_UPSERT_FLOWCONTROL && !RELEASE_NO_UPSERT_FLOWCONTROL_SCALEGROUPS && !RELEASE_NO_UPSERT_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_UPSERT_FLOWCONTROL_STEPCOUNT && !RELEASE_NO_UPSERT_SCALEGROUPS && !RELEASE_NO_UPSERT_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_UPSERT_STEPCOUNT
2 | using System;
3 | using System.Net;
4 | using System.Net.Http;
5 | using System.Threading.Tasks;
6 | using MicroflowShared;
7 | using Microsoft.Azure.WebJobs;
8 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
9 | using Microsoft.Azure.WebJobs.Extensions.Http;
10 |
11 | namespace MicroflowApi
12 | {
13 | public static class WorkflowApi
14 | {
15 | ///
16 | /// Delete orchestration history
17 | ///
18 | ///
19 | [FunctionName("PurgeInstanceHistory")]
20 | public static async Task PurgeInstanceHistory([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "PurgeInstanceHistory/{workflowName?}")]
21 | HttpRequestMessage req,
22 | [DurableClient] IDurableOrchestrationClient client,
23 | string workflowName)
24 | {
25 | try
26 | {
27 | await client.PurgeInstanceHistoryAsync(workflowName);
28 |
29 | return new HttpResponseMessage(HttpStatusCode.OK);
30 | }
31 | catch (Exception e)
32 | {
33 | HttpResponseMessage resp = new(HttpStatusCode.InternalServerError)
34 | {
35 | Content = new StringContent(e.Message)
36 | };
37 |
38 | return resp;
39 | }
40 | }
41 |
42 | ///
43 | /// This must be called at least once before a workflow runs,
44 | /// this is to prevent multiple concurrent instances from writing step data at workflow run,
45 | /// call UpsertWorkflow when something changed in the workflow, but do not always call this when concurrent multiple workflows
46 | ///
47 | [FunctionName("UpsertWorkflow")]
48 | public static async Task UpsertWorkflow([HttpTrigger(AuthorizationLevel.Anonymous, "post",
49 | Route = "UpsertWorkflow/{globalKey?}")] HttpRequestMessage req,
50 | [DurableClient] IDurableEntityClient client, string globalKey)
51 | {
52 | return await client.UpsertWorkflow(await req.Content.ReadAsStringAsync(), globalKey);
53 | }
54 |
55 |
56 | ///
57 | /// Returns the workflow Json that was saved with UpsertWorkflow
58 | ///
59 | [FunctionName("GetWorkflow")]
60 | public static async Task GetWorkflowJson([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "GetWorkflow/{workflowName}")] HttpRequestMessage req,
61 | string workflowName)
62 | {
63 | return await WorkflowHelper.GetWorkflowJson(workflowName);
64 | }
65 | }
66 | }
67 | #endif
--------------------------------------------------------------------------------
/MicroflowApiFunctionApp/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "MicroflowApiFunctionApp": {
4 | "commandName": "Project",
5 | "commandLineArgs": "host start --pause-on-error --port 5860"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/MicroflowApiFunctionApp/Properties/serviceDependencies.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "appInsights1": {
4 | "type": "appInsights"
5 | },
6 | "storage1": {
7 | "type": "storage",
8 | "connectionId": "AzureWebJobsStorage"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/MicroflowApiFunctionApp/Properties/serviceDependencies.local.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "appInsights1": {
4 | "type": "appInsights.sdk"
5 | },
6 | "storage1": {
7 | "type": "storage.emulator",
8 | "connectionId": "AzureWebJobsStorage"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/MicroflowApiFunctionApp/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "extensions": {
4 | "durableTask": {
5 | "logReplayEvents": "false"
6 | },
7 | "http": {
8 | "routePrefix": "microflow/v1"
9 | }
10 | },
11 | "logging": {
12 | "applicationInsights": {
13 | "samplingSettings": {
14 | "isEnabled": false,
15 | "excludedTypes": "Request"
16 | }
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/MicroflowApiFunctionApp/local.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet",
6 | "MicroflowStorage": "UseDevelopmentStorage=true",
7 | "AzureWebJobsSecretStorageType": "files"
8 | }
9 | }
--------------------------------------------------------------------------------
/MicroflowConsoleApp/MicroFlowConsole/MicroflowConsole.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/MicroflowConsoleApp/MicroflowConsole/MicroflowConsole.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/MicroflowConsoleApp/MicroflowConsole/TestWorkflows.cs:
--------------------------------------------------------------------------------
1 | using MicroflowModels;
2 | using MicroflowSDK;
3 | using System.Collections.Generic;
4 |
5 | namespace MicroflowConsole
6 | {
7 | public static class TestWorkflows
8 | {
9 | public static List CreateTestWorkflow_SimpleSteps()
10 | {
11 | // create
12 | List steps = WorkflowManager.CreateSteps(4, 1,"{default_post_url}");
13 | //steps[0].IsHttpGet = true;
14 | //steps[0].CallbackTimeoutSeconds = 30;
15 | //steps[0].WebhookAction = "approve";
16 | //steps[0].CalloutUrl = "http://localhost:7071/SleepTestOrchestrator_HttpStart";
17 | //steps[0].SetRetryForStep(1, 2, 1);
18 | //steps[0].StopOnActionFailed = true;
19 | //steps[0].CalloutTimeoutSeconds = 190;
20 |
21 | steps[1].AddSubSteps(steps[2], steps[3]);
22 | steps[4].AddParentSteps(steps[2], steps[3]);
23 |
24 | steps.Remove(steps[0]);
25 |
26 | return steps;
27 | }
28 |
29 | public static List CreateTestWorkflow_10StepsParallel()
30 | {
31 | List steps = WorkflowManager.CreateSteps(14, 1, "{default_post_url}");
32 |
33 | steps[1].AddSubSteps(steps[2], steps[3]);
34 | steps[2].AddSubSteps(steps[4], steps[5], steps[6], steps[7], steps[8]);
35 | steps[3].AddSubSteps(steps[9], steps[10], steps[11], steps[12], steps[13]);
36 | // 2 groups of 5 parallel steps = 10 parallel steps
37 | steps[14].AddParentSteps(steps[4], steps[5], steps[6], steps[7], steps[8], steps[9], steps[10], steps[11], steps[12], steps[13]);
38 |
39 | steps.Remove(steps[0]);
40 |
41 | return steps;
42 | }
43 |
44 | public static List CreateTestWorkflow_Complex1()
45 | {
46 | List steps = WorkflowManager.CreateSteps(8, 1, "{default_post_url}");
47 |
48 | steps[1].AddSubSteps(steps[2], steps[3], steps[4]);
49 | steps[2].AddSubSteps(steps[5], steps[6]);
50 | steps[3].AddSubSteps(steps[6], steps[7]);
51 | steps[4].AddSubSteps(steps[6], steps[8]);
52 | steps[5].AddSubSteps(steps[3], steps[6]);
53 | steps[6].AddSubSteps(steps[8]);
54 | steps[7].AddSubSteps(steps[8]);
55 |
56 | steps.Remove(steps[0]);
57 |
58 | return steps;
59 | }
60 |
61 |
62 | public static List CreateTestWorkflow_110Steps()
63 | {
64 | List steps = WorkflowManager.CreateSteps(110, 1, "{default_post_url}");
65 |
66 | steps[1].AddSubStepRange(steps, 3, 11);
67 | steps[11].AddSubStepRange(steps, 13, 22);
68 | steps[22].AddSubStepRange(steps, 24, 33);
69 | steps[33].AddSubStepRange(steps, 35, 44);
70 | steps[44].AddSubStepRange(steps, 46, 55);
71 | steps[55].AddSubStepRange(steps, 57, 66);
72 | steps[66].AddSubStepRange(steps, 68, 77);
73 | steps[77].AddSubStepRange(steps, 79, 88);
74 | steps[88].AddSubStepRange(steps, 90, 99);
75 | steps[99].AddSubStepRange(steps, 101, 110);
76 |
77 | steps.Remove(steps[0]);
78 |
79 | return steps;
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/MicroflowFunctionApp/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # Azure Functions localsettings file
5 | local.settings.json
6 |
7 | # User-specific files
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | bld/
24 | [Bb]in/
25 | [Oo]bj/
26 | [Ll]og/
27 |
28 | # Visual Studio 2015 cache/options directory
29 | .vs/
30 | # Uncomment if you have tasks that create the project's static files in wwwroot
31 | #wwwroot/
32 |
33 | # MSTest test Results
34 | [Tt]est[Rr]esult*/
35 | [Bb]uild[Ll]og.*
36 |
37 | # NUNIT
38 | *.VisualState.xml
39 | TestResult.xml
40 |
41 | # Build Results of an ATL Project
42 | [Dd]ebugPS/
43 | [Rr]eleasePS/
44 | dlldata.c
45 |
46 | # DNX
47 | project.lock.json
48 | project.fragment.lock.json
49 | artifacts/
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # NCrunch
117 | _NCrunch_*
118 | .*crunch*.local.xml
119 | nCrunchTemp_*
120 |
121 | # MightyMoose
122 | *.mm.*
123 | AutoTest.Net/
124 |
125 | # Web workbench (sass)
126 | .sass-cache/
127 |
128 | # Installshield output folder
129 | [Ee]xpress/
130 |
131 | # DocProject is a documentation generator add-in
132 | DocProject/buildhelp/
133 | DocProject/Help/*.HxT
134 | DocProject/Help/*.HxC
135 | DocProject/Help/*.hhc
136 | DocProject/Help/*.hhk
137 | DocProject/Help/*.hhp
138 | DocProject/Help/Html2
139 | DocProject/Help/html
140 |
141 | # Click-Once directory
142 | publish/
143 |
144 | # Publish Web Output
145 | *.[Pp]ublish.xml
146 | *.azurePubxml
147 | # TODO: Comment the next line if you want to checkin your web deploy settings
148 | # but database connection strings (with potential passwords) will be unencrypted
149 | #*.pubxml
150 | *.publishproj
151 |
152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
153 | # checkin your Azure Web App publish settings, but sensitive information contained
154 | # in these scripts will be unencrypted
155 | PublishScripts/
156 |
157 | # NuGet Packages
158 | *.nupkg
159 | # The packages folder can be ignored because of Package Restore
160 | **/packages/*
161 | # except build/, which is used as an MSBuild target.
162 | !**/packages/build/
163 | # Uncomment if necessary however generally it will be regenerated when needed
164 | #!**/packages/repositories.config
165 | # NuGet v3's project.json files produces more ignoreable files
166 | *.nuget.props
167 | *.nuget.targets
168 |
169 | # Microsoft Azure Build Output
170 | csx/
171 | *.build.csdef
172 |
173 | # Microsoft Azure Emulator
174 | ecf/
175 | rcf/
176 |
177 | # Windows Store app package directories and files
178 | AppPackages/
179 | BundleArtifacts/
180 | Package.StoreAssociation.xml
181 | _pkginfo.txt
182 |
183 | # Visual Studio cache files
184 | # files ending in .cache can be ignored
185 | *.[Cc]ache
186 | # but keep track of directories ending in .cache
187 | !*.[Cc]ache/
188 |
189 | # Others
190 | ClientBin/
191 | ~$*
192 | *~
193 | *.dbmdl
194 | *.dbproj.schemaview
195 | *.jfm
196 | *.pfx
197 | *.publishsettings
198 | node_modules/
199 | orleans.codegen.cs
200 |
201 | # Since there are multiple workflows, uncomment next line to ignore bower_components
202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
203 | #bower_components/
204 |
205 | # RIA/Silverlight projects
206 | Generated_Code/
207 |
208 | # Backup & report files from converting an old project file
209 | # to a newer Visual Studio version. Backup files are not needed,
210 | # because we have git ;-)
211 | _UpgradeReport_Files/
212 | Backup*/
213 | UpgradeLog*.XML
214 | UpgradeLog*.htm
215 |
216 | # SQL Server files
217 | *.mdf
218 | *.ldf
219 |
220 | # Business Intelligence projects
221 | *.rdl.data
222 | *.bim.layout
223 | *.bim_*.settings
224 |
225 | # Microsoft Fakes
226 | FakesAssemblies/
227 |
228 | # GhostDoc plugin setting file
229 | *.GhostDoc.xml
230 |
231 | # Node.js Tools for Visual Studio
232 | .ntvs_analysis.dat
233 |
234 | # Visual Studio 6 build log
235 | *.plg
236 |
237 | # Visual Studio 6 workspace options file
238 | *.opt
239 |
240 | # Visual Studio LightSwitch build output
241 | **/*.HTMLClient/GeneratedArtifacts
242 | **/*.DesktopClient/GeneratedArtifacts
243 | **/*.DesktopClient/ModelManifest.xml
244 | **/*.Server/GeneratedArtifacts
245 | **/*.Server/ModelManifest.xml
246 | _Pvt_Extensions
247 |
248 | # Paket dependency manager
249 | .paket/paket.exe
250 | paket-files/
251 |
252 | # FAKE - F# Make
253 | .fake/
254 |
255 | # JetBrains Rider
256 | .idea/
257 | *.sln.iml
258 |
259 | # CodeRush
260 | .cr/
261 |
262 | # Python Tools for Visual Studio (PTVS)
263 | __pycache__/
264 | *.pyc
--------------------------------------------------------------------------------
/MicroflowFunctionApp/API/Webhooks/Webhooks.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Threading.Tasks;
3 | using Microsoft.Azure.WebJobs;
4 | using Microsoft.Azure.WebJobs.Extensions.Http;
5 | using System.Net.Http;
6 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
7 | using MicroflowModels.Helpers;
8 | using MicroflowModels;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.Text.Json;
12 | using System.Linq;
13 |
14 | namespace Microflow.Webhooks
15 | {
16 | ///
17 | /// This is a generic webhooks implementation with /webhooks/{webhookId}/{action}
18 | /// Substeps to execute can be set with the step`s WebhookSubStepsMapping property, accociated to the action
19 | ///
20 | public static class Webhooks
21 | {
22 | /////
23 | ///// For a webhook defined as {webhookId}/{action}
24 | /////
25 | [FunctionName("Webhook")]
26 | public static async Task WebhookWithAction(
27 | [HttpTrigger(AuthorizationLevel.Function, "get", "post",
28 | Route = Constants.MicroflowPath + "/Webhooks/{webhookId}/{action?}")] HttpRequestMessage req,
29 | [DurableClient] IDurableOrchestrationClient orchClient,
30 | string webhookId, string action)
31 | => await orchClient.ProcessWebhook(webhookId, action, req.RequestUri);
32 |
33 | private static async Task ProcessWebhook(this IDurableOrchestrationClient client,
34 | string webhookId,
35 | string action,
36 | Uri location)
37 | {
38 | Webhook webhook = await TableHelper.GetWebhook(webhookId);
39 |
40 | if (webhook == null)
41 | {
42 | return new(HttpStatusCode.NotFound);
43 | }
44 |
45 | try
46 | {
47 | // no action
48 | if (string.IsNullOrWhiteSpace(action))
49 | {
50 | if (!string.IsNullOrWhiteSpace(webhook.WebhookSubStepsMapping))
51 | {
52 | return new(HttpStatusCode.NotFound);
53 | }
54 |
55 | await client.RaiseEventAsync(webhookId, webhookId, new MicroflowHttpResponse()
56 | {
57 | Success = true,
58 | HttpResponseStatusCode = 200
59 | });
60 |
61 | return new(HttpStatusCode.OK);
62 | }
63 |
64 | // with action
65 | if (string.IsNullOrWhiteSpace(webhook.WebhookSubStepsMapping))
66 | {
67 | return new(HttpStatusCode.NotFound);
68 | }
69 |
70 | List webhookSubStepsMapping = JsonSerializer.Deserialize>(webhook.WebhookSubStepsMapping);
71 |
72 | SubStepsMappingForActions hook = webhookSubStepsMapping.FirstOrDefault(h => h.WebhookAction.Equals(action, StringComparison.OrdinalIgnoreCase));
73 |
74 | MicroflowHttpResponse webhookResult;
75 |
76 | if (hook != null)
77 | {
78 | webhookResult = new()
79 | {
80 | Success = true,
81 | HttpResponseStatusCode = 200,
82 | SubStepsToRun = hook.SubStepsToRunForAction,
83 | Action = action
84 | };
85 | }
86 | else
87 | {
88 | return new(HttpStatusCode.NotFound);
89 | }
90 |
91 | await client.RaiseEventAsync(webhookId, webhookId, webhookResult);
92 |
93 | return new(HttpStatusCode.OK);
94 | }
95 | catch (ArgumentException)
96 | {
97 | try
98 | {
99 | DurableOrchestrationStatus statusCheck = await client.GetStatusAsync("callout" + webhookId);
100 |
101 | if (statusCheck == null)
102 | {
103 | return new(HttpStatusCode.NotFound);
104 | }
105 |
106 | // unliky but possible that the event have not yet been created
107 | HttpResponseMessage response = new(HttpStatusCode.Accepted);
108 | response.Headers.Location = location;
109 |
110 | return response;
111 | }
112 | catch
113 | {
114 | return new(HttpStatusCode.InternalServerError);
115 | }
116 | }
117 | catch
118 | {
119 | return new(HttpStatusCode.InternalServerError);
120 | }
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/MicroflowFunctionApp/API/Webhooks/WebhooksGet.cs:
--------------------------------------------------------------------------------
1 | namespace Microflow.Webhooks
2 | {
3 | ///
4 | /// This is a generic webhooks implementation with /webhooks/{webhookId}/{stepId}/{action}
5 | /// Substeps to execute can be set with the step`s WebhookSubStepsMapping property, accociated to the action
6 | ///
7 | public static class WebhooksGet
8 | {
9 | ///
10 | /// For a webhook defined as "{webhookId}/{stepId}"
11 | ///
12 | //[FunctionName("GetWebhooks")]
13 | //public static async Task GetWebhooks(
14 | //[HttpTrigger(AuthorizationLevel.Function, "get", "post",
15 | //Route = Constants.MicroflowPath + "/GetWebhooks/{workflowName}/{webhookId}/{stepNumber}/{instanceGuid?}")] HttpRequestMessage req,
16 | //[DurableClient] IDurableOrchestrationClient orchClient,
17 | //string workflowName, string webhookId, int stepNumber, string instanceGuid = "")
18 | //{
19 | // var li = await MicroflowTableHelper.GetWebhooks(workflowName, webhookId, stepNumber, instanceGuid);
20 |
21 | // return null;
22 | //}
23 |
24 | //private static async Task GetWebhooks()
25 | //{
26 | // await Task.Delay(3);
27 | //}
28 |
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/MicroflowFunctionApp/FlowControl/CanStepExecuteNow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Microflow.Models;
4 | using MicroflowModels;
5 | using Microsoft.Azure.WebJobs;
6 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
7 | using static MicroflowModels.Constants;
8 |
9 | namespace Microflow.FlowControl
10 | {
11 | public static class CanStepExecuteNow
12 | {
13 | ///
14 | /// Calculate if a step is ready to execute by locking and counting the completed parents
15 | ///
16 | /// Bool to indicate if this step request can be executed or not
17 | [Deterministic]
18 | [FunctionName(CallNames.CanExecuteNow)]
19 | public static async Task CanExecuteNow([OrchestrationTrigger] IDurableOrchestrationContext context)
20 | {
21 | CanExecuteNowObject canExecuteNowObject = context.GetInput();
22 | try
23 | {
24 | EntityId countId = new(MicroflowEntities.CanExecuteNowCount,
25 | canExecuteNowObject.RunId + canExecuteNowObject.StepNumber);
26 |
27 | CanExecuteResult canExecuteResult = null;
28 |
29 | using (await context.LockAsync(countId))
30 | {
31 | int parentCompletedCount = await context.CallEntityAsync(countId, MicroflowEntityKeys.Read);
32 |
33 | if (parentCompletedCount + 1 >= canExecuteNowObject.ParentCount)
34 | {
35 | // cleanup with silnalentity
36 | //context.SignalEntity(countId, "delete");
37 |
38 | canExecuteResult = new CanExecuteResult()
39 | {
40 | CanExecute = true,
41 | StepNumber = canExecuteNowObject.StepNumber
42 | };
43 | }
44 | else
45 | {
46 | await context.CallEntityAsync(countId, MicroflowEntityKeys.Add);
47 |
48 | canExecuteResult = new CanExecuteResult()
49 | {
50 | CanExecute = false,
51 | StepNumber = canExecuteNowObject.StepNumber
52 | };
53 | }
54 | }
55 |
56 | if(canExecuteResult.CanExecute)
57 | {
58 | context.SignalEntity(countId, MicroflowEntityKeys.Delete);
59 | }
60 |
61 | return canExecuteResult;
62 | }
63 | catch (Exception e)
64 | {
65 | // log to table error
66 | LogErrorEntity errorEntity = new(canExecuteNowObject.WorkflowName,
67 | Convert.ToInt32(canExecuteNowObject.StepNumber),
68 | e.Message,
69 | canExecuteNowObject.RunId);
70 |
71 | await context.CallActivityAsync(CallNames.LogError, errorEntity);
72 |
73 | return new CanExecuteResult()
74 | {
75 | CanExecute = false,
76 | StepNumber = canExecuteNowObject.StepNumber
77 | };
78 | }
79 | }
80 |
81 | ///
82 | /// Durable entity to keep a count for each run and each step in the run
83 | ///
84 | [Deterministic]
85 | [FunctionName(MicroflowEntities.CanExecuteNowCount)]
86 | public static void CanExecuteNowCounter([EntityTrigger] IDurableEntityContext ctx)
87 | {
88 | switch (ctx.OperationName)
89 | {
90 | case MicroflowEntityKeys.Add:
91 | ctx.SetState(ctx.GetState() + 1);
92 | break;
93 | //case "reset":
94 | // ctx.SetState(0);
95 | // break;
96 | case MicroflowEntityKeys.Read:
97 | ctx.Return(ctx.GetState());
98 | break;
99 | case MicroflowEntityKeys.Delete:
100 | ctx.DeleteState();
101 | break;
102 | }
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/FlowControl/CanStepExecuteNowForScalingGroup.cs:
--------------------------------------------------------------------------------
1 | #if DEBUG || RELEASE || !DEBUG_NO_FLOWCONTROL_SCALEGROUPS && !DEBUG_NO_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_SCALEGROUPS && !DEBUG_NO_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_UPSERT_FLOWCONTROL_SCALEGROUPS && !DEBUG_NO_UPSERT_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_UPSERT_SCALEGROUPS && !DEBUG_NO_UPSERT_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_FLOWCONTROL_SCALEGROUPS && !RELEASE_NO_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_SCALEGROUPS && !RELEASE_NO_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_UPSERT_FLOWCONTROL_SCALEGROUPS && !RELEASE_NO_UPSERT_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_UPSERT_SCALEGROUPS && !RELEASE_NO_UPSERT_SCALEGROUPS_STEPCOUNT
2 | using System;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Microflow.Models;
6 | using MicroflowModels;
7 | using Microsoft.Azure.WebJobs;
8 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
9 | using static MicroflowModels.Constants;
10 |
11 | namespace Microflow.FlowControl
12 | {
13 | ///
14 | /// Check if the step can execute taking into account the max concurrent steps in the scale group
15 | ///
16 | public static class CanStepExecuteNowForScalingGroup
17 | {
18 | [Deterministic]
19 | [FunctionName(ScaleGroupCalls.CanExecuteNowInScaleGroup)]
20 | public static async Task CheckMaxScaleCountForGroup([OrchestrationTrigger] IDurableOrchestrationContext context)
21 | {
22 | CanExecuteNowObject canExecuteNowObject = context.GetInput();
23 | EntityId countId = new(ScaleGroupCalls.CanExecuteNowInScaleGroupCount, canExecuteNowObject.ScaleGroupId);
24 |
25 | EntityId scaleGroupCountId = new(ScaleGroupCalls.ScaleGroupMaxConcurrentInstanceCount, canExecuteNowObject.ScaleGroupId);
26 | ScaleGroupState scaleGroupState = await context.CallEntityAsync(scaleGroupCountId, MicroflowControlKeys.Read);
27 |
28 | if (scaleGroupState.ScaleGroupMaxConcurrentInstanceCount == 0)
29 | {
30 | return;
31 | }
32 |
33 | using (await context.LockAsync(countId))
34 | {
35 | int scaleGroupInProcessCount = await context.CallEntityAsync(countId, MicroflowEntityKeys.Read);
36 |
37 | if (scaleGroupInProcessCount < scaleGroupState.ScaleGroupMaxConcurrentInstanceCount)
38 | {
39 | await context.CallEntityAsync(countId, MicroflowEntityKeys.Add);
40 |
41 | return;
42 | }
43 | }
44 |
45 | DateTime endDate = context.CurrentUtcDateTime.AddHours(scaleGroupState.PollingMaxHours);
46 | // start interval seconds
47 | int count = scaleGroupState.PollingIntervalSeconds;
48 | // max interval seconds
49 | int max = scaleGroupState.PollingIntervalMaxSeconds;
50 |
51 | using (CancellationTokenSource cts = new())
52 | {
53 | try
54 | {
55 | while (context.CurrentUtcDateTime < endDate)
56 | {
57 | DateTime deadline = context.CurrentUtcDateTime.Add(TimeSpan.FromSeconds(count < max ? count : max));
58 | await context.CreateTimer(deadline, cts.Token);
59 | count++;
60 |
61 | using (await context.LockAsync(countId))
62 | {
63 | int scaleGroupInProcessCount = await context.CallEntityAsync(countId, MicroflowEntityKeys.Read);
64 |
65 | if (scaleGroupInProcessCount < scaleGroupState.ScaleGroupMaxConcurrentInstanceCount)
66 | {
67 | await context.CallEntityAsync(countId, MicroflowEntityKeys.Add);
68 |
69 | return;
70 | }
71 | }
72 | }
73 | }
74 | catch (TaskCanceledException tex)
75 | {
76 | // log to table error
77 | LogErrorEntity errorEntity = new(canExecuteNowObject.WorkflowName,
78 | Convert.ToInt32(canExecuteNowObject.StepNumber),
79 | tex.Message,
80 | canExecuteNowObject.RunId);
81 |
82 | await context.CallActivityAsync(CallNames.LogError, errorEntity);
83 | }
84 | catch (Exception e)
85 | {
86 | // log to table error
87 | LogErrorEntity errorEntity = new(canExecuteNowObject.WorkflowName,
88 | Convert.ToInt32(canExecuteNowObject.StepNumber),
89 | e.Message,
90 | canExecuteNowObject.RunId);
91 |
92 | await context.CallActivityAsync(CallNames.LogError, errorEntity);
93 | }
94 | finally
95 | {
96 | cts.Dispose();
97 | }
98 | }
99 | }
100 |
101 | ///
102 | /// Durable entity to keep a count for each run and each step in the run
103 | ///
104 | [FunctionName(ScaleGroupCalls.CanExecuteNowInScaleGroupCount)]
105 | public static void CanExecuteNowInScalingGroupCounter([EntityTrigger] IDurableEntityContext ctx)
106 | {
107 | switch (ctx.OperationName)
108 | {
109 | case MicroflowEntityKeys.Add:
110 | ctx.SetState(ctx.GetState() + 1);
111 | break;
112 | case MicroflowEntityKeys.Subtract:
113 | int state = ctx.GetState();
114 | ctx.SetState(state <= 0 ? 0 : state - 1);
115 | break;
116 | case MicroflowEntityKeys.Read:
117 | ctx.Return(ctx.GetState());
118 | break;
119 | //case "delete":
120 | // ctx.DeleteState();
121 | // break;
122 | }
123 | }
124 |
125 | [FunctionName(ScaleGroupCalls.ScaleGroupMaxConcurrentInstanceCount)]
126 | public static void ScaleGroupMaxConcurrentInstanceCounter([EntityTrigger] IDurableEntityContext ctx)
127 | {
128 | switch (ctx.OperationName.ToLowerInvariant())
129 | {
130 | case MicroflowEntityKeys.Set:
131 | ctx.SetState(ctx.GetInput());
132 | break;
133 | case MicroflowEntityKeys.Read:
134 | ctx.Return(ctx.GetState());
135 | break;
136 | //case "delete":
137 | // ctx.DeleteState();
138 | // break;
139 | }
140 | }
141 | }
142 | }
143 | #endif
144 |
--------------------------------------------------------------------------------
/MicroflowFunctionApp/FlowControl/Interfaces/IMicroflowContext.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace Microflow.FlowControl
4 | {
5 | public interface IMicroflowContext
6 | {
7 | Task RunMicroflow();
8 | }
9 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/FlowControl/MicroflowStart.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Microsoft.Azure.WebJobs;
4 | using Microsoft.Azure.WebJobs.Extensions.Http;
5 | using Microsoft.Extensions.Logging;
6 | using System.Net.Http;
7 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
8 | using Microflow.Helpers;
9 | using MicroflowModels;
10 | using Azure;
11 | using static MicroflowModels.Constants;
12 |
13 | namespace Microflow.FlowControl
14 | {
15 | ///
16 | /// UpsertWorkflow must be called to save workflow step meta data to table storage
17 | /// after this, Start can be called multiple times,
18 | /// if a change is made to the workflow, call UpsertWorkflow again to apply the changes
19 | ///
20 | public static class MicroflowStartFunctions
21 | {
22 | ///
23 | /// This is the entry point, workflow payload is in the http body
24 | ///
25 | /// If an instanceId is passed in, it will run as a singleton, else it will run concurrently with each with a new instanceId
26 | [FunctionName(CallNames.MicroflowStart)]
27 | public static async Task MicroflowStart([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = MicroflowPath + "/Start/{workflowName}/{instanceId?}")]
28 | HttpRequestMessage req,
29 | [DurableClient] IDurableOrchestrationClient client,
30 | string instanceId, string workflowName)
31 | {
32 | return await client.StartWorkflow(req, instanceId, workflowName);
33 | }
34 |
35 | ///
36 | /// This is called from Microflow_HttpStart, it does the looping and calls the ExecuteStep sub orchestration passing in the top step
37 | ///
38 | ///
39 | [Deterministic]
40 | [FunctionName(CallNames.MicroflowStartOrchestration)]
41 | public static async Task MicroflowStartOrchestration([OrchestrationTrigger] IDurableOrchestrationContext context,
42 | ILogger inLog)
43 | {
44 | ILogger log = context.CreateReplaySafeLogger(inLog);
45 |
46 | // read workflowRun payload
47 | MicroflowRun workflowRun = context.GetInput();
48 |
49 | try
50 | {
51 | Task resp = context.MicroflowCheckAndWaitForReadyToRun(workflowRun.WorkflowName);
52 |
53 | if (!await resp)
54 | {
55 | return;
56 | }
57 |
58 | await context.StartMicroflow(log, workflowRun);
59 | }
60 | catch (RequestFailedException e)
61 | {
62 | // log to table workflow completed
63 | LogErrorEntity errorEntity = new(workflowRun.WorkflowName,
64 | Convert.ToInt32(workflowRun.RunObject.StepNumber),
65 | e.Message,
66 | workflowRun.RunObject.RunId);
67 |
68 | await context.CallActivityAsync(CallNames.LogError, errorEntity);
69 | }
70 | catch (Exception e)
71 | {
72 | // log to table workflow completed
73 | LogErrorEntity errorEntity = new(workflowRun.WorkflowName,
74 | Convert.ToInt32(workflowRun.RunObject.StepNumber),
75 | e.Message,
76 | workflowRun.RunObject.RunId);
77 |
78 | await context.CallActivityAsync(CallNames.LogError, errorEntity);
79 | }
80 | }
81 | }
82 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/FlowControl/MicroflowStep.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using MicroflowModels;
4 | using MicroflowModels.Helpers;
5 | using Microsoft.Azure.WebJobs;
6 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
7 | using Microsoft.Extensions.Logging;
8 | using static MicroflowModels.Constants;
9 |
10 | namespace Microflow.FlowControl
11 | {
12 | public static class MicroflowStep
13 | {
14 | ///
15 | /// Get the current step table config
16 | ///
17 | [FunctionName(CallNames.GetStepInternal)]
18 | public static async Task GetStep([ActivityTrigger] MicroflowRun workflowRun) => await workflowRun.GetStep();
19 |
20 | ///
21 | /// Recursive step and sub-step execution
22 | ///
23 | [Deterministic]
24 | [FunctionName(CallNames.ExecuteStep)]
25 | public static async Task ExecuteStep([OrchestrationTrigger] IDurableOrchestrationContext context,
26 | ILogger inLog)
27 | {
28 | MicroflowRun workflowRun = context.GetInput();
29 | MicroflowContext microflowContext = null;
30 |
31 | try
32 | {
33 | microflowContext = new MicroflowContext(context, workflowRun, inLog);
34 |
35 | await microflowContext.RunMicroflow();
36 | }
37 | catch (Exception e)
38 | {
39 | if (microflowContext != null)
40 | {
41 | string stepNumber = microflowContext.HttpCallWithRetries == null
42 | ? "-2"
43 | : microflowContext.HttpCallWithRetries.RowKey;
44 |
45 | // log to table workflow completed
46 | LogErrorEntity errorEntity = new(workflowRun?.WorkflowName,
47 | Convert.ToInt32(stepNumber),
48 | e.Message,
49 | workflowRun?.RunObject?.RunId);
50 |
51 | await context.CallActivityAsync(CallNames.LogError, errorEntity);
52 |
53 | throw;
54 | }
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/MicroflowFunctionApp/Helpers/MicroflowHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Http;
4 | using System.Threading.Tasks;
5 | using Microsoft.Azure.WebJobs;
6 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
7 | using Microsoft.Azure.WebJobs.Extensions.Http;
8 | using static MicroflowModels.Constants;
9 |
10 | namespace Microflow.Helpers
11 | {
12 | public static class MicroflowHelper
13 | {
14 | #if DEBUG || RELEASE || !DEBUG_NO_FLOWCONTROL && !DEBUG_NO_FLOWCONTROL_SCALEGROUPS && !DEBUG_NO_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_FLOWCONTROL_STEPCOUNT && !DEBUG_NO_UPSERT_FLOWCONTROL && !DEBUG_NO_UPSERT_FLOWCONTROL_SCALEGROUPS && !DEBUG_NO_UPSERT_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_UPSERT_FLOWCONTROL_STEPCOUNT && !RELEASE_NO_FLOWCONTROL && !RELEASE_NO_FLOWCONTROL_SCALEGROUPS && !RELEASE_NO_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_FLOWCONTROL_STEPCOUNT && !RELEASE_NO_UPSERT_FLOWCONTROL && !RELEASE_NO_UPSERT_FLOWCONTROL_SCALEGROUPS && !RELEASE_NO_UPSERT_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_UPSERT_FLOWCONTROL_STEPCOUNT
15 | ///
16 | /// Get workflow state
17 | /// Pause, run, or stop the workflow, cmd can be "run", "pause", or "stop"
18 | ///
19 | [FunctionName("WorkflowControl")]
20 | public static async Task WorkflowControl([HttpTrigger(AuthorizationLevel.Anonymous, "get",
21 | Route = MicroflowPath + "/WorkflowControl/{cmd}/{workflowName}/{workflowVersion}")] HttpRequestMessage req,
22 | [DurableClient] IDurableEntityClient client, string workflowName, string cmd, string workflowVersion)
23 | {
24 | string key = string.IsNullOrWhiteSpace(workflowVersion)
25 | ? workflowName
26 | : $"{workflowName}@{workflowVersion}";
27 |
28 | if (cmd.Equals(MicroflowControlKeys.Read, StringComparison.OrdinalIgnoreCase))
29 | {
30 | EntityId projStateId = new(MicroflowStateKeys.WorkflowState, key);
31 | EntityStateResponse stateRes = await client.ReadEntityStateAsync(projStateId);
32 |
33 | HttpResponseMessage resp = new(HttpStatusCode.OK)
34 | {
35 | Content = new StringContent(stateRes.EntityState)
36 | };
37 |
38 | return resp;
39 | }
40 |
41 | return await client.SetRunState(nameof(WorkflowState), key, cmd);
42 | }
43 |
44 | ///
45 | /// Pause, run, or stop all with the same global key, cmd can be "run", "pause", or "stop"
46 | ///
47 | [FunctionName("GlobalControl")]
48 | public static async Task GlobalControl([HttpTrigger(AuthorizationLevel.Anonymous, "get",
49 | Route = MicroflowPath + "/GlobalControl/{cmd}/{globalKey}")] HttpRequestMessage req,
50 | [DurableClient] IDurableEntityClient client, string globalKey, string cmd)
51 | {
52 | if (cmd.Equals(MicroflowControlKeys.Read, StringComparison.OrdinalIgnoreCase))
53 | {
54 | EntityId globStateId = new(MicroflowStateKeys.GlobalState, globalKey);
55 | EntityStateResponse stateRes = await client.ReadEntityStateAsync(globStateId);
56 |
57 | HttpResponseMessage resp = new(HttpStatusCode.OK)
58 | {
59 | Content = new StringContent(stateRes.EntityState)
60 | };
61 |
62 | return resp;
63 | }
64 |
65 | return await client.SetRunState(nameof(GlobalState), globalKey, cmd);
66 | }
67 | #endif
68 |
69 | ///
70 | /// Durable entity check and set if the global state
71 | ///
72 | [FunctionName(MicroflowStateKeys.GlobalState)]
73 | public static void GlobalState([EntityTrigger] IDurableEntityContext ctx)
74 | {
75 | ctx.RunState();
76 | }
77 |
78 | ///
79 | /// Durable entity check and set workflow state
80 | ///
81 | [FunctionName(MicroflowStateKeys.WorkflowState)]
82 | public static void WorkflowState([EntityTrigger] IDurableEntityContext ctx)
83 | {
84 | ctx.RunState();
85 | }
86 |
87 | ///
88 | /// For workflow and global key states
89 | ///
90 | private static void RunState(this IDurableEntityContext ctx)
91 | {
92 | switch (ctx.OperationName)
93 | {
94 | case MicroflowControlKeys.Ready:
95 | ctx.SetState(MicroflowStates.Ready);
96 | break;
97 | case MicroflowControlKeys.Pause:
98 | ctx.SetState(MicroflowStates.Paused);
99 | break;
100 | case MicroflowControlKeys.Stop:
101 | ctx.SetState(MicroflowStates.Stopped);
102 | break;
103 | case MicroflowControlKeys.Read:
104 | ctx.Return(ctx.GetState());
105 | break;
106 | }
107 | }
108 |
109 | ///
110 | /// Set the global or workflow state with the key, and the cmd can be "pause", "ready", or "stop"
111 | ///
112 | public static async Task SetRunState(this IDurableEntityClient client,
113 | string stateEntityId,
114 | string key,
115 | string cmd)
116 | {
117 | EntityId runStateId = new(stateEntityId, key);
118 |
119 | switch (cmd)
120 | {
121 | case MicroflowControlKeys.Pause:
122 | await client.SignalEntityAsync(runStateId, MicroflowControlKeys.Pause);
123 | break;
124 | case MicroflowControlKeys.Ready:
125 | await client.SignalEntityAsync(runStateId, MicroflowControlKeys.Ready);
126 | break;
127 | case MicroflowControlKeys.Stop:
128 | await client.SignalEntityAsync(runStateId, MicroflowControlKeys.Stop);
129 | break;
130 | }
131 |
132 | return new HttpResponseMessage(HttpStatusCode.OK);
133 | }
134 | }
135 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/Helpers/MicroflowHttpHelper.cs:
--------------------------------------------------------------------------------
1 | using MicroflowModels;
2 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
3 | using Microsoft.Extensions.Primitives;
4 | using System;
5 | using System.Net.Http;
6 | using System.Text;
7 | using System.Text.Json;
8 | using static MicroflowModels.Constants;
9 |
10 | namespace Microflow.Helpers
11 | {
12 | public static class MicroflowHttpHelper
13 | {
14 | ///
15 | /// Used to handle callout responses
16 | ///
17 | [Deterministic]
18 | public static MicroflowHttpResponse GetMicroflowResponse(this DurableHttpResponse durableHttpResponse, bool forwardPostData)
19 | {
20 | int statusCode = (int)durableHttpResponse.StatusCode;
21 |
22 | if (statusCode <= 200 || ((statusCode > 201) && (statusCode < 300)))
23 | {
24 | return new MicroflowHttpResponse() { Success = true, HttpResponseStatusCode = statusCode, Content = forwardPostData ? durableHttpResponse.Content : string.Empty };
25 | }
26 |
27 | // if 201 created try get the location header to save it in the steps log
28 | if (statusCode != 201)
29 | return new MicroflowHttpResponse() { Success = false, HttpResponseStatusCode = statusCode };
30 |
31 | return durableHttpResponse.Headers.TryGetValue("location", out StringValues values)
32 | ? new MicroflowHttpResponse() { Success = true, HttpResponseStatusCode = statusCode, Content = values[0] }
33 | : new MicroflowHttpResponse() { Success = true, HttpResponseStatusCode = statusCode };
34 | }
35 |
36 | [Deterministic]
37 | public static DurableHttpRequest CreateMicroflowDurableHttpRequest(this HttpCall httpCall, string instanceId, MicroflowHttpResponse microflowHttpResponse)
38 | {
39 | DurableHttpRequest newDurableHttpRequest;
40 |
41 | //string webhook = string.IsNullOrWhiteSpace(httpCall.WebhookId)
42 | // ? ""
43 | // : httpCall.WebhookId;
44 |
45 | httpCall.CalculateGlobalKey();
46 |
47 | if (!httpCall.IsHttpGet)
48 | {
49 | MicroflowPostData postData = new()
50 | {
51 | WorkflowName = httpCall.PartitionKey,
52 | SubOrchestrationId = instanceId,
53 | RunId = httpCall.RunId,
54 | StepId = httpCall.StepId,
55 | StepNumber = Convert.ToInt32(httpCall.RowKey),
56 | MainOrchestrationId = httpCall.MainOrchestrationId,
57 | Webhook = httpCall.EnableWebhook ? $"{CallNames.BaseUrl}/webhooks/{httpCall.WebhookId}" : null,
58 | GlobalKey = httpCall.GlobalKey,
59 | PostData = microflowHttpResponse.Content
60 | };
61 |
62 | string body = JsonSerializer.Serialize(postData);
63 |
64 | newDurableHttpRequest = new DurableHttpRequest(
65 | method: HttpMethod.Post,
66 | uri: new Uri(httpCall.CalloutUrl),
67 | timeout: TimeSpan.FromSeconds(httpCall.CalloutTimeoutSeconds),
68 | content: body,
69 | asynchronousPatternEnabled: httpCall.AsynchronousPollingEnabled
70 | //headers: durableHttpRequest.Headers,
71 | //tokenSource: durableHttpRequest.TokenSource
72 |
73 | );
74 |
75 | // Do not copy over the x-functions-key header, as in many cases, the
76 | // functions key used for the initial request will be a Function-level key
77 | // and the status endpoint requires a master key.
78 | //newDurableHttpRequest.Headers.Remove("x-functions-key");
79 |
80 | }
81 | else
82 | {
83 | newDurableHttpRequest = new DurableHttpRequest(
84 | method: HttpMethod.Get,
85 | uri: new Uri(httpCall.CalloutUrl),
86 | timeout: TimeSpan.FromSeconds(httpCall.CalloutTimeoutSeconds),
87 | asynchronousPatternEnabled: httpCall.AsynchronousPollingEnabled
88 | //headers: durableHttpRequest.Headers,
89 | //tokenSource: durableHttpRequest.TokenSource
90 |
91 | );
92 |
93 | // Do not copy over the x-functions-key header, as in many cases, the
94 | // functions key used for the initial request will be a Function-level key
95 | // and the status endpoint requires a master key.
96 | //newDurableHttpRequest.Headers.Remove("x-functions-key");
97 | }
98 |
99 | return newDurableHttpRequest;
100 | }
101 |
102 | [Deterministic]
103 | public static void ParseUrlMicroflowData(IHttpCallWithRetries httpCall, string instanceId)
104 | {
105 | // do not replace the querystring if it is a post
106 | if(!httpCall.IsHttpGet)
107 | {
108 | return;
109 | }
110 |
111 | StringBuilder sb = new(httpCall.CalloutUrl);
112 |
113 | sb.Replace("", httpCall.PartitionKey);
114 | sb.Replace("", httpCall.MainOrchestrationId);
115 | sb.Replace("", instanceId);
116 | if(httpCall.EnableWebhook)
117 | {
118 | sb.Replace("", $"{CallNames.BaseUrl}/webhooks/{httpCall.WebhookId}");
119 | }
120 | sb.Replace("", httpCall.RunId);
121 | sb.Replace("", httpCall.StepId);
122 | sb.Replace("", httpCall.RowKey);
123 | sb.Replace("", httpCall.GlobalKey);
124 |
125 | httpCall.CalloutUrl = sb.ToString();
126 | }
127 | }
128 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/Helpers/MicroflowStartupHelper.cs:
--------------------------------------------------------------------------------
1 | using Azure;
2 | using MicroflowModels;
3 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
4 | using System;
5 | using System.Collections.Specialized;
6 | using static MicroflowModels.Constants;
7 | using System.Net.Http;
8 | using System.Net;
9 | using System.Threading.Tasks;
10 |
11 | namespace Microflow.Helpers
12 | {
13 | public static class MicroflowStartupHelper
14 | {
15 | public static async Task StartWorkflow(this IDurableOrchestrationClient client, HttpRequestMessage req, string instanceId, string workflowNameVersion)
16 | {
17 | try
18 | {
19 | MicroflowRun workflowRun = MicroflowWorkflowHelper.CreateMicroflowRun(req, ref instanceId, workflowNameVersion);
20 |
21 | if (req.Method == HttpMethod.Post)
22 | {
23 | string content = await req.Content.ReadAsStringAsync();
24 |
25 | if(!string.IsNullOrEmpty(content))
26 | {
27 | workflowRun.RunObject.MicroflowStepResponseData = new()
28 | {
29 | Content = content
30 | };
31 | }
32 |
33 | //workflowRun.RunObject.MicroflowStepResponseData.Content = content;
34 | }
35 |
36 | // start
37 | await client.StartNewAsync(CallNames.MicroflowStartOrchestration, instanceId, workflowRun);
38 |
39 | HttpResponseMessage response = client.CreateCheckStatusResponse(req, instanceId);
40 |
41 | if (response.StatusCode == HttpStatusCode.OK)
42 | {
43 | response.Content = new StringContent(instanceId);
44 | }
45 |
46 | return response;
47 |
48 | }
49 | catch (RequestFailedException ex)
50 | {
51 | HttpResponseMessage resp = new(HttpStatusCode.InternalServerError)
52 | {
53 | Content = new StringContent(ex.Message + " - workflow in error state, call 'UpsertWorkflow' at least once before running a workflow.")
54 | };
55 |
56 | return resp;
57 | }
58 | catch (Exception e)
59 | {
60 | HttpResponseMessage resp = new(HttpStatusCode.InternalServerError)
61 | {
62 | Content = new StringContent(e.Message)
63 | };
64 |
65 | return resp;
66 | }
67 | }
68 |
69 | ///
70 | /// Create a new workflowRun for stratup, set GlobalKey
71 | ///
72 | public static MicroflowRun CreateStartupRun(NameValueCollection data,
73 | ref string instanceId,
74 | string workflowName)
75 | {
76 | var input = new
77 | {
78 | Loop = Convert.ToInt32(data["loop"]),
79 | GlobalKey = data["globalkey"]
80 | };
81 |
82 | // create a workflow run
83 | MicroflowRun workflowRun = new()
84 | {
85 | WorkflowName = workflowName,
86 | Loop = input.Loop != 0
87 | ? input.Loop
88 | : 1
89 | };
90 |
91 | // create a new run object
92 | RunObject runObj = new();
93 | workflowRun.RunObject = runObj;
94 |
95 | // instanceId is set/singleton
96 | if (!string.IsNullOrWhiteSpace(instanceId))
97 | {
98 | // globalKey is set
99 | if (!string.IsNullOrWhiteSpace(input.GlobalKey))
100 | {
101 | runObj.GlobalKey = input.GlobalKey;
102 | }
103 | else
104 | {
105 | runObj.GlobalKey = Guid.NewGuid().ToString();
106 | }
107 | }
108 | // instanceId is not set/multiple concurrent instances
109 | else
110 | {
111 | instanceId = Guid.NewGuid().ToString();
112 | // globalKey is set
113 | if (!string.IsNullOrWhiteSpace(input.GlobalKey))
114 | {
115 | runObj.GlobalKey = input.GlobalKey;
116 | }
117 | else
118 | {
119 | runObj.GlobalKey = instanceId;
120 | }
121 | }
122 |
123 | //workflowRun.RunObject.StepNumber = "-1";
124 | workflowRun.OrchestratorInstanceId = instanceId;
125 |
126 | return workflowRun;
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/MicroflowFunctionApp/Helpers/MicroflowTableHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Azure;
3 | using Azure.Data.Tables;
4 | using Microflow.MicroflowTableModels;
5 | using System.Collections.Generic;
6 | using MicroflowModels.Helpers;
7 |
8 | namespace Microflow.Helpers
9 | {
10 | public static class MicroflowTableHelper
11 | {
12 | #region Table operations
13 |
14 | public static async Task LogStep(this LogStepEntity logEntity)
15 | {
16 | TableClient tableClient = TableHelper.GetLogStepsTable();
17 |
18 | await tableClient.UpsertEntityAsync(logEntity);
19 | }
20 |
21 | public static async Task LogOrchestration(this LogOrchestrationEntity logEntity)
22 | {
23 | TableClient tableClient = TableHelper.GetLogOrchestrationTable();
24 |
25 | await tableClient.UpsertEntityAsync(logEntity);
26 | }
27 |
28 | public static async Task LogWebhook(this LogWebhookEntity logEntity)
29 | {
30 | TableClient tableClient = TableHelper.GetLogWebhookTable();
31 |
32 | await tableClient.UpsertEntityAsync(logEntity);
33 | }
34 |
35 | public static async Task> GetWebhooks(string workflowName, string webhookId, int stepNumber, string instanceGuid = "")
36 | {
37 | TableClient tableClient = TableHelper.GetLogWebhookTable();
38 |
39 | string query = $"(PartitionKey eq '{workflowName}' or PartitionKey >= '{workflowName}~' and PartitionKey < '{workflowName}~~') and RowKey lt '{stepNumber + 1}' and RowKey gt '{stepNumber}~";
40 |
41 | if (!string.IsNullOrEmpty(instanceGuid))
42 | {
43 | query += $"{instanceGuid}~' and RowKey lt '{stepNumber}~{instanceGuid}~~'";
44 | }
45 | else
46 | {
47 | query += "'";
48 | }
49 | //"(PartitionKey eq 'Myflow_ClientX2@2.1' or PartitionKey >= 'Myflow_ClientX2@2.1~' and PartitionKey lt 'Myflow_ClientX2@2.1~~') and RowKey < '3' and RowKey gt '2~d17f312f-8b2f-5d34-aa4e-92c76236029d~'and RowKey < '3' and RowKey lt '2~d17f312f-8b2f-5d34-aa4e-92c76236029d~~'";
50 |
51 | AsyncPageable queryResultsFilter = tableClient.QueryAsync(filter: query);
52 |
53 | List list = new();
54 |
55 | // Iterate the to access all queried entities.
56 | await foreach (LogWebhookEntity qEntity in queryResultsFilter)
57 | {
58 | list.Add(qEntity);
59 | }
60 |
61 | return list;
62 | }
63 |
64 | #endregion
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/MicroflowFunctionApp/Helpers/MicroflowWorkflowHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Specialized;
3 | using System.Net.Http;
4 | using MicroflowModels;
5 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
6 | using static MicroflowModels.Constants;
7 |
8 | namespace Microflow.Helpers
9 | {
10 | public static class MicroflowWorkflowHelper
11 | {
12 | public static MicroflowRun CreateMicroflowRun(HttpRequestMessage req, ref string instanceId, string workflowName)
13 | {
14 | return MicroflowStartupHelper.CreateStartupRun(req.RequestUri.ParseQueryString(), ref instanceId, workflowName);
15 | }
16 |
17 | public static Microsoft.Azure.WebJobs.Extensions.DurableTask.RetryOptions GetRetryOptions(this IHttpCallWithRetries httpCallWithRetries)
18 | {
19 | Microsoft.Azure.WebJobs.Extensions.DurableTask.RetryOptions ops = new(TimeSpan.FromSeconds(httpCallWithRetries.RetryDelaySeconds),
20 | httpCallWithRetries.RetryMaxRetries + 1)
21 | {
22 | RetryTimeout = TimeSpan.FromSeconds(httpCallWithRetries.RetryTimeoutSeconds),
23 | MaxRetryInterval = TimeSpan.FromSeconds(httpCallWithRetries.RetryMaxDelaySeconds),
24 | BackoffCoefficient = httpCallWithRetries.RetryBackoffCoefficient
25 | };
26 |
27 | return ops;
28 | }
29 |
30 | ///
31 | /// Work out what the global key is for this call
32 | ///
33 | [Deterministic]
34 | public static void CalculateGlobalKey(this HttpCall httpCall)
35 | {
36 | // check if it is call to Microflow
37 | if (httpCall.CalloutUrl.StartsWith($"{CallNames.BaseUrl}start/"))
38 | {
39 | // parse query string
40 | NameValueCollection data = new Uri(httpCall.CalloutUrl).ParseQueryString();
41 | // if there is query string data
42 | if (data.Count > 0)
43 | {
44 | // check if there is a global key (maybe if it is an assigned key)
45 | if (string.IsNullOrEmpty(data.Get("globalkey")))
46 | {
47 | httpCall.CalloutUrl += $"&globalkey={httpCall.GlobalKey}";
48 | }
49 | }
50 | else
51 | {
52 | httpCall.CalloutUrl += $"?globalkey={httpCall.GlobalKey}";
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/MicroflowFunctionApp/Models/Interfaces/IMicroflowHttpResponse.cs:
--------------------------------------------------------------------------------
1 | namespace Microflow.Models
2 | {
3 | //public interface IMicroflowHttpResponse
4 | //{
5 | // int HttpResponseStatusCode { get; set; }
6 | // string Content { get; set; }
7 | // bool Success { get; set; }
8 | // public List SubStepsToRun { get; set; }
9 | //}
10 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/Models/MicroflowTableModels.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Azure;
3 | using Azure.Data.Tables;
4 |
5 | namespace Microflow.MicroflowTableModels
6 | {
7 | #region TableEntity
8 |
9 | public class WebhookEntity : ITableEntity
10 | {
11 | public WebhookEntity() { }
12 |
13 | public WebhookEntity(string webhookId, string webhookSubStepsMapping)
14 | {
15 | PartitionKey = webhookId;
16 | RowKey = "0";
17 | WebhookSubStepsMapping = webhookSubStepsMapping;
18 | }
19 |
20 | public string WebhookSubStepsMapping { get; set; }
21 | public string PartitionKey { get; set; }
22 | public string RowKey { get; set; }
23 | public DateTimeOffset? Timestamp { get; set; }
24 | public ETag ETag { get; set; }
25 | }
26 |
27 | public class LogWebhookEntity : ITableEntity
28 | {
29 | public LogWebhookEntity() { }
30 |
31 | public LogWebhookEntity(bool isCreate, string workflowName, string rowKey, DateTime date, string runId = "", string action = "")
32 | {
33 | PartitionKey = workflowName;
34 | RowKey = rowKey;
35 |
36 | if(isCreate)
37 | {
38 | CreateDate = date;
39 | RunId = runId;
40 | }
41 | else
42 | {
43 | ActionDate = date;
44 | Action = action;
45 | }
46 | }
47 |
48 | public string Action { get; set; }
49 | public string RunId { get; set; }
50 | public DateTime? CreateDate { get; set; }
51 | public DateTime? ActionDate { get; set; }
52 | public string PartitionKey { get; set; }
53 | public string RowKey { get; set; }
54 | public DateTimeOffset? Timestamp { get; set; }
55 | public ETag ETag { get; set; }
56 | }
57 |
58 | ///
59 | /// Used for orchestration level logging
60 | ///
61 | public class LogOrchestrationEntity : ITableEntity
62 | {
63 | public LogOrchestrationEntity() { }
64 |
65 | public LogOrchestrationEntity(bool isStart, string workflowName, string rowKey, string logMessage, DateTime date, string orchestrationId, string globalKey)
66 | {
67 | PartitionKey = workflowName;
68 | LogMessage = logMessage;
69 | RowKey = rowKey;
70 | OrchestrationId = orchestrationId;
71 | GlobalKey = globalKey;
72 |
73 | if (isStart)
74 | StartDate = date;
75 | else
76 | EndDate = date;
77 | }
78 |
79 | public string GlobalKey { get; set; }
80 | public string OrchestrationId { get; set; }
81 | public string LogMessage { get; set; }
82 | public DateTime? StartDate { get; set; }
83 | public DateTime? EndDate { get; set; }
84 | public string PartitionKey { get; set; }
85 | public string RowKey { get; set; }
86 | public DateTimeOffset? Timestamp { get; set; }
87 | public ETag ETag { get; set; }
88 | }
89 |
90 | ///
91 | /// Used for step level logging
92 | ///
93 | public class LogStepEntity : ITableEntity
94 | {
95 | public LogStepEntity() { }
96 |
97 | public LogStepEntity(bool isStart,
98 | string workflowName,
99 | string rowKey,
100 | int stepNumber,
101 | string mainOrchestrationId,
102 | string runId,
103 | string globalKey,
104 | string? calloutUrl = null,
105 | bool? success = null,
106 | int? httpStatusCode = null,
107 | string message = null,
108 | string? subOrchestrationId = null,
109 | string? webhookId = null)
110 | {
111 | PartitionKey = workflowName + "__" + mainOrchestrationId;
112 | RowKey = rowKey;
113 | StepNumber = stepNumber;
114 | GlobalKey = globalKey;
115 | RunId = runId;
116 | CalloutUrl = calloutUrl;
117 | SubOrchestrationId = subOrchestrationId;
118 |
119 | if (isStart)
120 | StartDate = DateTime.UtcNow;
121 | else
122 | {
123 | Success = success;
124 | HttpStatusCode = httpStatusCode;
125 | Message = message;
126 | EndDate = DateTime.UtcNow;
127 | }
128 | }
129 |
130 | public bool? Success { get; set; }
131 | public int? HttpStatusCode { get; set; }
132 | public string Message { get; set; }
133 | public int StepNumber { get; set; }
134 | public string RunId { get; set; }
135 | public string GlobalKey { get; set; }
136 | public string CalloutUrl { get; set; }
137 | public string SubOrchestrationId { get; set; }
138 | public string WebhookId { get; set; }
139 | public DateTime? StartDate { get; set; }
140 | public DateTime? EndDate { get; set; }
141 | public string PartitionKey { get; set; }
142 | public string RowKey { get; set; }
143 | public DateTimeOffset? Timestamp { get; set; }
144 | public ETag ETag { get; set; }
145 | }
146 |
147 | #endregion
148 | }
149 |
--------------------------------------------------------------------------------
/MicroflowFunctionApp/Models/Models.cs:
--------------------------------------------------------------------------------
1 | namespace Microflow.Models
2 | {
3 | #region POCOs
4 |
5 | ///
6 | /// Used when locking for parent step concurrency count checking
7 | ///
8 | public class CanExecuteNowObject
9 | {
10 | public string RunId { get; set; }
11 | public string StepNumber { get; set; }
12 | public int ParentCount { get; set; }
13 | public string ScaleGroupId { get; set; }
14 | public string WorkflowName { get; set; }
15 | }
16 |
17 | ///
18 | /// Used when locking for parent step concurrency count checking
19 | ///
20 | public class CanExecuteResult
21 | {
22 | public bool CanExecute { get; set; }
23 | public string StepNumber { get; set; }
24 | }
25 |
26 | #endregion
27 | }
28 |
--------------------------------------------------------------------------------
/MicroflowFunctionApp/Optional/ScaleGroupsApi.cs:
--------------------------------------------------------------------------------
1 | #define INCLUDE_scalegroups
2 | #if INCLUDE_scalegroups
3 | using MicroflowModels;
4 | using Microsoft.Azure.WebJobs;
5 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
6 | using Microsoft.Azure.WebJobs.Extensions.Http;
7 | using Newtonsoft.Json.Linq;
8 | using Newtonsoft.Json;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.Linq;
12 | using System.Net;
13 | using System.Net.Http;
14 | using System.Text.Json;
15 | using System.Threading;
16 | using System.Threading.Tasks;
17 | using static MicroflowModels.Constants;
18 |
19 | namespace MicroflowApi
20 | {
21 | public class ScaleGroupsApi
22 | {
23 | ///
24 | /// Get max instance count for scale group
25 | ///
26 | [FunctionName("Get" + ScaleGroupCalls.ScaleGroup)]
27 | public static async Task GetScaleGroup([HttpTrigger(AuthorizationLevel.Anonymous, "get",
28 | Route = MicroflowPath + "/ScaleGroup/{scaleGroupId}")] HttpRequestMessage req,
29 | [DurableClient] IDurableEntityClient client, string scaleGroupId)
30 | {
31 | Dictionary result = new();
32 | EntityQueryResult res = null;
33 |
34 | using (CancellationTokenSource cts = new())
35 | {
36 | res = await client.ListEntitiesAsync(new EntityQuery()
37 | {
38 | PageSize = 99999999,
39 | EntityName = ScaleGroupCalls.ScaleGroupMaxConcurrentInstanceCount,
40 | FetchState = true
41 | }, cts.Token);
42 | }
43 |
44 | if (string.IsNullOrWhiteSpace(scaleGroupId))
45 | {
46 | foreach (DurableEntityStatus rr in res.Entities)
47 | {
48 | result.Add(rr.EntityId.EntityKey, rr.State.Value());
49 | }
50 | }
51 | else
52 | {
53 | foreach (DurableEntityStatus rr in res.Entities.Where(e => e.EntityId.EntityKey.Equals(scaleGroupId)))
54 | {
55 | result.Add(rr.EntityId.EntityKey, rr.State.ToObject());
56 | }
57 | }
58 |
59 | StringContent content = new(JsonConvert.SerializeObject(result));
60 |
61 | return new HttpResponseMessage(HttpStatusCode.OK)
62 | {
63 | Content = content
64 | };
65 | }
66 |
67 | ///
68 | /// Set the max instance count for scale group, default to wait for 5 seconds
69 | ///
70 | [FunctionName("Set" + ScaleGroupCalls.ScaleGroup)]
71 | public static async Task ScaleGroup([HttpTrigger(AuthorizationLevel.Anonymous, "post",
72 | Route = MicroflowPath + "/ScaleGroup/{scaleGroupId}/{maxWaitSeconds:int?}")] HttpRequestMessage req,
73 | [DurableClient] IDurableOrchestrationClient client, string scaleGroupId, int? maxWaitSeconds)
74 | {
75 | ScaleGroupState state = JsonConvert.DeserializeObject(await req.Content.ReadAsStringAsync());
76 |
77 | string instanceId = await client.StartNewAsync("SetScaleGroupMaxConcurrentCount", null, (scaleGroupId, state));
78 |
79 | return await client.WaitForCompletionOrCreateCheckStatusResponseAsync(req, instanceId, TimeSpan.FromSeconds(maxWaitSeconds is null
80 | ? 5
81 | : maxWaitSeconds.Value));
82 | }
83 |
84 | ///
85 | /// Set the scale group max concurrent instance count orchestration
86 | ///
87 | ///
88 | [Deterministic]
89 | [FunctionName("SetScaleGroupMaxConcurrentCount")]
90 | public static async Task SetScaleGroupMaxOrchestration([OrchestrationTrigger] IDurableOrchestrationContext context)
91 | {
92 | (string scaleGroupId, ScaleGroupState state) = context.GetInput<(string, ScaleGroupState)>();
93 |
94 | EntityId scaleGroupCountId = new(ScaleGroupCalls.ScaleGroupMaxConcurrentInstanceCount, scaleGroupId);
95 |
96 | await context.CallEntityAsync(scaleGroupCountId, MicroflowEntityKeys.Set, state);
97 | }
98 | }
99 | }
100 |
101 | #endif
--------------------------------------------------------------------------------
/MicroflowFunctionApp/Optional/WorkflowApi.cs:
--------------------------------------------------------------------------------
1 | #if DEBUG || RELEASE || !DEBUG_NO_UPSERT && !DEBUG_NO_UPSERT_FLOWCONTROL && !DEBUG_NO_UPSERT_FLOWCONTROL_SCALEGROUPS && !DEBUG_NO_UPSERT_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_UPSERT_FLOWCONTROL_STEPCOUNT && !DEBUG_NO_UPSERT_SCALEGROUPS && !DEBUG_NO_UPSERT_SCALEGROUPS_STEPCOUNT && !DEBUG_NO_UPSERT_STEPCOUNT && !RELEASE_NO_UPSERT && !RELEASE_NO_UPSERT_FLOWCONTROL && !RELEASE_NO_UPSERT_FLOWCONTROL_SCALEGROUPS && !RELEASE_NO_UPSERT_FLOWCONTROL_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_UPSERT_FLOWCONTROL_STEPCOUNT && !RELEASE_NO_UPSERT_SCALEGROUPS && !RELEASE_NO_UPSERT_SCALEGROUPS_STEPCOUNT && !RELEASE_NO_UPSERT_STEPCOUN
2 | using System.Threading.Tasks;
3 | using Microsoft.Azure.WebJobs;
4 | using Microsoft.Azure.WebJobs.Extensions.Http;
5 | using System.Net.Http;
6 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
7 | using MicroflowShared;
8 | using Microflow.Helpers;
9 |
10 | namespace MicroflowApi
11 | {
12 | public class WorkflowSplitCode
13 | {
14 | ///
15 | /// Inserts a workflow and starts it - skips workflow and global states, deletes, workflow save, create tables
16 | /// This will update the entire entity, so if its not the same steps then an unexpacted issue can occur
17 | ///
18 | [FunctionName("QuickInsertAndStartWorkflow")]
19 | public static async Task QuickInsertAndStartWorkflow([HttpTrigger(AuthorizationLevel.Anonymous, "post",
20 | Route = MicroflowModels.Constants.MicroflowPath + "/QuickInsertAndStartWorkflow/{workflowNameVersion}/{instanceId?}/{globalKey?}")] HttpRequestMessage req,
21 | [DurableClient] IDurableOrchestrationClient client, string workflowNameVersion, string instanceId, string globalKey)
22 | {
23 | await client.QuickInsert(await req.Content.ReadAsStringAsync(), globalKey);
24 |
25 | return await client.StartWorkflow(req, instanceId, workflowNameVersion);
26 | }
27 |
28 | ///
29 | /// This must be called at least once before a workflow runs,
30 | /// this is to prevent multiple concurrent instances from writing step data at workflow run,
31 | /// call UpsertWorkflow when something changed in the workflow, but do not always call this when concurrent multiple workflows
32 | ///
33 | [FunctionName("UpsertWorkflow")]
34 | public static async Task UpsertWorkflow([HttpTrigger(AuthorizationLevel.Anonymous, "post",
35 | Route = MicroflowModels.Constants.MicroflowPath + "/UpsertWorkflow/{globalKey?}")] HttpRequestMessage req,
36 | [DurableClient] IDurableEntityClient client, string globalKey)
37 | {
38 | return await client.UpsertWorkflow(await req.Content.ReadAsStringAsync(), globalKey);
39 | }
40 |
41 |
42 | ///
43 | /// Returns the workflow Json that was saved with UpsertWorkflow
44 | ///
45 | [FunctionName("GetWorkflow")]
46 | public static async Task GetWorkflowJson([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = MicroflowModels.Constants.MicroflowPath + "/GetWorkflow/{workflowName}")] HttpRequestMessage req,
47 | string workflowName)
48 | {
49 | return await WorkflowHelper.GetWorkflowJson(workflowName);
50 | }
51 | }
52 | }
53 | #endif
--------------------------------------------------------------------------------
/MicroflowFunctionApp/Properties/ServiceDependencies/MicroflowApp20210615143625 - Zip Deploy/storage1.arm.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "resourceGroupName": {
6 | "type": "string",
7 | "defaultValue": "ACloudGuru",
8 | "metadata": {
9 | "_parameterType": "resourceGroup",
10 | "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking."
11 | }
12 | },
13 | "resourceGroupLocation": {
14 | "type": "string",
15 | "defaultValue": "southafricanorth",
16 | "metadata": {
17 | "_parameterType": "location",
18 | "description": "Location of the resource group. Resource groups could have different location than resources."
19 | }
20 | },
21 | "resourceLocation": {
22 | "type": "string",
23 | "defaultValue": "[parameters('resourceGroupLocation')]",
24 | "metadata": {
25 | "_parameterType": "location",
26 | "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there."
27 | }
28 | }
29 | },
30 | "resources": [
31 | {
32 | "type": "Microsoft.Resources/resourceGroups",
33 | "name": "[parameters('resourceGroupName')]",
34 | "location": "[parameters('resourceGroupLocation')]",
35 | "apiVersion": "2019-10-01"
36 | },
37 | {
38 | "type": "Microsoft.Resources/deployments",
39 | "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat('csvimportservice', subscription().subscriptionId)))]",
40 | "resourceGroup": "[parameters('resourceGroupName')]",
41 | "apiVersion": "2019-10-01",
42 | "dependsOn": [
43 | "[parameters('resourceGroupName')]"
44 | ],
45 | "properties": {
46 | "mode": "Incremental",
47 | "template": {
48 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
49 | "contentVersion": "1.0.0.0",
50 | "resources": [
51 | {
52 | "sku": {
53 | "name": "Standard_LRS",
54 | "tier": "Standard"
55 | },
56 | "kind": "Storage",
57 | "name": "csvimportservice",
58 | "type": "Microsoft.Storage/storageAccounts",
59 | "location": "[parameters('resourceLocation')]",
60 | "apiVersion": "2017-10-01"
61 | }
62 | ]
63 | }
64 | }
65 | }
66 | ],
67 | "metadata": {
68 | "_dependencyType": "storage.azure"
69 | }
70 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/Properties/serviceDependencies.MicroflowApp20210615143625 - Zip Deploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "storage1": {
4 | "resourceId": "/subscriptions/[parameters('subscriptionId')]/resourceGroups/[parameters('resourceGroupName')]/providers/Microsoft.Storage/storageAccounts/csvimportservice",
5 | "type": "storage.azure",
6 | "connectionId": "AzureWebJobsStorage"
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/Properties/serviceDependencies.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "appInsights1": {
4 | "type": "appInsights"
5 | },
6 | "storage1": {
7 | "type": "storage",
8 | "connectionId": "AzureWebJobsStorage"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/Properties/serviceDependencies.local.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "appInsights1": {
4 | "type": "appInsights.sdk"
5 | },
6 | "storage1": {
7 | "type": "storage.emulator",
8 | "connectionId": "AzureWebJobsStorage"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/TableLogging/BlobLogResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Azure;
4 | using Azure.Storage.Blobs;
5 | using Microsoft.Azure.WebJobs;
6 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
7 | using static MicroflowModels.Constants;
8 |
9 | namespace Microflow.Logging
10 | {
11 | public static class BlobLogResponseActivity
12 | {
13 | [FunctionName(CallNames.LogMicroflowHttpData)]
14 | public static async Task BlobLogResponse([ActivityTrigger] (string blobName, string data, bool isRequest) input)
15 | {
16 | string prefix = input.isRequest ? "request-" : "response-";
17 |
18 | BlobContainerClient blobContainerClient = new("UseDevelopmentStorage=true", "microflow-httpdata");
19 |
20 | try
21 | {
22 | await blobContainerClient.UploadBlobAsync(prefix + input.blobName, BinaryData.FromString(input.data));
23 | }
24 | catch (RequestFailedException ex) when (ex.Status == 404)
25 | {
26 | await blobContainerClient.CreateIfNotExistsAsync();
27 |
28 | await BlobLogResponse(input);
29 | }
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/TableLogging/TableLogErrorActivity.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using MicroflowModels;
3 | using MicroflowModels.Helpers;
4 | using Microsoft.Azure.WebJobs;
5 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
6 | using static MicroflowModels.Constants;
7 |
8 | namespace Microflow.Logging
9 | {
10 | public static class TableLogginActivity
11 | {
12 | [FunctionName(CallNames.LogError)]
13 | public static async Task LogError([ActivityTrigger] LogErrorEntity logEntity)
14 | {
15 | await TableHelper.LogError(logEntity);
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/TableLogging/TableLogOrchestrationActivity.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microflow.Helpers;
3 | using Microflow.MicroflowTableModels;
4 | using Microsoft.Azure.WebJobs;
5 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
6 | using static MicroflowModels.Constants;
7 |
8 | namespace Microflow.Logging
9 | {
10 | public static class TableLogOrchestrationActivity
11 | {
12 | [FunctionName(CallNames.LogOrchestration)]
13 | public static async Task TableLogOrchestration([ActivityTrigger] LogOrchestrationEntity logEntity)
14 | {
15 | await logEntity.LogOrchestration();
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/TableLogging/TableLogStepActivity.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microflow.Helpers;
3 | using Microflow.MicroflowTableModels;
4 | using Microsoft.Azure.WebJobs;
5 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
6 | using static MicroflowModels.Constants;
7 |
8 | namespace Microflow.Logging
9 | {
10 | public static class BlobLogRequest
11 | {
12 | [FunctionName(CallNames.LogStep)]
13 | public static async Task TableLogActivity([ActivityTrigger] LogStepEntity logEntity)
14 | {
15 | await logEntity.LogStep();
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/TableLogging/TableLogWebhook.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microflow.Helpers;
3 | using Microflow.MicroflowTableModels;
4 | using Microsoft.Azure.WebJobs;
5 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
6 | using static MicroflowModels.Constants;
7 |
8 | namespace Microflow.Logging
9 | {
10 | public static class TableLogWebhook
11 | {
12 | [FunctionName(CallNames.LogWebhook)]
13 | public static async Task TableLogWebhookActivity([ActivityTrigger] LogWebhookEntity logEntity)
14 | {
15 | await logEntity.LogWebhook();
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/cloudconfig.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "AzureWebJobsDashboard",
4 | "value": "DefaultEndpointsProtocol=https;AccountName=microflowapp202202032142;AccountKey=lsGSaloGV9L6RCn7XlR/zDZe6xOnOv8BtdZACqxvJpkIFiqWSHiiAIg12LpwK5jt4Rk+WeeUG57ykIjgTiSaqg==;EndpointSuffix=core.windows.net",
5 | "slotSetting": false
6 | },
7 | {
8 | "name": "AzureWebJobsStorage",
9 | "value": "DefaultEndpointsProtocol=https;AccountName=microflowapp202202032142;AccountKey=lsGSaloGV9L6RCn7XlR/zDZe6xOnOv8BtdZACqxvJpkIFiqWSHiiAIg12LpwK5jt4Rk+WeeUG57ykIjgTiSaqg==;EndpointSuffix=core.windows.net",
10 | "slotSetting": false
11 | },
12 | {
13 | "name": "BaseUrl",
14 | "value": "https://microflowapp20220203214232.azurewebsites.net/",
15 | "slotSetting": false
16 | },
17 | {
18 | "name": "CallBack",
19 | "value": "webhook",
20 | "slotSetting": false
21 | },
22 | {
23 | "name": "FUNCTIONS_EXTENSION_VERSION",
24 | "value": "~4",
25 | "slotSetting": false
26 | },
27 | {
28 | "name": "FUNCTIONS_WORKER_RUNTIME",
29 | "value": "dotnet",
30 | "slotSetting": false
31 | },
32 | {
33 | "name": "MicroflowStorage",
34 | "value": "DefaultEndpointsProtocol=https;AccountName=microflowapp202202032142;AccountKey=lsGSaloGV9L6RCn7XlR/zDZe6xOnOv8BtdZACqxvJpkIFiqWSHiiAIg12LpwK5jt4Rk+WeeUG57ykIjgTiSaqg==;EndpointSuffix=core.windows.net",
35 | "slotSetting": false
36 | },
37 | {
38 | "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
39 | "value": "DefaultEndpointsProtocol=https;AccountName=microflowapp202202032142;AccountKey=lsGSaloGV9L6RCn7XlR/zDZe6xOnOv8BtdZACqxvJpkIFiqWSHiiAIg12LpwK5jt4Rk+WeeUG57ykIjgTiSaqg==;EndpointSuffix=core.windows.net",
40 | "slotSetting": false
41 | },
42 | {
43 | "name": "WEBSITE_CONTENTSHARE",
44 | "value": "microflowapp20220203214232",
45 | "slotSetting": false
46 | }
47 | ]
--------------------------------------------------------------------------------
/MicroflowFunctionApp/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "extensions": {
4 | "durableTask": {
5 | "logReplayEvents": "false",
6 | "hubName": "MicroflowTaskHub"
7 | },
8 | "http": {
9 | "routePrefix": ""
10 | }
11 | },
12 | "logging": {
13 | "logLevel": {
14 | "DurableTask.AzureStorage": "None",
15 | "DurableTask.Core": "None"
16 | },
17 | "applicationInsights": {
18 | "samplingExcludedTypes": "Request",
19 | "samplingSettings": {
20 | "isEnabled": false
21 | }
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/MicroflowFunctionApp/local.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
5 | "MicroflowStorage": "UseDevelopmentStorage=true",
6 | "FUNCTIONS_WORKER_RUNTIME": "dotnet",
7 | "BasePrefix": "http://",
8 | "PollingMaxHours": 240,
9 | "PollingIntervalSeconds": 5,
10 | "PollingIntervalMaxSeconds": 15,
11 | "AzureWebJobsSecretStorageType": "files"
12 | }
13 | }
--------------------------------------------------------------------------------
/MicroflowModels/Constants.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MicroflowModels
4 | {
5 | public static class Constants
6 | {
7 |
8 | #if DEBUG || RELEASE || !DEBUG_NO_SCALEGROUPS && !RELEASE_NO_SCALEGROUPS
9 | public static class ScaleGroupCalls
10 | {
11 | public const string CanExecuteNowInScaleGroup = "CanExecuteNowInScaleGroup";
12 | public const string CanExecuteNowInScaleGroupCount = "CanExecuteNowInScaleGroupCount";
13 | public const string ScaleGroupMaxConcurrentInstanceCount = "ScaleGroupMaxConcurrentInstanceCount";
14 | public const string ScaleGroup = "ScaleGroup";
15 | }
16 | #endif
17 |
18 | public static class PollingConfig
19 | {
20 | public static readonly long PollingMaxHours = Convert.ToInt64(Environment.GetEnvironmentVariable("PollingMaxHours"));
21 | public static readonly int PollingIntervalMaxSeconds = Convert.ToInt32(Environment.GetEnvironmentVariable("PollingIntervalMaxSeconds"));
22 | public static readonly int PollingIntervalSeconds = Convert.ToInt32(Environment.GetEnvironmentVariable("PollingIntervalSeconds"));
23 | }
24 |
25 | public const string MicroflowPath = "microflow/v1";
26 |
27 | public static class CallNames
28 | {
29 | public static readonly string BaseUrl = $"{Environment.GetEnvironmentVariable("BasePrefix")}{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/{MicroflowPath}";
30 | public const string CanExecuteNow = "CanExecuteNow";
31 | public const string ExecuteStep = "ExecuteStep";
32 | public const string GetStepInternal = "GetStepInternal";
33 | public const string LogError = "LogError";
34 | public const string LogStep = "LogStep";
35 | public const string LogWebhook = "TableLogWebhook";
36 | public const string LogMicroflowHttpData = "BlobLogRequest";
37 | public const string LogOrchestration = "LogOrchestration";
38 | public const string HttpCallOrchestrator = "HttpCallOrchestrator";
39 | public const string WebhookOrchestrator = "WebhookOrchestrator";
40 | public const string MicroflowStart = "MicroflowStart";
41 | public const string MicroflowStartOrchestration = "MicroflowStartOrchestration";
42 | public const string StepFlowControl = "StepFlowControl";
43 | public const string RunFromSteps = "RunFromSteps";
44 | public const string RunFromStepsOrchestrator = "RunFromStepsOrchestrator";
45 | }
46 |
47 | public static class MicroflowStates
48 | {
49 | public const int Ready = 0;
50 | public const int Paused = 1;
51 | public const int Stopped = 2;
52 | }
53 |
54 | public static class MicroflowEntities
55 | {
56 | public const string StepCount = "StepCount";
57 | public const string CanExecuteNowCount = "CanExecuteNowCount";
58 | public const string StepFlowState= "StepFlowState";
59 | }
60 |
61 | public static class MicroflowEntityKeys
62 | {
63 | public const string Add = "add";
64 | public const string Subtract = "subtract";
65 | public const string Read = "get";
66 | public const string Delete = "delete";
67 | public const string Set = "set";
68 | }
69 |
70 | public static class MicroflowStateKeys
71 | {
72 | public const string WorkflowState = "WorkflowState";
73 | public const string GlobalState = "GlobalState";
74 | }
75 |
76 | public static class MicroflowControlKeys
77 | {
78 | public const string Ready = "ready";
79 | public const string Pause = "pause";
80 | public const string Stop = "stop";
81 | public const string Read = "get";
82 | }
83 |
84 | public static readonly char[] Splitter = { ',', ';' };
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/MicroflowModels/Helpers/TableHelper.cs:
--------------------------------------------------------------------------------
1 | using Azure.Data.Tables;
2 | using System;
3 | using System.Net;
4 | using System.Net.Http;
5 | using System.Threading.Tasks;
6 |
7 | namespace MicroflowModels.Helpers
8 | {
9 | public static class TableHelper
10 | {
11 | #region Formatting
12 |
13 | public static string GetTableLogRowKeyDescendingByDate(DateTime date, string postfix)
14 | {
15 | return $"{String.Format("{0:D19}", DateTime.MaxValue.Ticks - date.Ticks)}{postfix}";
16 | }
17 |
18 | public static string GetTableRowKeyDescendingByDate()
19 | {
20 | return $"{String.Format("{0:D19}", DateTime.MaxValue.Ticks - DateTime.UtcNow.Ticks)}{Guid.NewGuid()}";
21 | }
22 |
23 | #endregion
24 |
25 | public static async Task UpsertStep(this HttpCallWithRetries step)
26 | {
27 | TableClient tableClient = GetStepsTable();
28 |
29 | await tableClient.UpsertEntityAsync(step);
30 | }
31 |
32 | public static async Task LogError(this LogErrorEntity logEntity)
33 | {
34 | TableClient tableClient = GetErrorsTable();
35 |
36 | await tableClient.UpsertEntityAsync(logEntity);
37 | }
38 |
39 | public static async Task LogError(string workflowName, string globalKey, string runId, Exception e)
40 | {
41 | await new LogErrorEntity(workflowName, -999, e.Message, globalKey, runId).LogError();
42 |
43 | HttpResponseMessage resp = new(HttpStatusCode.InternalServerError)
44 | {
45 | Content = new StringContent(e.Message)
46 | };
47 |
48 | return resp;
49 | }
50 |
51 | public static async Task GetStep(this MicroflowRun workflowRun)
52 | {
53 | TableClient tableClient = GetStepsTable();
54 |
55 | return await tableClient.GetEntityAsync(workflowRun.WorkflowName, workflowRun.RunObject.StepNumber);
56 | }
57 |
58 | public static async Task GetWebhook(string webhookId)
59 | {
60 | TableClient tableClient = GetWebhooksTable();
61 |
62 | return await tableClient.GetEntityAsync(webhookId, "0", new string[] { "WebhookSubStepsMapping" });
63 | }
64 |
65 | public static TableClient GetErrorsTable()
66 | {
67 | TableServiceClient tableClient = GetTableClient();
68 |
69 | return tableClient.GetTableClient($"MicroflowLogErrors");
70 | }
71 |
72 | public static TableClient GetStepsTable()
73 | {
74 | TableServiceClient tableClient = GetTableClient();
75 |
76 | return tableClient.GetTableClient($"MicroflowStepConfigs");
77 | }
78 |
79 | public static TableServiceClient GetTableClient()
80 | {
81 | return new TableServiceClient(Environment.GetEnvironmentVariable("MicroflowStorage"));
82 | }
83 |
84 | public static TableClient GetLogWebhookTable()
85 | {
86 | TableServiceClient tableClient = GetTableClient();
87 |
88 | return tableClient.GetTableClient($"MicroflowLogWebhooks");
89 | }
90 |
91 | public static TableClient GetWebhooksTable()
92 | {
93 | TableServiceClient tableClient = GetTableClient();
94 |
95 | return tableClient.GetTableClient($"MicroflowWebhookConfigs");
96 | }
97 |
98 | public static TableClient GetLogOrchestrationTable()
99 | {
100 | TableServiceClient tableClient = GetTableClient();
101 |
102 | return tableClient.GetTableClient($"MicroflowLogOrchestrations");
103 | }
104 |
105 | public static TableClient GetLogStepsTable()
106 | {
107 | TableServiceClient tableClient = GetTableClient();
108 |
109 | return tableClient.GetTableClient($"MicroflowLogSteps");
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/MicroflowModels/Interfaces/IHttpCall.cs:
--------------------------------------------------------------------------------
1 | namespace MicroflowModels
2 | {
3 | public interface IHttpCall : IStepEntity
4 | {
5 | int WebhookTimeoutSeconds { get; set; }
6 | bool AsynchronousPollingEnabled { get; set; }
7 | public string WebhookId { get; set; }
8 | public bool EnableWebhook { get; set; }
9 | string CalloutUrl { get; set; }
10 | string GlobalKey { get; set; }
11 | bool IsHttpGet { get; set; }
12 | string MainOrchestrationId { get; set; }
13 | string RunId { get; set; }
14 | string StepId { get; set; }
15 | bool StopOnCalloutFailure { get; set; }
16 | string SubStepsToRunForCalloutFailure { get; set; }
17 | bool StopOnWebhookTimeout { get; set; }
18 | string ScaleGroupId { get; set; }
19 | string SubStepsToRunForWebhookTimeout { get; set; }
20 | bool ForwardResponseData { get; set; }
21 | }
22 | }
--------------------------------------------------------------------------------
/MicroflowModels/Interfaces/IHttpCallWithRetries.cs:
--------------------------------------------------------------------------------
1 | namespace MicroflowModels
2 | {
3 | public interface IHttpCallWithRetries : IHttpCall
4 | {
5 | double RetryBackoffCoefficient { get; set; }
6 | int RetryDelaySeconds { get; set; }
7 | int RetryMaxDelaySeconds { get; set; }
8 | int RetryMaxRetries { get; set; }
9 | int RetryTimeoutSeconds { get; set; }
10 | }
11 | }
--------------------------------------------------------------------------------
/MicroflowModels/Interfaces/IMicroflowRun.cs:
--------------------------------------------------------------------------------
1 | namespace MicroflowModels
2 | {
3 | public interface IMicroflowRun
4 | {
5 | int CurrentLoop { get; set; }
6 | int Loop { get; set; }
7 | string OrchestratorInstanceId { get; set; }
8 | int PausedStepId { get; set; }
9 | string WorkflowName { get; set; }
10 | RunObject RunObject { get; set; }
11 | }
12 | }
--------------------------------------------------------------------------------
/MicroflowModels/Interfaces/IStepEntity.cs:
--------------------------------------------------------------------------------
1 | using Azure.Data.Tables;
2 | using System.Collections.Generic;
3 |
4 | namespace MicroflowModels
5 | {
6 | public interface IStepEntity : ITableEntity
7 | {
8 | Dictionary MergeFields { get; set; }
9 | string SubSteps { get; set; }
10 | }
11 | }
--------------------------------------------------------------------------------
/MicroflowModels/MicroflowModels.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | DEBUG;RELEASE;DEBUG_NO_SCALEGROUPS;RELEASE_NO_SCALEGROUPS
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/MicroflowModels/Models/MicroflowRun.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace MicroflowModels
4 | {
5 | ///
6 | /// This is the minimalist workflow object that is passed during execution
7 | ///
8 | public class MicroflowRun : IMicroflowRun
9 | {
10 | public string WorkflowName { get; set; }
11 | public RunObject RunObject { get; set; }
12 | public int PausedStepId { get; set; }
13 | public int Loop { get; set; } = 1;
14 | public int CurrentLoop { get; set; } = 1;
15 | public string OrchestratorInstanceId { get; set; }
16 | }
17 |
18 | ///
19 | /// This is the minimalist run object that is passed during execution inside the workflow run object
20 | ///
21 | public class RunObject
22 | {
23 | public string RunId { get; set; }
24 | public string StepNumber { get; set; }
25 | public string GlobalKey { get; set; }
26 | public MicroflowHttpResponse MicroflowStepResponseData { get; set; }
27 | }
28 |
29 | ///
30 | /// Used to hold Microflow specific http status code results
31 | ///
32 | public class MicroflowHttpResponseBase
33 | {
34 | public bool Success { get; set; }
35 | public int HttpResponseStatusCode { get; set; }
36 | public string Content { get; set; }
37 | }
38 |
39 | public class MicroflowHttpResponse : MicroflowHttpResponseBase//, IMicroflowHttpResponse
40 | {
41 | public List SubStepsToRun { get; set; }
42 | public string Action { get; set; }
43 | public CalloutOrWebhook CalloutOrWebhook { get;set;}
44 | }
45 | //public interface IMicroflowHttpResponse
46 | //{
47 | // int HttpResponseStatusCode { get; set; }
48 | // string Content { get; set; }
49 | // bool Success { get; set; }
50 | // public List SubStepsToRun { get; set; }
51 | //}
52 | }
53 |
--------------------------------------------------------------------------------
/MicroflowModels/Models/Models.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.Serialization;
4 |
5 | namespace MicroflowModels
6 | {
7 | #region POCOs
8 |
9 | public enum CalloutOrWebhook
10 | {
11 | Callout,
12 | Webhook
13 | }
14 |
15 | public class ScaleGroupState
16 | {
17 | public int ScaleGroupMaxConcurrentInstanceCount { get; set; }
18 | public int PollingIntervalSeconds { get; set; }
19 | public int PollingIntervalMaxSeconds { get; set; }
20 | public int PollingMaxHours { get; set; }
21 | }
22 |
23 | public class SubStepsMappingForActions
24 | {
25 | public string WebhookAction { get; set; }
26 | public List SubStepsToRunForAction { get; set; } = new();
27 | }
28 |
29 | public class MicroflowPostData
30 | {
31 | public string WorkflowName { get; set; }
32 | public string MainOrchestrationId { get; set; }
33 | public string SubOrchestrationId { get; set; }
34 | public string Webhook { get; set; }
35 | public string RunId { get; set; }
36 | public int StepNumber { get; set; }
37 | public string StepId { get; set; }
38 | public string GlobalKey { get; set; }
39 | public string PostData { get; set; }
40 | }
41 |
42 | public class MicroflowBase
43 | {
44 | public string WorkflowName { get; set; }
45 | }
46 |
47 | public class Microflow : MicroflowBase
48 | {
49 | public List Steps { get; set; }
50 |
51 | [DataMember(Name = "MergeFields", EmitDefaultValue = false)]
52 | public Dictionary MergeFields { get; set; } = new Dictionary();
53 |
54 | public RetryOptions DefaultRetryOptions { get; set; }
55 |
56 | public string WorkflowVersion { get;set; }
57 | }
58 |
59 | public class Step
60 | {
61 | public Step() { }
62 |
63 | public Step(int stepNumber, string calloutUrl, string stepId = null)
64 | {
65 | if (stepId == null)
66 | {
67 | StepId = $"{stepNumber}_{Guid.NewGuid()}";
68 | }
69 | else
70 | {
71 | StepId = stepId;
72 | }
73 | CalloutUrl = calloutUrl;
74 | StepNumber = stepNumber;
75 | }
76 |
77 | public Step(string stepId, List subSteps)
78 | {
79 | StepId = stepId;
80 | SubSteps = subSteps;
81 | }
82 |
83 | public int StepNumber { get; set; }
84 | public string StepId { get; set; }
85 |
86 | [DataMember(Name = "SubSteps", EmitDefaultValue = false)]
87 | public List SubSteps { get; set; } = new List();
88 |
89 | [DataMember(Name = "WaitForAllParents", EmitDefaultValue = false)]
90 | public bool WaitForAllParents { get; set; } = true;
91 |
92 | public string CalloutUrl { get; set; }
93 | public int CalloutTimeoutSeconds { get; set; } = 1000;
94 | public bool StopOnCalloutFailure { get; set; }
95 | public List SubStepsToRunForCalloutFailure { get; set; }
96 | public bool IsHttpGet { get; set; }
97 | public bool EnableWebhook { get; set; }
98 | public string WebhookId { get; set; }
99 | public bool StopOnWebhookTimeout { get; set; } = true;
100 | public List SubStepsToRunForWebhookTimeout { get; set; }
101 | public int WebhookTimeoutSeconds { get; set; } = 1000;
102 |
103 | [DataMember(Name = "WebhookSubStepsMapping", EmitDefaultValue = false)]
104 | public List WebhookSubStepsMapping { get; set; }
105 |
106 | public string ScaleGroupId { get; set; }
107 | public bool AsynchronousPollingEnabled { get; set; } = true;
108 | public bool ForwardResponseData { get; set; }
109 |
110 | [DataMember(Name = "RetryOptions", EmitDefaultValue = false)]
111 | public RetryOptions RetryOptions { get; set; }
112 | }
113 |
114 | public class RetryOptions
115 | {
116 | public int DelaySeconds { get; set; } = 5;
117 | public int MaxDelaySeconds { get; set; } = 120;
118 | public int MaxRetries { get; set; } = 15;
119 | public double BackoffCoefficient { get; set; } = 5;
120 | public int TimeOutSeconds { get; set; } = 300;
121 | }
122 |
123 | #endregion
124 | }
125 |
--------------------------------------------------------------------------------
/MicroflowModels/TableModels/TableModels.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Azure;
4 | using Azure.Data.Tables;
5 | using MicroflowModels.Helpers;
6 |
7 | namespace MicroflowModels
8 | {
9 | #region TableEntity
10 |
11 | public class Webhook : ITableEntity
12 | {
13 | public Webhook() { }
14 |
15 | public Webhook(string webhookId, string webhookSubStepsMapping)
16 | {
17 | PartitionKey = webhookId;
18 | RowKey = "0";
19 | WebhookSubStepsMapping = webhookSubStepsMapping;
20 | }
21 |
22 | public string WebhookSubStepsMapping { get; set; }
23 | public string PartitionKey { get; set; }
24 | public string RowKey { get; set; }
25 | public DateTimeOffset? Timestamp { get; set; }
26 | public ETag ETag { get; set; }
27 | }
28 |
29 | //public class WebhookSubStepsMappingEntity : ITableEntity
30 | //{
31 | // public string WebhookSubStepsMapping { get; set; }
32 |
33 | // public string PartitionKey { get; set; }
34 | // public string RowKey { get; set; }
35 | // public DateTimeOffset? Timestamp { get; set; }
36 | // public ETag ETag { get; set; }
37 | //}
38 |
39 | ///
40 | /// Used for step level logging
41 | ///
42 | public class LogErrorEntity : ITableEntity
43 | {
44 | public LogErrorEntity() { }
45 |
46 | public LogErrorEntity(string workflowName, int stepNumber, string message, string globalKey, string runId = null)
47 | {
48 | PartitionKey = workflowName + "__" + runId;
49 | RowKey = TableHelper.GetTableRowKeyDescendingByDate();
50 | StepNumber = stepNumber;
51 | Message = message;
52 | Date = DateTime.UtcNow;
53 | GlobalKey = globalKey;
54 | }
55 |
56 | public string GlobalKey { get; set; }
57 | public int StepNumber { get; set; }
58 | public DateTime Date { get; set; }
59 | public string Message { get; set; }
60 | public string PartitionKey { get; set; }
61 | public string RowKey { get; set; }
62 | public DateTimeOffset? Timestamp { get; set; }
63 | public ETag ETag { get; set; }
64 | }
65 |
66 | ///
67 | /// Base class for http calls
68 | ///
69 | public class StepEntity : ITableEntity, IStepEntity
70 | {
71 | public StepEntity() { }
72 |
73 | public string SubSteps { get; set; }
74 | public Dictionary MergeFields { get; set; }
75 | public string PartitionKey { get; set; }
76 | public string RowKey { get; set; }
77 | public DateTimeOffset? Timestamp { get; set; }
78 | public ETag ETag { get; set; }
79 | }
80 |
81 | ///
82 | /// Basic http call with no retries
83 | ///
84 | public class HttpCall : StepEntity, IHttpCall
85 | {
86 | public HttpCall() { }
87 |
88 | public HttpCall(string workflow, string stepNumber, string stepId, string subSteps)
89 | {
90 | PartitionKey = workflow;
91 | RowKey = stepNumber;
92 | SubSteps = subSteps;
93 | StepId = stepId;
94 | }
95 | public bool AsynchronousPollingEnabled { get; set; }
96 | public string CalloutUrl { get; set; }
97 | public string WebhookId { get; set; }
98 | public bool EnableWebhook { get; set; }
99 | public int WebhookTimeoutSeconds { get; set; } = 1000;
100 | public bool StopOnWebhookTimeout { get; set; }
101 | public int CalloutTimeoutSeconds { get; set; }
102 | public bool IsHttpGet { get; set; }
103 | public string StepId { get; set; }
104 | public string ScaleGroupId { get; set; }
105 | public bool ForwardResponseData { get; set; }
106 | public string SubStepsToRunForWebhookTimeout { get; set; }
107 | public bool StopOnCalloutFailure { get; set; }
108 | public string SubStepsToRunForCalloutFailure { get; set; }
109 |
110 | //[IgnoreDataMember]
111 | public string GlobalKey { get; set; }
112 |
113 | //[IgnoreDataMember]
114 | public string RunId { get; set; }
115 |
116 | //[IgnoreDataMember]
117 | public string MainOrchestrationId { get; set; }
118 | }
119 |
120 | ///
121 | /// Http call with retries
122 | ///
123 | public class HttpCallWithRetries : HttpCall, IHttpCallWithRetries
124 | {
125 | public HttpCallWithRetries() { }
126 |
127 | public HttpCallWithRetries(string workflow, string stepNumber, string stepId, string subSteps)
128 | {
129 | PartitionKey = workflow;
130 | RowKey = stepNumber;
131 | SubSteps = subSteps;
132 | StepId = stepId;
133 | }
134 |
135 | // retry options
136 | public int RetryDelaySeconds { get; set; }
137 | public int RetryMaxDelaySeconds { get; set; }
138 | public int RetryMaxRetries { get; set; }
139 | public double RetryBackoffCoefficient { get; set; }
140 | public int RetryTimeoutSeconds { get; set; }
141 | }
142 |
143 | ///
144 | /// This is used to check if all parents have completed
145 | ///
146 | public class ParentCountCompletedEntity : ITableEntity
147 | {
148 | public ParentCountCompletedEntity() { }
149 |
150 | public ParentCountCompletedEntity(string runId, string stepId)
151 | {
152 | PartitionKey = runId;
153 | RowKey = stepId;
154 | }
155 |
156 | public int ParentCountCompleted { get; set; }
157 | public string PartitionKey { get; set; }
158 | public string RowKey { get; set; }
159 | public DateTimeOffset? Timestamp { get; set; }
160 | public ETag ETag { get; set; }
161 | }
162 |
163 | #endregion
164 | }
--------------------------------------------------------------------------------
/MicroflowSDK/CompilerDirectiveMaker.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace MicroflowSDK
5 | {
6 | public class CompilerDirectiveMaker
7 | {
8 | public static string MakeCompilerDirectiveString(string largestCombo)
9 | {
10 | string[] arr = largestCombo.Split('_');
11 |
12 | List combinations = CreateCombinations(0, "", arr);
13 | List list = new List();
14 |
15 |
16 | for (int i1 = 1; i1 < arr.Length; i1++)
17 | {
18 | string i = arr[i1];
19 |
20 | for (int yy = 1; yy < combinations.Count; yy++)
21 | {
22 | string j = combinations[yy];
23 |
24 | if (!j.StartsWith(i))
25 | {
26 | int idx = j.IndexOf(i);
27 | if (idx > -1)
28 | {
29 | combinations[yy] = j.Insert(idx, "_");
30 | }
31 | }
32 | }
33 | }
34 |
35 | for (int i1 = 0; i1 < combinations.Count; i1++)
36 | {
37 | list.Add("RELEASE_NO_" + combinations[i1]);
38 | list.Add("DEBUG_NO_" + combinations[i1]);
39 | }
40 |
41 | list.Add("DEBUG");
42 | list.Add("RELEASE");
43 |
44 | //var tuple = GetCSharpDirectiveForOption()
45 | list.Sort();
46 |
47 | return $"{string.Join(";", list)}";
48 | }
49 |
50 | private static List CreateCombinations(int startIndex, string pair, string[] initialArray)
51 | {
52 | List combinations = new List();
53 | for (int i = startIndex; i < initialArray.Length; i++)
54 | {
55 | string value = $"{pair}{initialArray[i]}";
56 | combinations.Add(value);
57 | combinations.AddRange(CreateCombinations(i + 1, value, initialArray));
58 | }
59 |
60 | return combinations;
61 | }
62 |
63 | public static string GetCompilerDirectiveForOptionToExclude(bool IsDebug, string key, string config)
64 | {
65 | List li = config.Split(';').ToList();
66 |
67 | string res = $"#if DEBUG || RELEASE || !{string.Join(" && !", li.FindAll(r => r.Contains(key)))}";
68 |
69 | return res;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/MicroflowSDK/HttpBlobDataManager.cs:
--------------------------------------------------------------------------------
1 | using Azure.Storage.Blobs;
2 | using Azure.Storage.Blobs.Models;
3 | using System.Threading.Tasks;
4 |
5 | namespace MicroflowSDK
6 | {
7 | public static class HttpBlobDataManager
8 | {
9 | public static async Task GetHttpBlob(bool isRequest, string workflowName, int stepNumber, string runId, string subinstanceId)
10 | {
11 | string prefix = isRequest ? "request-" : "response-";
12 |
13 | BlobContainerClient blobContainerClient = new("UseDevelopmentStorage=true", "microflow-httpdata");
14 |
15 | var blobClient = blobContainerClient.GetBlobClient($"{prefix}{workflowName}@{stepNumber}@{runId}@{subinstanceId}");
16 |
17 | BlobDownloadResult downloadResult = await blobClient.DownloadContentAsync();
18 |
19 | return downloadResult.Content.ToString();
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/MicroflowSDK/MicroflowSDK.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/MicroflowSDK/ScaleGroupsManager.cs:
--------------------------------------------------------------------------------
1 | using MicroflowModels;
2 | using System.Collections.Generic;
3 | using System.Net.Http;
4 | using System.Net.Http.Json;
5 | using System.Text.Json;
6 | using System.Threading.Tasks;
7 |
8 | namespace MicroflowSDK
9 | {
10 | public static class ScaleGroupsManager
11 | {
12 | public static async Task SetMaxInstanceCountForScaleGroup(string scaleGroupId, ScaleGroupState scaleGroupState, string baseUrl, HttpClient httpClient, int waitForResultSeconds = 30)
13 | {
14 |
15 | return await httpClient.PostAsJsonAsync($"{baseUrl}/ScaleGroup/{scaleGroupId}/{waitForResultSeconds}", scaleGroupState, new JsonSerializerOptions(JsonSerializerDefaults.General));
16 | }
17 |
18 | public static async Task> GetScaleGroupsWithMaxInstanceCounts(string scaleGroupId, string baseUrl, HttpClient httpClient)
19 | {
20 | Dictionary li = null;
21 |
22 | if (!string.IsNullOrWhiteSpace(scaleGroupId))
23 | {
24 | string t = await httpClient.GetStringAsync($"{baseUrl}/ScaleGroup/{scaleGroupId}");
25 | li = JsonSerializer.Deserialize>(t);
26 | }
27 | else
28 | {
29 | string t = await httpClient.GetStringAsync($"{baseUrl}/ScaleGroup");
30 | li = JsonSerializer.Deserialize>(t);
31 | }
32 |
33 | return li;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/MicroflowSDK/StepsManager.cs:
--------------------------------------------------------------------------------
1 | using MicroflowModels;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace MicroflowSDK
6 | {
7 | public static class StepsManager
8 | {
9 | public static void AddParentSteps(this Step step, params Step[] parents)
10 | {
11 | foreach (Step parentStep in parents)
12 | {
13 | parentStep.SubSteps.Add(step.StepNumber);
14 | }
15 | }
16 |
17 | public static void AddSubSteps(this Step step, params Step[] subSteps)
18 | {
19 | foreach (Step subStep in subSteps)
20 | {
21 | step.SubSteps.Add(subStep.StepNumber);
22 | }
23 | }
24 |
25 | public static void AddSubStepRange(this Step step, List steps, int fromId, int toId)
26 | {
27 | List li = steps.FindAll(s => s.StepNumber >= fromId && s.StepNumber <= toId);
28 | step.SubSteps.AddRange(from s in li select s.StepNumber);
29 | }
30 |
31 | public static void SetRetryForSteps(params Step[] steps)
32 | {
33 | foreach (Step step in steps)
34 | {
35 | step.RetryOptions = new RetryOptions();
36 | }
37 | }
38 |
39 | public static void SetRetryForSteps(int delaySeconds, int maxDelaySeconds, int maxRetries, int timeOutSeconds, int backoffCoefficient, params Step[] steps)
40 | {
41 | RetryOptions retryOptions = new RetryOptions()
42 | {
43 | DelaySeconds = delaySeconds,
44 | MaxDelaySeconds = maxDelaySeconds,
45 | MaxRetries = maxRetries,
46 | TimeOutSeconds = timeOutSeconds,
47 | BackoffCoefficient = backoffCoefficient
48 | };
49 |
50 | foreach(Step step in steps)
51 | {
52 | step.RetryOptions = retryOptions;
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/MicroflowSDK/WebhooksManager.cs:
--------------------------------------------------------------------------------
1 | namespace MicroflowSDK
2 | {
3 | public static class WebhooksManager
4 | {
5 | public static void GetWebhooks(string workflowName, int stepNumber, string runId)
6 | {
7 |
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/MicroflowSDK/WorkflowManager.cs:
--------------------------------------------------------------------------------
1 | using MicroflowModels;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.Http;
5 | using System.Net.Http.Json;
6 | using System.Text.Json;
7 | using System.Threading.Tasks;
8 |
9 | namespace MicroflowSDK
10 | {
11 | public class PassThroughParams
12 | {
13 | public bool WorkflowName { get; set; } = true;
14 | public bool MainOrchestrationId { get; set; } = true;
15 | public bool SubOrchestrationId { get; set; } = true;
16 | public bool WebhookId { get; set; } = true;
17 | public bool RunId { get; set; } = true;
18 | public bool StepNumber { get; set; } = true;
19 | public bool GlobalKey { get; set; } = true;
20 | public bool StepId { get; set; } = true;
21 | }
22 |
23 | public static class WorkflowManager
24 | {
25 | public static HttpClient HttpClient = new();
26 |
27 | public static Step Step(this Microflow microFlow, int stepNumber) => microFlow.Steps.First(s => s.StepNumber == stepNumber);
28 |
29 | public static Step StepNumber(this List steps, int stepNumber) => steps.First(s => s.StepNumber == stepNumber);
30 |
31 | public static List CreateSteps(int count, int fromId, string defaultCalloutURI = "")
32 | {
33 | List stepsList = new();
34 |
35 | for (; fromId <= count; fromId++)
36 | {
37 | Step step = new(fromId, defaultCalloutURI, "myStep " + fromId);
38 | stepsList.Add(step);
39 | }
40 |
41 | return stepsList;
42 | }
43 |
44 | public static async Task UpsertWorkFlow(Microflow workflow, string baseUrl)
45 | {
46 | // Upsert
47 | HttpResponseMessage result = await HttpClient.PostAsJsonAsync(baseUrl + "/UpsertWorkflow/", workflow, new JsonSerializerOptions(JsonSerializerDefaults.General));
48 |
49 | if (result.StatusCode == System.Net.HttpStatusCode.OK)
50 | {
51 | return true;
52 | }
53 |
54 | return false;
55 | }
56 |
57 | public static async Task QuickInsertAndStartWorkFlow(Microflow workflow, string baseUrl)
58 | {
59 | // Upsert and start
60 | return await HttpClient.PostAsJsonAsync(baseUrl + $"/QuickInsertAndStartWorkflow/{workflow.WorkflowName}@{workflow.WorkflowVersion}", workflow, new JsonSerializerOptions(JsonSerializerDefaults.General));
61 | }
62 |
63 | public static async Task WaitForWorkflowCompleted(HttpResponseMessage resp)
64 | {
65 | OrchResult? res = JsonSerializer.Deserialize(await resp.Content.ReadAsStringAsync());
66 |
67 | string res2result = "";
68 |
69 | while (!res2result.Contains("\"runtimeStatus\":\"Completed\""))
70 | {
71 | await Task.Delay(2000);
72 |
73 | HttpResponseMessage res2 = await HttpClient.GetAsync(res.statusQueryGetUri);
74 | res2result = await res2.Content.ReadAsStringAsync();
75 |
76 | if (res2result.Contains("\"runtimeStatus\":\"Completed\""))
77 | break;
78 | }
79 |
80 | return res.id;
81 | }
82 | }
83 | public class OrchResult
84 | {
85 | public string id { get; set; }
86 | public string purgeHistoryDeleteUri { get; set; }
87 | public string sendEventPostUri { get; set; }
88 | public string statusQueryGetUri { get; set; }
89 | public string terminatePostUri { get; set; }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/MicroflowShared/TableReferences.cs:
--------------------------------------------------------------------------------
1 | namespace MicroflowShared
2 | {
3 | public static class TableReferences
4 | {
5 | #region Get table references
6 |
7 |
8 | #endregion
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/MicroflowTest/LogReader.cs:
--------------------------------------------------------------------------------
1 | using Azure.Data.Tables;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Microflow.MicroflowTableModels;
5 |
6 | namespace MicroflowTest
7 | {
8 | internal class LogReader
9 | {
10 | public static async Task> GetOrchLog(string workflowName)
11 | {
12 | List li = new();
13 | TableClient tableClient = GetLogOrchestrationTable();
14 |
15 | Azure.AsyncPageable logTask = tableClient.QueryAsync(filter: $"PartitionKey eq '{workflowName}'");
16 |
17 | await foreach(LogOrchestrationEntity log in logTask)
18 | {
19 | li.Add(log);
20 | }
21 |
22 | return li;
23 | }
24 |
25 | public static async Task> GetStepsLog(string workflowName, string instanceId)
26 | {
27 | List li = new();
28 | TableClient tableClient = GetStepsLogTable();
29 |
30 | Azure.AsyncPageable logTask = tableClient.QueryAsync(filter: $"PartitionKey eq '{workflowName}__{instanceId}'");
31 |
32 | await foreach (LogStepEntity log in logTask)
33 | {
34 | li.Add(log);
35 | }
36 |
37 | return li;
38 | }
39 |
40 | public static TableClient GetErrorsTable()
41 | {
42 | TableServiceClient tableClient = GetTableClient();
43 |
44 | return tableClient.GetTableClient($"MicroflowLogErrors");
45 | }
46 |
47 | public static TableClient GetStepsLogTable()
48 | {
49 | TableServiceClient tableClient = GetTableClient();
50 |
51 | return tableClient.GetTableClient($"MicroflowLogSteps");
52 | }
53 |
54 | public static TableClient GetStepsTable()
55 | {
56 | TableServiceClient tableClient = GetTableClient();
57 |
58 | return tableClient.GetTableClient($"MicroflowStepConfigs");
59 | }
60 |
61 | public static TableClient GetLogOrchestrationTable()
62 | {
63 | TableServiceClient tableClient = GetTableClient();
64 |
65 | return tableClient.GetTableClient($"MicroflowLogOrchestrations");
66 | }
67 |
68 | public static TableServiceClient GetTableClient()
69 | {
70 | return new TableServiceClient("UseDevelopmentStorage=true");
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/MicroflowTest/MicroflowTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 |
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 | all
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | ..\MicroflowFunctionApp\bin\Debug\net6.0\MicroflowApp.dll
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/MicroflowTest/MicroflowTest.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.3.32901.215
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroflowTest", "MicroflowTest.csproj", "{F56DD770-E3B5-4846-9301-46748A9EAC43}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroflowSDK", "..\MicroflowSDK\MicroflowSDK.csproj", "{D52A21A8-E941-4627-A14D-C6E722C7ABD3}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroflowModels", "..\MicroflowModels\MicroflowModels.csproj", "{9C1D7EC0-68A8-48C8-9F68-A96707D93E06}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | DEBUG_NO_SCALEGROUPS|Any CPU = DEBUG_NO_SCALEGROUPS|Any CPU
15 | Debug|Any CPU = Debug|Any CPU
16 | RELEASE_NO_SCALEGROUPS|Any CPU = RELEASE_NO_SCALEGROUPS|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {F56DD770-E3B5-4846-9301-46748A9EAC43}.DEBUG_NO_SCALEGROUPS|Any CPU.ActiveCfg = Debug|Any CPU
21 | {F56DD770-E3B5-4846-9301-46748A9EAC43}.DEBUG_NO_SCALEGROUPS|Any CPU.Build.0 = Debug|Any CPU
22 | {F56DD770-E3B5-4846-9301-46748A9EAC43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {F56DD770-E3B5-4846-9301-46748A9EAC43}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {F56DD770-E3B5-4846-9301-46748A9EAC43}.RELEASE_NO_SCALEGROUPS|Any CPU.ActiveCfg = Release|Any CPU
25 | {F56DD770-E3B5-4846-9301-46748A9EAC43}.RELEASE_NO_SCALEGROUPS|Any CPU.Build.0 = Release|Any CPU
26 | {F56DD770-E3B5-4846-9301-46748A9EAC43}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {F56DD770-E3B5-4846-9301-46748A9EAC43}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {D52A21A8-E941-4627-A14D-C6E722C7ABD3}.DEBUG_NO_SCALEGROUPS|Any CPU.ActiveCfg = Debug|Any CPU
29 | {D52A21A8-E941-4627-A14D-C6E722C7ABD3}.DEBUG_NO_SCALEGROUPS|Any CPU.Build.0 = Debug|Any CPU
30 | {D52A21A8-E941-4627-A14D-C6E722C7ABD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {D52A21A8-E941-4627-A14D-C6E722C7ABD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {D52A21A8-E941-4627-A14D-C6E722C7ABD3}.RELEASE_NO_SCALEGROUPS|Any CPU.ActiveCfg = Release|Any CPU
33 | {D52A21A8-E941-4627-A14D-C6E722C7ABD3}.RELEASE_NO_SCALEGROUPS|Any CPU.Build.0 = Release|Any CPU
34 | {D52A21A8-E941-4627-A14D-C6E722C7ABD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {D52A21A8-E941-4627-A14D-C6E722C7ABD3}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {9C1D7EC0-68A8-48C8-9F68-A96707D93E06}.DEBUG_NO_SCALEGROUPS|Any CPU.ActiveCfg = DEBUG_NO_SCALEGROUPS|Any CPU
37 | {9C1D7EC0-68A8-48C8-9F68-A96707D93E06}.DEBUG_NO_SCALEGROUPS|Any CPU.Build.0 = DEBUG_NO_SCALEGROUPS|Any CPU
38 | {9C1D7EC0-68A8-48C8-9F68-A96707D93E06}.Debug|Any CPU.ActiveCfg = DEBUG|Any CPU
39 | {9C1D7EC0-68A8-48C8-9F68-A96707D93E06}.Debug|Any CPU.Build.0 = DEBUG|Any CPU
40 | {9C1D7EC0-68A8-48C8-9F68-A96707D93E06}.RELEASE_NO_SCALEGROUPS|Any CPU.ActiveCfg = RELEASE_NO_SCALEGROUPS|Any CPU
41 | {9C1D7EC0-68A8-48C8-9F68-A96707D93E06}.RELEASE_NO_SCALEGROUPS|Any CPU.Build.0 = RELEASE_NO_SCALEGROUPS|Any CPU
42 | {9C1D7EC0-68A8-48C8-9F68-A96707D93E06}.Release|Any CPU.ActiveCfg = RELEASE|Any CPU
43 | {9C1D7EC0-68A8-48C8-9F68-A96707D93E06}.Release|Any CPU.Build.0 = RELEASE|Any CPU
44 | EndGlobalSection
45 | GlobalSection(SolutionProperties) = preSolution
46 | HideSolutionNode = FALSE
47 | EndGlobalSection
48 | GlobalSection(ExtensibilityGlobals) = postSolution
49 | SolutionGuid = {11422D0E-2C78-4241-933B-841495B865D6}
50 | EndGlobalSection
51 | EndGlobal
52 |
--------------------------------------------------------------------------------
/MicroflowTest/Test2_Retries.cs:
--------------------------------------------------------------------------------
1 | using MicroflowModels;
2 | using MicroflowSDK;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Net.Http;
8 | using System.Threading.Tasks;
9 |
10 | namespace MicroflowTest
11 | {
12 | [TestClass]
13 | public class Test2_Retries
14 | {
15 | [TestMethod]
16 | public async Task Reties()
17 | {
18 | List workflow = TestWorkflowHelper.CreateTestWorkflow_SimpleSteps();
19 |
20 | (MicroflowModels.Microflow workflow, string workflowName) microflow = TestWorkflowHelper.CreateMicroflow(workflow);
21 |
22 | int loop = 1;
23 | string globalKey = Guid.NewGuid().ToString();
24 |
25 | microflow.workflow.Step(1).EnableWebhook = true;
26 |
27 | microflow.workflow.Step(1).WebhookId = "mywebhook123-" + Guid.NewGuid().ToString();
28 |
29 | microflow.workflow.Step(1).StopOnWebhookTimeout = false;
30 |
31 | microflow.workflow.Step(1).WebhookTimeoutSeconds = 3;
32 | microflow.workflow.Step(1).RetryOptions = new RetryOptions() { BackoffCoefficient = 1, DelaySeconds = 1, MaxDelaySeconds = 1, MaxRetries = 2, TimeOutSeconds = 300 };
33 |
34 | // Upsert
35 | bool successUpsert = await WorkflowManager.UpsertWorkFlow(microflow.workflow, TestWorkflowHelper.BaseUrl);
36 |
37 | Assert.IsTrue(successUpsert);
38 |
39 | // start the upserted Microflow
40 | HttpResponseMessage startResult = await TestWorkflowHelper.StartMicroflow(microflow, loop, globalKey);
41 |
42 | string instanceId = await WorkflowManager.WaitForWorkflowCompleted(startResult);
43 |
44 | List log = await LogReader.GetOrchLog(microflow.workflowName);
45 |
46 | Assert.IsTrue(log.FindIndex(i=>i.OrchestrationId.Equals(instanceId))>=0);
47 |
48 | List steps = await LogReader.GetStepsLog(microflow.workflowName, instanceId);
49 |
50 | List s = steps.OrderBy(e => e.EndDate).ToList();
51 |
52 | Assert.IsTrue(s[0].StepNumber == 1);
53 |
54 | if(s[1].StepNumber==2)
55 | Assert.IsTrue(s[2].StepNumber==3);
56 | else
57 | {
58 | Assert.IsTrue(s[1].StepNumber == 3);
59 | Assert.IsTrue(s[2].StepNumber == 2);
60 | }
61 |
62 | Assert.IsTrue(s[3].StepNumber == 4);
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/MicroflowTest/Test3_Webhooks.cs:
--------------------------------------------------------------------------------
1 | using MicroflowModels;
2 | using MicroflowSDK;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Net.Http;
8 | using System.Threading.Tasks;
9 |
10 | namespace MicroflowTest
11 | {
12 | [TestClass]
13 | public class Test3_Webhooks
14 | {
15 | [TestMethod]
16 | public async Task BasicWebhook()
17 | {
18 | List workflow = TestWorkflowHelper.CreateTestWorkflow_SimpleSteps();
19 |
20 | (MicroflowModels.Microflow workflow, string workflowName) microflow = TestWorkflowHelper.CreateMicroflow(workflow);
21 |
22 | int loop = 1;
23 | string globalKey = Guid.NewGuid().ToString();
24 |
25 | microflow.workflow.Step(2).WebhookId = Guid.NewGuid().ToString();
26 | microflow.workflow.Step(2).EnableWebhook = true;
27 |
28 | // Upsert
29 | bool successUpsert = await WorkflowManager.UpsertWorkFlow(microflow.workflow, TestWorkflowHelper.BaseUrl);
30 |
31 | Assert.IsTrue(successUpsert);
32 |
33 | // start the upserted Microflow
34 | HttpResponseMessage startResult = await TestWorkflowHelper.StartMicroflow(microflow, loop, globalKey);
35 |
36 | // Emulator will reply on the webhook
37 | if (!TestWorkflowHelper.UseEmulator)
38 | {
39 | while (true)
40 | {
41 | await Task.Delay(2000);
42 |
43 | //HttpResponseMessage webhookcall = await TestWorkflowHelper.HttpClient.GetAsync(
44 | // $"{TestWorkflowHelper.BaseUrl}/getwebhooks/{microflow.workflowName}/{microflow.workflow.Step(2).WebhookId}/{microflow.workflow.Step(2).StepNumber}");
45 | HttpResponseMessage webhookcall = await TestWorkflowHelper.HttpClient.GetAsync($"{TestWorkflowHelper.BaseUrl}/webhooks/{microflow.workflow.Step(2).WebhookId}");
46 |
47 | // if the callout sent out a webhookid externally, and the events is not created yet, then a 202 will always return
48 | if (webhookcall.StatusCode == System.Net.HttpStatusCode.Accepted || webhookcall.StatusCode == System.Net.HttpStatusCode.NotFound)
49 | {
50 | continue;
51 | }
52 | else if (webhookcall.StatusCode == System.Net.HttpStatusCode.OK)
53 | {
54 | break;
55 | }
56 | }
57 | }
58 |
59 | string instanceId = await WorkflowManager.WaitForWorkflowCompleted(startResult);
60 |
61 | List log = await LogReader.GetOrchLog(microflow.workflowName);
62 |
63 | Assert.IsTrue(log.FindIndex(i => i.OrchestrationId.Equals(instanceId)) >= 0);
64 |
65 | List steps = await LogReader.GetStepsLog(microflow.workflowName, instanceId);
66 |
67 | List s = steps.OrderBy(e => e.EndDate).ToList();
68 |
69 | Assert.IsTrue(s[0].StepNumber == 1);
70 |
71 | if (s[1].StepNumber == 2)
72 | Assert.IsTrue(s[2].StepNumber == 3);
73 | else
74 | {
75 | Assert.IsTrue(s[1].StepNumber == 3);
76 | Assert.IsTrue(s[2].StepNumber == 2);
77 | }
78 |
79 | Assert.IsTrue(s[3].StepNumber == 4);
80 |
81 | Assert.IsTrue(s.Count == 4);
82 | }
83 |
84 | [TestMethod]
85 | public async Task WebhookWithCallbackActionAndSubStepsIfElse()
86 | {
87 | List workflow = TestWorkflowHelper.CreateTestWorkflow_SimpleSteps();
88 |
89 | (MicroflowModels.Microflow workflow, string workflowName) microflow = TestWorkflowHelper.CreateMicroflow(workflow);
90 |
91 | // Emulator will reply on the webhook, set /approve
92 | if (TestWorkflowHelper.UseEmulator)
93 | {
94 | TestWorkflowHelper.SetEmulatorWebhookAction(microflow.workflow);
95 | }
96 |
97 | int loop = 1;
98 | string globalKey = Guid.NewGuid().ToString();
99 |
100 | string webhookId = Guid.NewGuid().ToString();// "mywebhook";
101 |
102 | microflow.workflow.Step(1).WebhookId = webhookId;
103 | microflow.workflow.Step(1).EnableWebhook = true;
104 | microflow.workflow.Step(1).WebhookSubStepsMapping = new();
105 | microflow.workflow.Step(1).WebhookSubStepsMapping.Add(new()
106 | {
107 | WebhookAction = "decline",
108 | SubStepsToRunForAction = new List() { 2 }
109 | });
110 | microflow.workflow.Step(1).WebhookSubStepsMapping.Add(new()
111 | {
112 | WebhookAction = "approve",
113 | SubStepsToRunForAction = new List() { 3 }
114 | });
115 | microflow.workflow.Step(4).WaitForAllParents = false;
116 |
117 | // Upsert
118 | bool successUpsert = await WorkflowManager.UpsertWorkFlow(microflow.workflow, TestWorkflowHelper.BaseUrl);
119 |
120 | Assert.IsTrue(successUpsert);
121 |
122 | // start the upserted Microflow
123 | HttpResponseMessage startResult = await TestWorkflowHelper.StartMicroflow(microflow, loop, globalKey);
124 |
125 | // Emulator will reply on the webhook
126 | if (!TestWorkflowHelper.UseEmulator)
127 | {
128 | while (true)
129 | {
130 | await Task.Delay(2000);
131 |
132 | HttpResponseMessage webhookcall = await TestWorkflowHelper.HttpClient.GetAsync($"{TestWorkflowHelper.BaseUrl}/webhooks/{webhookId}/approve");
133 |
134 | // if the callout sent out a webhookid externally, and the events is not created yet, then a 202 will always return
135 | if (webhookcall.StatusCode == System.Net.HttpStatusCode.Accepted || webhookcall.StatusCode == System.Net.HttpStatusCode.NotFound)
136 | {
137 | continue;
138 | }
139 | else if (webhookcall.StatusCode == System.Net.HttpStatusCode.OK)
140 | {
141 | break;
142 | }
143 | }
144 | }
145 |
146 | string instanceId = await WorkflowManager.WaitForWorkflowCompleted(startResult);
147 |
148 | List log = await LogReader.GetOrchLog(microflow.workflowName);
149 |
150 | Assert.IsTrue(log.FindIndex(i => i.OrchestrationId.Equals(instanceId)) >= 0);
151 |
152 | List steps = await LogReader.GetStepsLog(microflow.workflowName, instanceId);
153 |
154 | List s = steps.OrderBy(e => e.EndDate).ToList();
155 |
156 | Assert.IsTrue(s[0].StepNumber == 1);
157 | Assert.IsTrue(s[1].StepNumber == 3);
158 | Assert.IsTrue(s[2].StepNumber == 4);
159 |
160 | Assert.IsTrue(s.Count == 3);
161 | }
162 | }
163 | }
--------------------------------------------------------------------------------
/MicroflowTest/Test4_ScaleGroups.cs:
--------------------------------------------------------------------------------
1 | using Microflow.MicroflowTableModels;
2 | using MicroflowModels;
3 | using MicroflowSDK;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Globalization;
8 | using System.Linq;
9 | using System.Net.Http;
10 | using System.Threading.Tasks;
11 |
12 | namespace MicroflowTest
13 | {
14 | [TestClass]
15 | public class Test4_ScaleGroups
16 | {
17 | string scaleGroupId = "mytestgroup";
18 |
19 | [TestMethod]
20 | public async Task MaxInstanceCountForScaleGroupTo10()
21 | {
22 | // create a workflow with parent step 1, two child steps 2 and 3, 2 with five children and 3 with five children,
23 | // and step 14 as the child of the ten children of 2 and 3
24 | List workflow = TestWorkflowHelper.CreateTestWorkflow_10StepsParallel();
25 |
26 | // create Microflow with the created workflow
27 | (MicroflowModels.Microflow workflow, string workflowName) microflow = TestWorkflowHelper.CreateMicroflow(workflow);
28 |
29 | // set loop to 1, how many time the workflow will execute
30 | int loop = 1;
31 |
32 | // set the global key if needed
33 | // the global key is needed to group related workflows and control the group
34 | string globalKey = Guid.NewGuid().ToString();
35 |
36 | // set the ten parallel steps 4 - 13 to the same scale group with the max instance count = 10, will overlap
37 | microflow.workflow.Steps.Where(s => s.StepNumber > 3 && s.StepNumber < 14).ToList().ForEach(x => x.ScaleGroupId = scaleGroupId);
38 |
39 | // upsert the workflow
40 | await UpsertWorkflow(microflow);
41 |
42 | // set the maximum limit of concurrent executions for the scale group id tp 10
43 | await TestWorkflowHelper.SetScaleGroupMax(10, scaleGroupId);
44 |
45 | // start the upserted Microflow
46 | HttpResponseMessage startResult = await TestWorkflowHelper.StartMicroflow(microflow, loop, globalKey);
47 |
48 | // wait for completion
49 | string instanceId = await WorkflowManager.WaitForWorkflowCompleted(startResult);
50 |
51 | // test that
52 | await TestNoScaleGroupWithMax10(microflow, instanceId);
53 | }
54 |
55 | [TestMethod]
56 | public async Task MaxInstanceCountForScaleGroupTo1()
57 | {
58 | // create a workflow with parent step 1, two child steps 2 and 3, 2 with five children and 3 with five children,
59 | // and step 14 as the child of the ten children of 2 and 3
60 | List workflow = TestWorkflowHelper.CreateTestWorkflow_10StepsParallel();
61 |
62 | // create Microflow with the created workflow
63 | (MicroflowModels.Microflow workflow, string workflowName) microflow = TestWorkflowHelper.CreateMicroflow(workflow);
64 |
65 | // set loop to 1, how many time the workflow will execute
66 | int loop = 1;
67 |
68 | // set the global key if needed
69 | // the global key is needed to group related workflows and control the group
70 | string globalKey = Guid.NewGuid().ToString();
71 |
72 | microflow.workflow.Steps.Where(s => s.StepNumber > 3 && s.StepNumber < 14).ToList().ForEach(x => x.ScaleGroupId = scaleGroupId);
73 |
74 | await UpsertWorkflow(microflow);
75 |
76 | // set the ten parallel steps 4 - 13 to the same scale group with the max instance count = 1, no overlapping
77 | await TestWorkflowHelper.SetScaleGroupMax(1, scaleGroupId);
78 |
79 | // start the upserted Microflow
80 | HttpResponseMessage startResult = await TestWorkflowHelper.StartMicroflow(microflow, loop, globalKey);
81 |
82 | string instanceId = await WorkflowManager.WaitForWorkflowCompleted(startResult);
83 |
84 | await TestNoScaleGroupWithMax1(microflow, instanceId);
85 | }
86 |
87 | private static async Task TestNoScaleGroupWithMax1((MicroflowModels.Microflow workflow, string workflowName) microflow, string instanceId)
88 | {
89 | //// CHECK RESULTS ////
90 |
91 | // get the orchestration log to check the results
92 | List log = await LogReader.GetOrchLog(microflow.workflowName);
93 |
94 | Assert.IsTrue(log.FindIndex(i => i.OrchestrationId.Equals(instanceId)) >= 0);
95 |
96 | // get the steps log to check the results
97 | List steps = await LogReader.GetStepsLog(microflow.workflowName, instanceId);
98 |
99 | List sortedSteps = TestBasicFlow(steps);
100 |
101 | IEnumerable parallelSteps = sortedSteps.Where(s => s.StepNumber > 3 && s.StepNumber < 14);
102 | int count = 1;
103 |
104 | foreach (LogStepEntity paraStep in parallelSteps)
105 | {
106 | // these are now not overlapping since the scalegroup max instance count is 1
107 | // count the other step`s with start dates before this step`s end date
108 | if (parallelSteps.Count(s => s.StartDate < paraStep.EndDate) != count)
109 | {
110 | Assert.Fail();
111 | break;
112 | }
113 |
114 | // now 1 more other step has a start date before the next step`s end date
115 | count++;
116 | }
117 | }
118 |
119 | private static async Task TestNoScaleGroupWithMax10((MicroflowModels.Microflow workflow, string workflowName) microflow, string instanceId)
120 | {
121 | //// CHECK RESULTS ////
122 |
123 | // get the orchestration log to check the results
124 | List log = await LogReader.GetOrchLog(microflow.workflowName);
125 |
126 | Assert.IsTrue(log.FindIndex(i => i.OrchestrationId.Equals(instanceId)) >= 0);
127 |
128 | // get the steps log to check the results
129 | List steps = await LogReader.GetStepsLog(microflow.workflowName, instanceId);
130 |
131 | List sortedSteps = TestBasicFlow(steps);
132 |
133 | IEnumerable parallelSteps = sortedSteps.Where(s => s.StepNumber > 3 && s.StepNumber < 14);
134 | bool foundOverlap = false;
135 |
136 | foreach (LogStepEntity paraStep in parallelSteps)
137 | {
138 | if (parallelSteps.Count(s => s.StartDate < paraStep.EndDate) > 0)
139 | {
140 | foundOverlap = true;
141 | break;
142 | }
143 | }
144 |
145 | Assert.IsTrue(foundOverlap);
146 | }
147 |
148 | private static List TestBasicFlow(List steps)
149 | {
150 | List sortedSteps = steps.OrderBy(e => e.EndDate).ToList();
151 |
152 | Assert.IsTrue(sortedSteps[0].StepNumber == 1);
153 | Assert.IsTrue(sortedSteps[1].StepNumber == 2 || sortedSteps[1].StepNumber == 3);
154 | Assert.IsTrue(sortedSteps.Count(n => n.StepNumber == 14) == 1);
155 | Assert.IsTrue(sortedSteps.Count == 14);
156 | Assert.IsTrue(sortedSteps[13].StepNumber == 14);
157 |
158 | return sortedSteps;
159 | }
160 |
161 | private static async Task UpsertWorkflow((MicroflowModels.Microflow workflow, string workflowName) microflow)
162 | {
163 | // upsert Microflow json
164 | //string json = JsonSerializer.Serialize(microflow.workflow);
165 | bool successUpsert = await WorkflowManager.UpsertWorkFlow(microflow.workflow, TestWorkflowHelper.BaseUrl);
166 |
167 | Assert.IsTrue(successUpsert);
168 | }
169 | }
170 | }
--------------------------------------------------------------------------------
/MicroflowTest/Test5_WaitForAllParents.cs:
--------------------------------------------------------------------------------
1 | using MicroflowModels;
2 | using MicroflowSDK;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Net.Http;
8 | using System.Threading.Tasks;
9 |
10 | namespace MicroflowTest
11 | {
12 | [TestClass]
13 | public class Test5_WaitForAllParents
14 | {
15 | [TestMethod]
16 | public async Task WaitForAllParents()
17 | {
18 | // create a workflow with parent step 1, two child steps 2 and 3, 2 with five children and 3 with five children,
19 | // and step 14 as the child of the ten children of 2 and 3
20 | List workflow = TestWorkflowHelper.CreateTestWorkflow_10StepsParallel();
21 |
22 | (MicroflowModels.Microflow workflow, string workflowName) microflow = TestWorkflowHelper.CreateMicroflow(workflow);
23 |
24 | int loop = 1;
25 | string globalKey = Guid.NewGuid().ToString();
26 |
27 | microflow.workflow.Step(14).WaitForAllParents = false;
28 |
29 | // Upsert
30 | bool successUpsert = await WorkflowManager.UpsertWorkFlow(microflow.workflow, TestWorkflowHelper.BaseUrl);
31 |
32 | Assert.IsTrue(successUpsert);
33 |
34 | // start the upserted Microflow
35 | HttpResponseMessage startResult = await TestWorkflowHelper.StartMicroflow(microflow, loop, globalKey);
36 |
37 | string instanceId = await WorkflowManager.WaitForWorkflowCompleted(startResult);
38 |
39 | List steps = await LogReader.GetStepsLog(microflow.workflowName, instanceId);
40 |
41 | List sorted = steps.OrderBy(e => e.EndDate).ToList();
42 |
43 | List log = await LogReader.GetOrchLog(microflow.workflowName);
44 |
45 | Assert.IsTrue(log.FindIndex(i => i.OrchestrationId.Equals(instanceId)) >= 0);
46 |
47 | Assert.IsTrue(sorted[0].StepNumber == 1);
48 |
49 | Assert.IsTrue(sorted[1].StepNumber == 2 || sorted[1].StepNumber == 3);
50 |
51 | Assert.IsTrue(sorted.Count() == 23);
52 |
53 | // with WaitForAllParents = true, step 14 will execute once when all parents are completed
54 | // with WaitForAllParents = false, step 14 will execute each time a parent completes
55 | Assert.IsTrue(sorted.Count(n => n.StepNumber == 14) == 10);
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/MicroflowTest/Test6_PassThroughParams.cs:
--------------------------------------------------------------------------------
1 | using MicroflowModels;
2 | using MicroflowSDK;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Net.Http;
8 | using System.Threading.Tasks;
9 |
10 | namespace MicroflowTest
11 | {
12 | [TestClass]
13 | public class Test6_PassThroughParams
14 | {
15 | [TestMethod]
16 | public async Task HttpGet_PassAllParams()
17 | {
18 | // create a simple workflow with parent step 1, subling children step 2 and 3, and child of 2 and 3 step 4
19 | // siblings steps 2 and 3 runs in parallel
20 | List stepsList = TestWorkflowHelper.CreateTestWorkflow_SimpleSteps();
21 | stepsList.RemoveRange(1, 3);
22 | stepsList[0].IsHttpGet = true;
23 | stepsList[0].SubSteps.Clear();
24 | stepsList[0].WebhookId = "mywebhook";
25 | //stepsList[3].WaitForAllParents = false;
26 |
27 | // create Microflow with the created workflow
28 | (MicroflowModels.Microflow workflow, string workflowName) microflow = TestWorkflowHelper.CreateMicroflow(stepsList, isHttpGet: true);
29 |
30 | // set loop to 1, how many time the workflow will execute
31 | int loop = 1;
32 |
33 | // set the global key if needed
34 | // the global key is needed to group related workflows and control the group
35 | string globalKey = Guid.NewGuid().ToString();
36 |
37 | // upsert Microflow json
38 | //string json = JsonSerializer.Serialize(microflow.workflow);
39 | bool successUpsert = await WorkflowManager.UpsertWorkFlow(microflow.workflow, TestWorkflowHelper.BaseUrl);
40 |
41 | Assert.IsTrue(successUpsert);
42 |
43 | // start the upserted Microflow
44 | HttpResponseMessage startResult = await TestWorkflowHelper.StartMicroflow(microflow, loop, globalKey);
45 |
46 | string instanceId = await WorkflowManager.WaitForWorkflowCompleted(startResult);
47 |
48 | //// CHECK RESULTS ////
49 | ///
50 | // get the steps log to check the results
51 | List steps = await LogReader.GetStepsLog(microflow.workflowName, instanceId);
52 |
53 | // get the orchestration log to check the results
54 | List log = await LogReader.GetOrchLog(microflow.workflowName);
55 |
56 | // check that the orchestraion id is logged
57 | Assert.IsTrue(log.FindIndex(i => i.OrchestrationId.Equals(instanceId)) >= 0);
58 |
59 | List sortedSteps = steps.OrderBy(e => e.EndDate).ToList();
60 |
61 | Assert.IsTrue(sortedSteps[0].StepNumber == 1);
62 |
63 | string blobHttpRosponse = await HttpBlobDataManager.GetHttpBlob(false, microflow.workflowName, sortedSteps[0].StepNumber, sortedSteps[0].RunId, sortedSteps[0].SubOrchestrationId);
64 |
65 | Assert.IsTrue(blobHttpRosponse.Equals("{\"success\":\"true\"}\n"));
66 |
67 | var arr = sortedSteps[0].PartitionKey.Split("__");
68 |
69 | var s = $"{TestWorkflowHelper.CalloutGETUrl}?WorkflowName={microflow.workflowName}&MainOrchestrationId={arr[1]}&SubOrchestrationId={sortedSteps[0].SubOrchestrationId}&WebhookId=&RunId={sortedSteps[0].RunId}&StepNumber=1&GlobalKey={sortedSteps[0].GlobalKey}&StepId={stepsList[0].StepId}";
70 |
71 | Assert.IsTrue(s.Equals(sortedSteps[0].CalloutUrl));
72 | }
73 |
74 | [TestMethod]
75 | public async Task HttpPost_PassAllParams()
76 | {
77 | // create a simple workflow with parent step 1, subling children step 2 and 3, and child of 2 and 3 step 4
78 | // siblings steps 2 and 3 runs in parallel
79 | List stepsList = TestWorkflowHelper.CreateTestWorkflow_SimpleSteps();
80 | stepsList.RemoveRange(1, 3);
81 | stepsList[0].IsHttpGet = false;
82 | stepsList[0].SubSteps.Clear();
83 | stepsList[0].WebhookId = "mywebhook";
84 | //stepsList[3].WaitForAllParents = false;
85 |
86 | // create Microflow with the created workflow
87 | (MicroflowModels.Microflow workflow, string workflowName) microflow = TestWorkflowHelper.CreateMicroflow(stepsList, isHttpGet: false);
88 |
89 | // set loop to 1, how many time the workflow will execute
90 | int loop = 1;
91 |
92 | // set the global key if needed
93 | // the global key is needed to group related workflows and control the group
94 | string globalKey = Guid.NewGuid().ToString();
95 |
96 | // upsert Microflow json
97 | //string json = JsonSerializer.Serialize(microflow.workflow);
98 | bool successUpsert = await WorkflowManager.UpsertWorkFlow(microflow.workflow, TestWorkflowHelper.BaseUrl);
99 |
100 | Assert.IsTrue(successUpsert);
101 |
102 | string input = "hello echo";
103 |
104 | // start the upserted Microflow
105 | HttpResponseMessage startResult = await TestWorkflowHelper.StartMicroflow(microflow, loop, globalKey, input);
106 |
107 | string instanceId = await WorkflowManager.WaitForWorkflowCompleted(startResult);
108 |
109 | //// CHECK RESULTS ////
110 | ///
111 | // get the steps log to check the results
112 | List steps = await LogReader.GetStepsLog(microflow.workflowName, instanceId);
113 |
114 | // get the orchestration log to check the results
115 | List log = await LogReader.GetOrchLog(microflow.workflowName);
116 |
117 | // check that the orchestraion id is logged
118 | Assert.IsTrue(log.FindIndex(i => i.OrchestrationId.Equals(instanceId)) >= 0);
119 |
120 | List sortedSteps = steps.OrderBy(e => e.EndDate).ToList();
121 |
122 | Assert.IsTrue(sortedSteps[0].StepNumber == 1);
123 |
124 | var arr = sortedSteps[0].PartitionKey.Split("__");
125 |
126 | var blobHttpRosponseTask = HttpBlobDataManager.GetHttpBlob(false, microflow.workflowName, sortedSteps[0].StepNumber, sortedSteps[0].RunId, sortedSteps[0].SubOrchestrationId);
127 | var blobHttpRequestTask = HttpBlobDataManager.GetHttpBlob(true, microflow.workflowName, sortedSteps[0].StepNumber, sortedSteps[0].RunId, sortedSteps[0].SubOrchestrationId);
128 |
129 | await Task.WhenAll(blobHttpRequestTask, blobHttpRosponseTask);
130 |
131 | var blobHttpRequest = blobHttpRequestTask.Result;
132 | var blobHttpRosponse = blobHttpRosponseTask.Result;
133 |
134 | Assert.IsTrue(blobHttpRequest.Equals("{\"WorkflowName\":\""
135 | + microflow.workflowName
136 | + "\",\"MainOrchestrationId\":\""
137 | + arr[1]
138 | + "\",\"SubOrchestrationId\":\""
139 | + sortedSteps[0].SubOrchestrationId
140 | + "\",\"Webhook\":null,"
141 | + "\"RunId\":\""
142 | + sortedSteps[0].RunId
143 | + "\",\"StepNumber\":1,\"StepId\":\"myStep 1\",\"GlobalKey\":\""
144 | + sortedSteps[0].GlobalKey
145 | + "\",\"PostData\":\""
146 | + input
147 | + "\"}"));
148 |
149 | Assert.IsTrue(blobHttpRosponse.Equals("{\"success\":\"true\"}\n"));
150 |
151 | // Microflow will not replace the querystring with meta data if it is a post
152 | //Assert.IsTrue(sortedSteps[0].CalloutUrl.Equals("https://reqbin.com/echo/post/json"));
153 | }
154 | }
155 | }
--------------------------------------------------------------------------------
/MicroflowTest/Test7_RunFromSteps.cs:
--------------------------------------------------------------------------------
1 | using Microflow.MicroflowTableModels;
2 | using MicroflowModels;
3 | using MicroflowSDK;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using Newtonsoft.Json;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Net.Http;
10 | using System.Net.Http.Json;
11 | using System.Threading.Tasks;
12 |
13 | namespace MicroflowTest
14 | {
15 | [TestClass]
16 | public class Test7_RunFromSteps
17 | {
18 | [TestMethod]
19 | public async Task RunFromSteps()
20 | {
21 | string workflowName = await RunBasicWorkflow();
22 |
23 | HttpResponseMessage runcall = await TestWorkflowHelper.HttpClient.PostAsJsonAsync>($"{TestWorkflowHelper.BaseUrl}/RunFromSteps/{workflowName}", new() { 2, 3 });
24 |
25 | //// CHECK RESULTS //// stepnumber 1 is now not in the log
26 | string instanceId = await WorkflowManager.WaitForWorkflowCompleted(runcall);
27 |
28 | // get the orchestration log to check the results
29 | List log = await LogReader.GetOrchLog(workflowName);
30 |
31 | // check that the orchestraion id is logged
32 | Assert.IsTrue(log.Single(i => i.OrchestrationId.Equals(instanceId) && i.RowKey.Contains("RunFromSteps")) != null);
33 |
34 | // get the steps log to check the results
35 | List steps = await LogReader.GetStepsLog(workflowName, instanceId);
36 |
37 | List sortedSteps = steps.OrderBy(e => e.EndDate).ToList();
38 |
39 | if (sortedSteps[0].StepNumber == 2)
40 | Assert.IsTrue(sortedSteps[1].StepNumber == 3);
41 | else
42 | {
43 | Assert.IsTrue(sortedSteps[0].StepNumber == 3);
44 | Assert.IsTrue(sortedSteps[1].StepNumber == 2);
45 | }
46 |
47 | Assert.IsTrue(sortedSteps[2].StepNumber == 4);
48 |
49 | Assert.IsTrue(sortedSteps.Count == 3);
50 | }
51 |
52 | private static async Task RunBasicWorkflow()
53 | {
54 | // create a simple workflow with parent step 1, subling children step 2 and 3, and child of 2 and 3 step 4
55 | // siblings steps 2 and 3 runs in parallel
56 | List stepsList = TestWorkflowHelper.CreateTestWorkflow_SimpleSteps();
57 |
58 | // create Microflow with the created workflow
59 | (MicroflowModels.Microflow workflow, string workflowName) microflow = TestWorkflowHelper.CreateMicroflow(stepsList, passThroughParams: false);
60 |
61 | // set loop to 1, how many time the workflow will execute
62 | int loop = 1;
63 |
64 | // set the global key if needed
65 | // the global key is needed to group related workflows and control the group
66 | string globalKey = Guid.NewGuid().ToString();
67 |
68 | // upsert Microflow json
69 | //string json = JsonSerializer.Serialize(microflow.workflow);
70 | bool successUpsert = await WorkflowManager.UpsertWorkFlow(microflow.workflow, TestWorkflowHelper.BaseUrl);
71 |
72 | Assert.IsTrue(successUpsert);
73 |
74 | // start the upserted Microflow
75 | HttpResponseMessage startResult = await TestWorkflowHelper.StartMicroflow(microflow, loop, globalKey);
76 |
77 | string instanceId = await WorkflowManager.WaitForWorkflowCompleted(startResult);
78 |
79 | //// CHECK RESULTS ////
80 |
81 | // get the orchestration log to check the results
82 | List log = await LogReader.GetOrchLog(microflow.workflowName);
83 |
84 | // check that the orchestraion id is logged
85 | Assert.IsTrue(log.FindIndex(i => i.OrchestrationId.Equals(instanceId)) >= 0);
86 |
87 | // get the steps log to check the results
88 | List steps = await LogReader.GetStepsLog(microflow.workflowName, instanceId);
89 |
90 | List sortedSteps = steps.OrderBy(e => e.EndDate).ToList();
91 |
92 | Assert.IsTrue(sortedSteps[0].StepNumber == 1);
93 |
94 | if (sortedSteps[1].StepNumber == 2)
95 | Assert.IsTrue(sortedSteps[2].StepNumber == 3);
96 | else
97 | {
98 | Assert.IsTrue(sortedSteps[1].StepNumber == 3);
99 | Assert.IsTrue(sortedSteps[2].StepNumber == 2);
100 | }
101 |
102 | Assert.IsTrue(sortedSteps[3].StepNumber == 4);
103 |
104 | Assert.IsTrue(sortedSteps.Count == 4);
105 |
106 | return microflow.workflowName;
107 | }
108 | }
109 | }
--------------------------------------------------------------------------------
/MicroflowTest/Test8_2TopStepsPostDataStart.cs:
--------------------------------------------------------------------------------
1 | using MicroflowModels;
2 | using MicroflowSDK;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Newtonsoft.Json;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Net.Http;
9 | using System.Threading.Tasks;
10 |
11 | namespace MicroflowTest
12 | {
13 | public class PostTestObject
14 | {
15 | public string Name { get; set; } = "Andre";
16 | public int Id { get; set; } = 1;
17 | }
18 |
19 | [TestClass]
20 | public class Test8_2TopStepsPostDataStart
21 | {
22 | [TestMethod]
23 | public async Task TwoTopStepsPostDataStart()
24 | {
25 | // create a simple workflow top steps 1 and 2, with a child step 3, and step 3 with child step 4
26 | List stepsList = TestWorkflowHelper.CreateTestWorkflow_2TopSteps();
27 | stepsList.ForEach(x=>x.IsHttpGet = false);
28 |
29 | // create Microflow with the created workflow
30 | (MicroflowModels.Microflow workflow, string workflowName) microflow = TestWorkflowHelper.CreateMicroflow(stepsList, isHttpGet: false);
31 |
32 | // set loop to 1, how many time the workflow will execute
33 | int loop = 1;
34 |
35 | // set the global key if needed
36 | // the global key is needed to group related workflows and control the group
37 | string globalKey = Guid.NewGuid().ToString();
38 |
39 | // upsert Microflow json
40 | //string json = JsonSerializer.Serialize(microflow.workflow);
41 | bool successUpsert = await WorkflowManager.UpsertWorkFlow(microflow.workflow, TestWorkflowHelper.BaseUrl);
42 |
43 | Assert.IsTrue(successUpsert);
44 |
45 | string input = JsonConvert.SerializeObject(new PostTestObject());
46 |
47 | // start the upserted Microflow
48 | HttpResponseMessage startResult = await TestWorkflowHelper.StartMicroflow(microflow, loop, globalKey, input);
49 |
50 | string instanceId = await WorkflowManager.WaitForWorkflowCompleted(startResult);
51 |
52 | //// CHECK RESULTS ////
53 | ///
54 | // get the steps log to check the results
55 | List steps = await LogReader.GetStepsLog(microflow.workflowName, instanceId);
56 |
57 | // get the orchestration log to check the results
58 | List log = await LogReader.GetOrchLog(microflow.workflowName);
59 |
60 | // check that the orchestraion id is logged
61 | Assert.IsTrue(log.FindIndex(i => i.OrchestrationId.Equals(instanceId)) >= 0);
62 |
63 | List sortedSteps = steps.OrderBy(e => e.EndDate).ToList();
64 |
65 | bool step0is1 = false;
66 |
67 | if (sortedSteps[0].StepNumber == 1)
68 | {
69 | Assert.IsTrue(sortedSteps[1].StepNumber == 2);
70 | step0is1 = true;
71 | }
72 | else
73 | {
74 | Assert.IsTrue(sortedSteps[0].StepNumber == 2);
75 | Assert.IsTrue(sortedSteps[1].StepNumber == 1);
76 | }
77 |
78 | Assert.IsTrue(sortedSteps[2].StepNumber == 3);
79 | Assert.IsTrue(sortedSteps[3].StepNumber == 4);
80 |
81 | var arr = sortedSteps[0].PartitionKey.Split("__");
82 |
83 | var blobHttpRequest1Task = HttpBlobDataManager.GetHttpBlob(true, microflow.workflowName, sortedSteps[0].StepNumber, sortedSteps[0].RunId, sortedSteps[0].SubOrchestrationId);
84 | var blobHttpRequest2Task = HttpBlobDataManager.GetHttpBlob(true, microflow.workflowName, sortedSteps[1].StepNumber, sortedSteps[1].RunId, sortedSteps[1].SubOrchestrationId);
85 |
86 | await Task.WhenAll(blobHttpRequest1Task, blobHttpRequest2Task);
87 |
88 | var blobHttpRosponse1 = blobHttpRequest1Task.Result;
89 | var blobHttpRosponse2 = blobHttpRequest2Task.Result;
90 |
91 | if (step0is1)
92 | {
93 | string s1 = "{\"WorkflowName\":\"Unit_test_workflow@1.0\",\"MainOrchestrationId\":\"" +
94 | arr[1] + "\"," +
95 | "\"SubOrchestrationId\":\"" +
96 | sortedSteps[0].SubOrchestrationId + "\"," +
97 | "\"Webhook\":null," +
98 | "\"RunId\":\"" +
99 | sortedSteps[0].RunId + "\"," +
100 | "\"StepNumber\":1,\"StepId\":\"myStep 1\",\"GlobalKey\":\"" +
101 | sortedSteps[0].GlobalKey + "\"," +
102 | "\"PostData\":\"{\\u0022Name\\u0022:\\u0022Andre\\u0022,\\u0022Id\\u0022:1}\"}";
103 |
104 | string s2 = "{\"WorkflowName\":\"Unit_test_workflow@1.0\",\"MainOrchestrationId\":\"" +
105 | arr[1] + "\"," +
106 | "\"SubOrchestrationId\":\"" +
107 | sortedSteps[1].SubOrchestrationId + "\"," +
108 | "\"Webhook\":null," +
109 | "\"RunId\":\"" +
110 | sortedSteps[1].RunId + "\"," +
111 | "\"StepNumber\":2,\"StepId\":\"myStep 2\",\"GlobalKey\":\"" +
112 | sortedSteps[1].GlobalKey + "\"," +
113 | "\"PostData\":\"{\\u0022Name\\u0022:\\u0022Andre\\u0022,\\u0022Id\\u0022:1}\"}";
114 |
115 | Assert.IsTrue(blobHttpRosponse1.Equals(s1));
116 | Assert.IsTrue(blobHttpRosponse2.Equals(s2));
117 | }
118 | else
119 | {
120 | string s1 = "{\"WorkflowName\":\"Unit_test_workflow@1.0\",\"MainOrchestrationId\":\"" +
121 | arr[1] + "\"," +
122 | "\"SubOrchestrationId\":\"" +
123 | sortedSteps[1].SubOrchestrationId + "\"," +
124 | "\"Webhook\":null," +
125 | "\"RunId\":\"" +
126 | sortedSteps[1].RunId + "\"," +
127 | "\"StepNumber\":1,\"StepId\":\"myStep 1\",\"GlobalKey\":\"" +
128 | sortedSteps[1].GlobalKey + "\"," +
129 | "\"PostData\":\"{\\u0022Name\\u0022:\\u0022Andre\\u0022,\\u0022Id\\u0022:1}\"}";
130 |
131 | string s2 = "{\"WorkflowName\":\"Unit_test_workflow@1.0\",\"MainOrchestrationId\":\"" +
132 | arr[1] + "\"," +
133 | "\"SubOrchestrationId\":\"" +
134 | sortedSteps[0].SubOrchestrationId + "\"," +
135 | "\"Webhook\":null," +
136 | "\"RunId\":\"" +
137 | sortedSteps[0].RunId + "\"," +
138 | "\"StepNumber\":2,\"StepId\":\"myStep 2\",\"GlobalKey\":\"" +
139 | sortedSteps[0].GlobalKey + "\"," +
140 | "\"PostData\":\"{\\u0022Name\\u0022:\\u0022Andre\\u0022,\\u0022Id\\u0022:1}\"}";
141 |
142 | Assert.IsTrue(blobHttpRosponse1.Equals(s2));
143 | Assert.IsTrue(blobHttpRosponse2.Equals(s1));
144 | }
145 | }
146 | }
147 | }
--------------------------------------------------------------------------------
/MicroflowTest/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "MicroflowStorage": "UseDevelopmentStorage=true",
3 | "BaseUrl": "http://localhost:7071/microflow/v1",
4 | "CallOut_GET_Url": "https://reqbin.com/echo/get/json",
5 | "CallOut_POST_Url": "https://reqbin.com/echo/post/json",
6 | "UseEmulator": "false",
7 | "EmulatorCallOut_GET_Url": "http://localhost:7072/api/SleepTestOrchestrator_HttpStart",
8 | "EmulatorCallOut_POST_Url": "http://localhost:7072/api/SleepTestOrchestrator_HttpStart"
9 | }
--------------------------------------------------------------------------------
/MicroserviceEmulator/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # Azure Functions localsettings file
5 | local.settings.json
6 |
7 | # User-specific files
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | bld/
24 | [Bb]in/
25 | [Oo]bj/
26 | [Ll]og/
27 |
28 | # Visual Studio 2015 cache/options directory
29 | .vs/
30 | # Uncomment if you have tasks that create the project's static files in wwwroot
31 | #wwwroot/
32 |
33 | # MSTest test Results
34 | [Tt]est[Rr]esult*/
35 | [Bb]uild[Ll]og.*
36 |
37 | # NUNIT
38 | *.VisualState.xml
39 | TestResult.xml
40 |
41 | # Build Results of an ATL Project
42 | [Dd]ebugPS/
43 | [Rr]eleasePS/
44 | dlldata.c
45 |
46 | # DNX
47 | project.lock.json
48 | project.fragment.lock.json
49 | artifacts/
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # NCrunch
117 | _NCrunch_*
118 | .*crunch*.local.xml
119 | nCrunchTemp_*
120 |
121 | # MightyMoose
122 | *.mm.*
123 | AutoTest.Net/
124 |
125 | # Web workbench (sass)
126 | .sass-cache/
127 |
128 | # Installshield output folder
129 | [Ee]xpress/
130 |
131 | # DocProject is a documentation generator add-in
132 | DocProject/buildhelp/
133 | DocProject/Help/*.HxT
134 | DocProject/Help/*.HxC
135 | DocProject/Help/*.hhc
136 | DocProject/Help/*.hhk
137 | DocProject/Help/*.hhp
138 | DocProject/Help/Html2
139 | DocProject/Help/html
140 |
141 | # Click-Once directory
142 | publish/
143 |
144 | # Publish Web Output
145 | *.[Pp]ublish.xml
146 | *.azurePubxml
147 | # TODO: Comment the next line if you want to checkin your web deploy settings
148 | # but database connection strings (with potential passwords) will be unencrypted
149 | #*.pubxml
150 | *.publishproj
151 |
152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
153 | # checkin your Azure Web App publish settings, but sensitive information contained
154 | # in these scripts will be unencrypted
155 | PublishScripts/
156 |
157 | # NuGet Packages
158 | *.nupkg
159 | # The packages folder can be ignored because of Package Restore
160 | **/packages/*
161 | # except build/, which is used as an MSBuild target.
162 | !**/packages/build/
163 | # Uncomment if necessary however generally it will be regenerated when needed
164 | #!**/packages/repositories.config
165 | # NuGet v3's project.json files produces more ignoreable files
166 | *.nuget.props
167 | *.nuget.targets
168 |
169 | # Microsoft Azure Build Output
170 | csx/
171 | *.build.csdef
172 |
173 | # Microsoft Azure Emulator
174 | ecf/
175 | rcf/
176 |
177 | # Windows Store app package directories and files
178 | AppPackages/
179 | BundleArtifacts/
180 | Package.StoreAssociation.xml
181 | _pkginfo.txt
182 |
183 | # Visual Studio cache files
184 | # files ending in .cache can be ignored
185 | *.[Cc]ache
186 | # but keep track of directories ending in .cache
187 | !*.[Cc]ache/
188 |
189 | # Others
190 | ClientBin/
191 | ~$*
192 | *~
193 | *.dbmdl
194 | *.dbproj.schemaview
195 | *.jfm
196 | *.pfx
197 | *.publishsettings
198 | node_modules/
199 | orleans.codegen.cs
200 |
201 | # Since there are multiple workflows, uncomment next line to ignore bower_components
202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
203 | #bower_components/
204 |
205 | # RIA/Silverlight projects
206 | Generated_Code/
207 |
208 | # Backup & report files from converting an old project file
209 | # to a newer Visual Studio version. Backup files are not needed,
210 | # because we have git ;-)
211 | _UpgradeReport_Files/
212 | Backup*/
213 | UpgradeLog*.XML
214 | UpgradeLog*.htm
215 |
216 | # SQL Server files
217 | *.mdf
218 | *.ldf
219 |
220 | # Business Intelligence projects
221 | *.rdl.data
222 | *.bim.layout
223 | *.bim_*.settings
224 |
225 | # Microsoft Fakes
226 | FakesAssemblies/
227 |
228 | # GhostDoc plugin setting file
229 | *.GhostDoc.xml
230 |
231 | # Node.js Tools for Visual Studio
232 | .ntvs_analysis.dat
233 |
234 | # Visual Studio 6 build log
235 | *.plg
236 |
237 | # Visual Studio 6 workspace options file
238 | *.opt
239 |
240 | # Visual Studio LightSwitch build output
241 | **/*.HTMLClient/GeneratedArtifacts
242 | **/*.DesktopClient/GeneratedArtifacts
243 | **/*.DesktopClient/ModelManifest.xml
244 | **/*.Server/GeneratedArtifacts
245 | **/*.Server/ModelManifest.xml
246 | _Pvt_Extensions
247 |
248 | # Paket dependency manager
249 | .paket/paket.exe
250 | paket-files/
251 |
252 | # FAKE - F# Make
253 | .fake/
254 |
255 | # JetBrains Rider
256 | .idea/
257 | *.sln.iml
258 |
259 | # CodeRush
260 | .cr/
261 |
262 | # Python Tools for Visual Studio (PTVS)
263 | __pycache__/
264 | *.pyc
--------------------------------------------------------------------------------
/MicroserviceEmulator/Function1.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Net.Http;
3 | using System.Threading.Tasks;
4 | using Microsoft.Azure.WebJobs;
5 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
6 | using Microsoft.Azure.WebJobs.Extensions.Http;
7 | using Microsoft.Extensions.Logging;
8 |
9 | namespace MicroserviceEmulator
10 | {
11 | public static class Function1
12 | {
13 | [FunctionName("Function1")]
14 | public static async Task> RunOrchestrator(
15 | [OrchestrationTrigger] IDurableOrchestrationContext context)
16 | {
17 | List outputs = new();
18 |
19 | // Replace "hello" with the name of your Durable Activity Function.
20 | outputs.Add(await context.CallActivityAsync("Function1_Hello", "Tokyo"));
21 | outputs.Add(await context.CallActivityAsync("Function1_Hello", "Seattle"));
22 | outputs.Add(await context.CallActivityAsync("Function1_Hello", "London"));
23 |
24 | // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
25 | return outputs;
26 | }
27 |
28 | [FunctionName("Function1_Hello")]
29 | public static string SayHello([ActivityTrigger] string name, ILogger log)
30 | {
31 | log.LogInformation($"Saying hello to {name}.");
32 | return $"Hello {name}!";
33 | }
34 |
35 | [FunctionName("Function1_HttpStart")]
36 | public static async Task HttpStart(
37 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestMessage req,
38 | [DurableClient] IDurableOrchestrationClient starter,
39 | ILogger log)
40 | {
41 | // Function input comes from the request content.
42 | string instanceId = await starter.StartNewAsync("Function1", null);
43 |
44 | log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
45 |
46 | return starter.CreateCheckStatusResponse(req, instanceId);
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/MicroserviceEmulator/HttpClient.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 |
3 | namespace Microflow.API
4 | {
5 | public class EmulatorShared
6 | {
7 | // NB! To prevent port exaustion, use 1 static HttpClient for as much as possible
8 | // This instance of the HttpClient is also used in the ResponseProxyInlineDemoFunction
9 | // This client will be removed and is only included for the SleepTestOrchestrator
10 | public static readonly HttpClient HttpClient = new();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/MicroserviceEmulator/MicroflowExternalAPI.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 | using System.Text.Json;
3 | using System.Threading.Tasks;
4 | using MicroflowModels;
5 | using MicroflowModels.Helpers;
6 | using Microsoft.Azure.WebJobs;
7 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
8 | using Microsoft.Azure.WebJobs.Extensions.Http;
9 | using static MicroflowModels.Constants;
10 |
11 | namespace Microflow.API.External
12 | {
13 | public static class MicroflowExternalApi
14 | {
15 |
16 | ///
17 | /// Called from Microflow.ExecuteStep to get the current step table config
18 | ///
19 | [FunctionName(CallNames.GetStepInternal)]
20 | public static async Task GetStep([ActivityTrigger] MicroflowRun workflowRun) => await workflowRun.GetStep();
21 |
22 | ///
23 | /// use this to test some things like causing an exception
24 | ///
25 | [FunctionName("testpost")]
26 | public static async Task TestPost(
27 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "testpost")] HttpRequestMessage req)
28 | {
29 | await Task.Delay(1000);
30 |
31 | //if (req.Method == HttpMethod.Post)
32 | //{
33 | string r = await req.Content.ReadAsStringAsync();
34 |
35 | MicroflowPostData result = JsonSerializer.Deserialize(r);
36 |
37 | if (result.StepNumber == 6 || result.StepNumber == 8 || result.StepNumber == 10)// && result.workflowName.Equals("xxx"))
38 | {
39 | //HttpResponseMessage result2 = await MicroflowHttpClient.HttpClient.GetAsync($"{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/start/");
40 | //var kgkg = 0;
41 | await Task.Delay(5000);
42 | }
43 | //}
44 | //else
45 | //{
46 | // NameValueCollection data = req.RequestUri.ParseQueryString();
47 | // MicroflowPostData postData = new MicroflowPostData()
48 | // {
49 | // Webhook = data["Webhook"],
50 | // MainOrchestrationId = data["MainOrchestrationId"],
51 | // workflowName = data["workflowName"],
52 | // RunId = data["RunId"],
53 | // StepNumber = Convert.ToInt32(data["StepNumber"]),
54 | // StepId = data["StepId"],
55 | // SubOrchestrationId = data["SubOrchestrationId"],
56 | // GlobalKey = data["GlobalKey"]
57 | // };
58 | // await Task.Delay(10000);
59 |
60 | //}
61 |
62 |
63 | HttpResponseMessage resp = new(System.Net.HttpStatusCode.OK);
64 | // resp.Headers.Location = new Uri("http://localhost:7071/api/testpost");
65 | //resp.Content = new StringContent("wappa");
66 | return resp;
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/MicroserviceEmulator/MicroserviceEmulator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | v4
5 | Debug;Release;DEBUG_NOUPSERT_NOFLOWCONTROL_NOSCALEGROUPS
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | PreserveNewest
17 |
18 |
19 | PreserveNewest
20 | Never
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/MicroserviceEmulator/MicroserviceEmulator/MicroserviceEmulator.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.3.32929.385
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroserviceEmulator", "..\MicroserviceEmulator.csproj", "{277EF938-ECB5-4208-AD74-0BD8530C6D1F}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | DEBUG_NOUPSERT_NOFLOWCONTROL_NOSCALEGROUPS|Any CPU = DEBUG_NOUPSERT_NOFLOWCONTROL_NOSCALEGROUPS|Any CPU
11 | Debug|Any CPU = Debug|Any CPU
12 | Release|Any CPU = Release|Any CPU
13 | EndGlobalSection
14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
15 | {277EF938-ECB5-4208-AD74-0BD8530C6D1F}.DEBUG_NOUPSERT_NOFLOWCONTROL_NOSCALEGROUPS|Any CPU.ActiveCfg = DEBUG_NOUPSERT_NOFLOWCONTROL_NOSCALEGROUPS|Any CPU
16 | {277EF938-ECB5-4208-AD74-0BD8530C6D1F}.DEBUG_NOUPSERT_NOFLOWCONTROL_NOSCALEGROUPS|Any CPU.Build.0 = DEBUG_NOUPSERT_NOFLOWCONTROL_NOSCALEGROUPS|Any CPU
17 | {277EF938-ECB5-4208-AD74-0BD8530C6D1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18 | {277EF938-ECB5-4208-AD74-0BD8530C6D1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
19 | {277EF938-ECB5-4208-AD74-0BD8530C6D1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
20 | {277EF938-ECB5-4208-AD74-0BD8530C6D1F}.Release|Any CPU.Build.0 = Release|Any CPU
21 | EndGlobalSection
22 | GlobalSection(SolutionProperties) = preSolution
23 | HideSolutionNode = FALSE
24 | EndGlobalSection
25 | GlobalSection(ExtensibilityGlobals) = postSolution
26 | SolutionGuid = {F710C39D-3540-4702-8042-5E45420F7B71}
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/MicroserviceEmulator/OrchestrationStateSimulations.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Specialized;
4 | using System.IO;
5 | using System.Net.Http;
6 | using System.Text.Json;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using MicroflowModels;
10 | using Microsoft.AspNetCore.Http;
11 | using Microsoft.Azure.WebJobs;
12 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
13 | using Microsoft.Azure.WebJobs.Extensions.Http;
14 | using Microsoft.Extensions.Logging;
15 |
16 | namespace Microflow.API.Internal
17 | {
18 | public static class OrchestrationStateSimulations
19 | {
20 | [FunctionName("RunningOrchestration")]
21 | public static async Task> RunningOrchestrator(
22 | [OrchestrationTrigger] IDurableOrchestrationContext context)
23 | {
24 | List outputs = new();
25 |
26 | outputs.Add(await context.CallActivityAsync("Arb_Activity", "Tokyo"));
27 | outputs.Add(await context.CallActivityAsync("Arb_Activity", "Seattle"));
28 | outputs.Add(await context.CallActivityAsync("Arb_Activity", "London"));
29 |
30 | //int o = 0;
31 | //int t = 5 / o;
32 |
33 | return outputs;
34 | }
35 |
36 | [FunctionName("Arb_Activity")]
37 | public static async Task Arb_Activity([ActivityTrigger] string name, ILogger log)
38 | {
39 | await Task.Delay(30000);
40 |
41 | log.LogInformation($"Saying hello to {name}.");
42 | return $"Hello {name}!";
43 | }
44 |
45 | [FunctionName("EmulateRunningOrchestration")]
46 | public static async Task EmulateRunningOrchestration(
47 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "EmulateRunningOrchestration")] HttpRequestMessage req,
48 | [DurableClient] IDurableOrchestrationClient starter,
49 | ILogger log)
50 | {
51 | string instanceId = await starter.StartNewAsync("RunningOrchestration", instanceId: "timer_qwerty");
52 |
53 | log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
54 |
55 | return starter.CreateCheckStatusResponse(req, instanceId);
56 | }
57 |
58 | [FunctionName("EmulateCompletedOrchestration")]
59 | public static async Task EmulateCompletedOrchestration(
60 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "EmulateCompletedOrchestration")] HttpRequestMessage req,
61 | [DurableClient] IDurableOrchestrationClient starter,
62 | ILogger log)
63 | {
64 | string instanceId = await starter.StartNewAsync("RunningOrchestration", null);
65 |
66 | log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
67 |
68 | return await starter.WaitForCompletionOrCreateCheckStatusResponseAsync(req, instanceId, timeout:TimeSpan.FromMinutes(100));
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/MicroserviceEmulator/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "MicroserviceEmulator": {
4 | "commandName": "Project",
5 | "commandLineArgs": "host start --pause-on-error --port 7072"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/MicroserviceEmulator/Properties/serviceDependencies.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "appInsights1": {
4 | "type": "appInsights"
5 | },
6 | "storage1": {
7 | "type": "storage",
8 | "connectionId": "AzureWebJobsStorage"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/MicroserviceEmulator/Properties/serviceDependencies.local.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "appInsights1": {
4 | "type": "appInsights.sdk"
5 | },
6 | "storage1": {
7 | "type": "storage.emulator",
8 | "connectionId": "AzureWebJobsStorage"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/MicroserviceEmulator/ResponseProxies/Callbacks/ResponseProxyCallbackDemoFunction.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Threading.Tasks;
3 | using Microsoft.Azure.WebJobs;
4 | using Microsoft.Azure.WebJobs.Extensions.Http;
5 | using System.Net.Http;
6 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
7 |
8 | namespace Microflow.ResponseProxies
9 | {
10 | public static class ResponseProxyCallbackDemoFunction
11 | {
12 | ///
13 | /// These client functions can be refactored into separate function apps,
14 | /// this being custom code that might change more frequently than the workflow engine core,
15 | /// and will then also scale on its own.
16 | ///
17 | [FunctionName("webhook")]
18 | public static async Task RaiseEvent(
19 | [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "webhook/{action}/{orchestratorId}/{stepId:int?}")] HttpRequestMessage req,
20 | [DurableClient] IDurableOrchestrationClient client, int stepId, string action, string orchestratorId)
21 | {
22 | HttpResponseMessage resp = new(HttpStatusCode.OK);
23 |
24 | await client.RaiseEventAsync(orchestratorId, action, resp);
25 |
26 | return resp;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/MicroserviceEmulator/ResponseProxies/Inline/ResponseProxyInlineDemoFunction.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.Azure.WebJobs;
3 | using System.Net.Http;
4 | using Microflow.API;
5 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
6 | using System.Threading;
7 | using MicroflowModels;
8 |
9 | namespace Microflow
10 | {
11 | public static class ResponseProxyInlineDemoFunction
12 | {
13 | ///
14 | /// This simulates an activity executing, replace with real call like an API call
15 | ///
16 | [FunctionName("httpcall")]
17 | public static async Task HttpCall([ActivityTrigger] HttpCall httpCall)
18 | {
19 | using (CancellationTokenSource cts = new(httpCall.WebhookTimeoutSeconds * 1000))
20 | {
21 | try
22 | {
23 | HttpResponseMessage result = await EmulatorShared.HttpClient.PostAsJsonAsync(httpCall.CalloutUrl, (ProcessId: httpCall.PartitionKey, StepId: httpCall.RowKey), cts.Token);
24 |
25 | if (result.IsSuccessStatusCode)
26 | {
27 | return new HttpResponseMessage(System.Net.HttpStatusCode.OK);
28 | }
29 |
30 | return new HttpResponseMessage(result.StatusCode);
31 | }
32 | catch (TaskCanceledException)
33 | {
34 | return new HttpResponseMessage(System.Net.HttpStatusCode.RequestTimeout);
35 | }
36 | finally
37 | {
38 | cts.Dispose();
39 | }
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/MicroserviceEmulator/SleepTestOrchestrator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Specialized;
3 | using System.IO;
4 | using System.Net.Http;
5 | using System.Text.Json;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using MicroflowModels;
9 | using Microsoft.AspNetCore.Http;
10 | using Microsoft.Azure.WebJobs;
11 | using Microsoft.Azure.WebJobs.Extensions.DurableTask;
12 | using Microsoft.Azure.WebJobs.Extensions.Http;
13 | using Microsoft.Extensions.Logging;
14 |
15 | namespace Microflow.API.Internal
16 | {
17 | ///
18 | /// Use this to mock an orchestration micro-service for testing purposes,
19 | /// this will be moved to its own function app in the future,
20 | /// because it`s better to split the action/micro-service workers from the Microflow app for scalability
21 | ///
22 | public static class SleepTestOrchestrator
23 | {
24 | ///
25 | /// Sleep for between random min and max seconds
26 | ///
27 | [Deterministic]
28 | [FunctionName("SleepTestOrchestrator")]
29 | public static async Task SleepTestMethod(
30 | [OrchestrationTrigger] IDurableOrchestrationContext context,
31 | ILogger inLog)
32 | {
33 | ILogger log = context.CreateReplaySafeLogger(inLog);
34 |
35 | (MicroflowPostData postData, string action) = context.GetInput<(MicroflowPostData, string)>();
36 |
37 | Random random = new();
38 | TimeSpan ts = TimeSpan.FromSeconds(random.Next(1, 5));
39 | DateTime deadline = context.CurrentUtcDateTime.Add(ts);
40 |
41 | using (CancellationTokenSource cts = new())
42 | {
43 | try
44 | {
45 | //cts.CancelAfter(60000);
46 | await context.CreateTimer(deadline, cts.Token);
47 | }
48 | catch (TaskCanceledException)
49 | {
50 | log.LogCritical("========================TaskCanceledException==========================");
51 | }
52 | finally
53 | {
54 | cts.Dispose();
55 | }
56 | }
57 | // test if the webhook is done, do the call back if there is 1
58 | if (!string.IsNullOrWhiteSpace(postData.Webhook))
59 | {
60 | postData.Webhook += string.IsNullOrEmpty(action) ? "" : "/" + action;
61 |
62 | DurableHttpRequest req = new(HttpMethod.Get, new Uri(postData.Webhook));
63 |
64 | await context.CallHttpAsync(req);
65 | }
66 |
67 | return;
68 | }
69 |
70 | ///
71 | /// Called by the normal function and creates a new SleepTestOrchestrator
72 | ///
73 | [FunctionName("SleepTestOrchestrator_HttpStart")]
74 | public static async Task SleepTestOrchestrator_HttpStart(
75 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "SleepTestOrchestrator_HttpStart/{webhookAction?}/{isAsync:bool?}")] HttpRequestMessage req,
76 | [DurableClient] IDurableOrchestrationClient client, string webhookAction, bool? isAsync)
77 | {
78 | if (!isAsync.HasValue)
79 | {
80 | isAsync = false;
81 | }
82 |
83 | MicroflowPostData postData;
84 |
85 | string instanceId = Guid.NewGuid().ToString();
86 |
87 | if (req.Method == HttpMethod.Post)
88 | {
89 | string data = await req.Content.ReadAsStringAsync();
90 |
91 | postData = JsonSerializer.Deserialize(data);
92 |
93 | await client.StartNewAsync("SleepTestOrchestrator", instanceId, (postData, webhookAction));
94 | }
95 | else
96 | {
97 | NameValueCollection data = req.RequestUri.ParseQueryString();
98 |
99 | postData = new()
100 | {
101 | Webhook = data["Webhook"],
102 | MainOrchestrationId = data["MainOrchestrationId"],
103 | WorkflowName = data["WorkflowName"],
104 | RunId = data["RunId"],
105 | StepNumber = Convert.ToInt32(data["StepNumber"]),
106 | StepId = data["StepId"],
107 | SubOrchestrationId = data["SubOrchestrationId"],
108 | GlobalKey = data["GlobalKey"]
109 | };
110 |
111 | await client.StartNewAsync("SleepTestOrchestrator", instanceId, (postData, webhookAction));
112 | }
113 |
114 | if(isAsync.Value)
115 | {
116 | return client.CreateCheckStatusResponse(req, instanceId);
117 | }
118 | else if (!string.IsNullOrEmpty(postData.Webhook))
119 | {
120 | return new HttpResponseMessage(System.Net.HttpStatusCode.OK)
121 | {
122 | Content = new StringContent("{\"success\":\"true\"}\n")
123 | };
124 | }
125 | else
126 | {
127 | await client.WaitForCompletionOrCreateCheckStatusResponseAsync(req, instanceId, TimeSpan.FromSeconds(1000));
128 |
129 | return new HttpResponseMessage(System.Net.HttpStatusCode.OK)
130 | {
131 | Content = new StringContent("{\"success\":\"true\"}\n")
132 | };
133 | }
134 | }
135 |
136 | ///
137 | /// Http entry point as a normal function calling a client function
138 | ///
139 | [FunctionName("SleepTestOrchestrator_Function")]
140 | public static async Task Run(
141 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req)
142 | {
143 | string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
144 |
145 | MicroflowPostData data = JsonSerializer.Deserialize(requestBody);
146 |
147 | await EmulatorShared.HttpClient.PostAsJsonAsync($"{Environment.GetEnvironmentVariable("BaseUrl")}SleepTestOrchestrator_HttpStart/", data);
148 |
149 | HttpResponseMessage resp = new();
150 |
151 | // test the returned status codes here and also the effect if Microflows step setting StopOnWebhookTimeout
152 | //resp.StatusCode = System.Net.HttpStatusCode.NotFound;
153 | resp.StatusCode = System.Net.HttpStatusCode.OK;
154 | // set the location and check in the stpe log if its saved when 201 created
155 | //resp.Headers.Location = new Uri("http://localhost:7071/api/SleepTestOrchestrator_HttpStart/");
156 |
157 | return resp;
158 | }
159 | }
160 | }
--------------------------------------------------------------------------------
/MicroserviceEmulator/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "durableTask": {
4 | "logReplayEvents": "false",
5 | "hubName": "MicroserviceEmulatorTaskHub"
6 | },
7 | "logging": {
8 | "applicationInsights": {
9 | "samplingSettings": {
10 | "isEnabled": true,
11 | "excludedTypes": "Request"
12 | }
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/MicroserviceEmulator/local.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet"
6 | }
7 | }
--------------------------------------------------------------------------------
/local.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet"
6 | }
7 | }
--------------------------------------------------------------------------------