@nocobase/plugin-workflow 0.10.1-alpha.1 → 0.11.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/client.d.ts +2 -3
  2. package/client.js +1 -30
  3. package/lib/client/AddButton.js +13 -11
  4. package/lib/client/Branch.js +10 -8
  5. package/lib/client/CanvasContent.js +12 -10
  6. package/lib/client/ExecutionCanvas.js +37 -33
  7. package/lib/client/ExecutionPage.js +4 -9
  8. package/lib/client/WorkflowCanvas.js +18 -15
  9. package/lib/client/WorkflowPage.js +4 -9
  10. package/lib/client/WorkflowProvider.js +1 -40
  11. package/lib/client/components/CollectionBlockInitializer.js +3 -3
  12. package/lib/client/components/CollectionFieldset.d.ts +1 -1
  13. package/lib/client/components/CollectionFieldset.js +15 -16
  14. package/lib/client/components/Duration.js +5 -5
  15. package/lib/client/components/DynamicExpression.d.ts +3 -3
  16. package/lib/client/components/FieldsSelect.d.ts +1 -1
  17. package/lib/client/components/FieldsSelect.js +10 -7
  18. package/lib/client/components/NodeDescription.js +45 -31
  19. package/lib/client/components/RadioWithTooltip.js +13 -20
  20. package/lib/client/components/ValueBlock.js +14 -21
  21. package/lib/client/components/renderEngineReference.js +1 -8
  22. package/lib/client/index.d.ts +12 -4
  23. package/lib/client/index.js +78 -15
  24. package/lib/client/locale/zh-CN.d.ts +5 -1
  25. package/lib/client/locale/zh-CN.js +6 -2
  26. package/lib/client/nodes/aggregate.d.ts +8 -3
  27. package/lib/client/nodes/aggregate.js +5 -4
  28. package/lib/client/nodes/calculation.d.ts +6 -4
  29. package/lib/client/nodes/calculation.js +22 -28
  30. package/lib/client/nodes/condition.d.ts +2 -10
  31. package/lib/client/nodes/condition.js +19 -37
  32. package/lib/client/nodes/create.d.ts +5 -6
  33. package/lib/client/nodes/create.js +1 -3
  34. package/lib/client/nodes/destroy.d.ts +1 -1
  35. package/lib/client/nodes/index.d.ts +2 -3
  36. package/lib/client/nodes/index.js +95 -102
  37. package/lib/client/nodes/loop.d.ts +1 -1
  38. package/lib/client/nodes/loop.js +46 -54
  39. package/lib/client/nodes/manual/FormBlockInitializer.js +6 -5
  40. package/lib/client/nodes/manual/ModeConfig.js +23 -30
  41. package/lib/client/nodes/manual/SchemaConfig.d.ts +4 -5
  42. package/lib/client/nodes/manual/SchemaConfig.js +180 -25
  43. package/lib/client/nodes/manual/WorkflowTodo.js +95 -110
  44. package/lib/client/nodes/manual/WorkflowTodoBlockInitializer.d.ts +2 -5
  45. package/lib/client/nodes/manual/WorkflowTodoBlockInitializer.js +6 -5
  46. package/lib/client/nodes/manual/forms/create.js +8 -1
  47. package/lib/client/nodes/manual/forms/custom.js +22 -22
  48. package/lib/client/nodes/manual/forms/update.js +8 -1
  49. package/lib/client/nodes/manual/index.d.ts +6 -1
  50. package/lib/client/nodes/manual/index.js +5 -4
  51. package/lib/client/nodes/parallel.js +23 -20
  52. package/lib/client/nodes/query.d.ts +3 -5
  53. package/lib/client/nodes/query.js +1 -3
  54. package/lib/client/nodes/request.d.ts +2 -2
  55. package/lib/client/nodes/request.js +7 -7
  56. package/lib/client/nodes/sql.d.ts +26 -0
  57. package/lib/client/{triggers/schedule/DateFieldsSelect.js → nodes/sql.js} +37 -46
  58. package/lib/client/nodes/update.d.ts +2 -2
  59. package/lib/client/nodes/update.js +1 -1
  60. package/lib/client/schemas/collection.d.ts +3 -4
  61. package/lib/client/schemas/collection.js +11 -17
  62. package/lib/client/style.d.ts +18 -13
  63. package/lib/client/style.js +315 -292
  64. package/lib/client/triggers/collection.d.ts +13 -13
  65. package/lib/client/triggers/collection.js +5 -1
  66. package/lib/client/triggers/index.d.ts +3 -4
  67. package/lib/client/triggers/index.js +51 -53
  68. package/lib/client/triggers/schedule/EndsByField.js +11 -11
  69. package/lib/client/triggers/schedule/OnField.js +45 -33
  70. package/lib/client/triggers/schedule/RepeatField.js +4 -4
  71. package/lib/client/triggers/schedule/ScheduleConfig.js +24 -31
  72. package/lib/client/triggers/schedule/index.d.ts +1 -1
  73. package/lib/client/triggers/schedule/index.js +32 -20
  74. package/lib/client/variable.d.ts +31 -13
  75. package/lib/client/variable.js +44 -29
  76. package/lib/server/Plugin.d.ts +3 -6
  77. package/lib/server/Plugin.js +15 -12
  78. package/lib/server/Processor.d.ts +3 -5
  79. package/lib/server/Processor.js +2 -2
  80. package/lib/server/actions/nodes.js +7 -7
  81. package/lib/server/fields/expression-field.d.ts +1 -2
  82. package/lib/server/fields/expression-field.js +1 -8
  83. package/lib/server/functions/index.d.ts +2 -3
  84. package/lib/server/index.d.ts +1 -0
  85. package/lib/server/index.js +12 -0
  86. package/lib/server/instructions/aggregate.d.ts +1 -1
  87. package/lib/server/instructions/aggregate.js +5 -5
  88. package/lib/server/instructions/condition.d.ts +2 -1
  89. package/lib/server/instructions/create.d.ts +2 -2
  90. package/lib/server/instructions/create.js +13 -13
  91. package/lib/server/instructions/delay.d.ts +3 -3
  92. package/lib/server/instructions/delay.js +66 -64
  93. package/lib/server/instructions/destroy.d.ts +1 -1
  94. package/lib/server/instructions/index.d.ts +5 -5
  95. package/lib/server/instructions/index.js +1 -1
  96. package/lib/server/instructions/loop.d.ts +1 -2
  97. package/lib/server/instructions/manual/actions.js +19 -7
  98. package/lib/server/instructions/manual/forms/create.js +7 -1
  99. package/lib/server/instructions/manual/forms/index.d.ts +1 -1
  100. package/lib/server/instructions/manual/forms/update.js +7 -1
  101. package/lib/server/instructions/manual/index.d.ts +1 -1
  102. package/lib/server/instructions/parallel.d.ts +1 -2
  103. package/lib/server/instructions/query.d.ts +1 -1
  104. package/lib/server/instructions/query.js +8 -1
  105. package/lib/server/instructions/request.d.ts +3 -3
  106. package/lib/server/instructions/request.js +5 -2
  107. package/lib/server/instructions/sql.d.ts +12 -0
  108. package/lib/server/instructions/sql.js +34 -0
  109. package/lib/server/instructions/update.d.ts +1 -1
  110. package/lib/server/migrations/20230221071831-calculation-expression.js +1 -1
  111. package/lib/server/migrations/20230221121203-condition-calculation.js +1 -1
  112. package/lib/server/migrations/20230221162902-jsonb-to-json.js +7 -7
  113. package/lib/server/migrations/20230411034722-manual-multi-form.js +1 -8
  114. package/lib/server/migrations/20230710115902-manual-action-values.d.ts +4 -0
  115. package/lib/server/migrations/20230710115902-manual-action-values.js +97 -0
  116. package/lib/server/triggers/collection.d.ts +1 -1
  117. package/lib/server/triggers/collection.js +15 -13
  118. package/lib/server/triggers/index.d.ts +1 -1
  119. package/lib/server/triggers/schedule.d.ts +1 -1
  120. package/lib/server/triggers/schedule.js +18 -18
  121. package/lib/server/{models → types}/Execution.d.ts +2 -3
  122. package/lib/server/{models → types}/FlowNode.d.ts +1 -2
  123. package/lib/server/{models → types}/Job.d.ts +1 -2
  124. package/lib/server/{models → types}/Workflow.d.ts +1 -2
  125. package/lib/server/types/index.d.ts +4 -0
  126. package/lib/server/types/index.js +5 -0
  127. package/lib/server/utils.d.ts +2 -0
  128. package/lib/server/utils.js +21 -0
  129. package/package.json +39 -18
  130. package/server.d.ts +2 -3
  131. package/server.js +1 -30
  132. package/src/client/AddButton.tsx +111 -0
  133. package/src/client/Branch.tsx +37 -0
  134. package/src/client/CanvasContent.tsx +25 -0
  135. package/src/client/ExecutionCanvas.tsx +166 -0
  136. package/src/client/ExecutionLink.tsx +16 -0
  137. package/src/client/ExecutionPage.tsx +45 -0
  138. package/src/client/ExecutionResourceProvider.tsx +21 -0
  139. package/src/client/FlowContext.ts +7 -0
  140. package/src/client/WorkflowCanvas.tsx +221 -0
  141. package/src/client/WorkflowLink.tsx +16 -0
  142. package/src/client/WorkflowPage.tsx +52 -0
  143. package/src/client/WorkflowProvider.tsx +84 -0
  144. package/src/client/components/CollectionBlockInitializer.tsx +71 -0
  145. package/src/client/components/CollectionFieldset.tsx +160 -0
  146. package/src/client/components/Duration.tsx +45 -0
  147. package/src/client/components/DynamicExpression.tsx +53 -0
  148. package/src/client/components/FieldsSelect.tsx +32 -0
  149. package/src/client/components/FilterDynamicComponent.tsx +15 -0
  150. package/src/client/components/NodeDescription.tsx +51 -0
  151. package/src/client/components/NullRender.tsx +3 -0
  152. package/src/client/components/OpenDrawer.tsx +24 -0
  153. package/src/client/components/RadioWithTooltip.tsx +38 -0
  154. package/src/client/components/ValueBlock.tsx +67 -0
  155. package/src/client/components/renderEngineReference.tsx +30 -0
  156. package/src/client/constants.tsx +91 -0
  157. package/src/client/index.tsx +51 -0
  158. package/src/client/interfaces/expression.tsx +25 -0
  159. package/src/client/locale/en-US.ts +136 -0
  160. package/src/client/locale/es-ES.ts +129 -0
  161. package/src/client/locale/index.ts +18 -0
  162. package/src/client/locale/ja-JP.ts +90 -0
  163. package/src/client/locale/pt-BR.ts +136 -0
  164. package/src/client/locale/ru-RU.ts +90 -0
  165. package/src/client/locale/tr-TR.ts +90 -0
  166. package/src/client/locale/zh-CN.ts +248 -0
  167. package/src/client/nodes/aggregate.tsx +327 -0
  168. package/src/client/nodes/calculation.tsx +216 -0
  169. package/src/client/nodes/condition.tsx +463 -0
  170. package/src/client/nodes/create.tsx +85 -0
  171. package/src/client/nodes/delay.tsx +37 -0
  172. package/src/client/nodes/destroy.tsx +34 -0
  173. package/src/client/nodes/index.tsx +485 -0
  174. package/src/client/nodes/loop.tsx +144 -0
  175. package/src/client/nodes/manual/AssigneesSelect.tsx +33 -0
  176. package/src/client/nodes/manual/DetailsBlockProvider.tsx +80 -0
  177. package/src/client/nodes/manual/FormBlockInitializer.tsx +69 -0
  178. package/src/client/nodes/manual/FormBlockProvider.tsx +75 -0
  179. package/src/client/nodes/manual/ModeConfig.tsx +84 -0
  180. package/src/client/nodes/manual/SchemaConfig.tsx +509 -0
  181. package/src/client/nodes/manual/WorkflowTodo.tsx +607 -0
  182. package/src/client/nodes/manual/WorkflowTodoBlockInitializer.tsx +28 -0
  183. package/src/client/nodes/manual/forms/create.tsx +92 -0
  184. package/src/client/nodes/manual/forms/custom.tsx +392 -0
  185. package/src/client/nodes/manual/forms/update.tsx +134 -0
  186. package/src/client/nodes/manual/index.tsx +162 -0
  187. package/src/client/nodes/manual/utils.ts +28 -0
  188. package/src/client/nodes/parallel.tsx +138 -0
  189. package/src/client/nodes/query.tsx +88 -0
  190. package/src/client/nodes/request.tsx +185 -0
  191. package/src/client/nodes/sql.tsx +37 -0
  192. package/src/client/nodes/update.tsx +99 -0
  193. package/src/client/schemas/collection.ts +75 -0
  194. package/src/client/schemas/executions.tsx +169 -0
  195. package/src/client/schemas/workflows.ts +364 -0
  196. package/src/client/style.tsx +347 -0
  197. package/src/client/triggers/collection.tsx +190 -0
  198. package/src/client/triggers/index.tsx +311 -0
  199. package/src/client/triggers/schedule/EndsByField.tsx +40 -0
  200. package/src/client/triggers/schedule/OnField.tsx +64 -0
  201. package/src/client/triggers/schedule/RepeatField.tsx +116 -0
  202. package/src/client/triggers/schedule/ScheduleConfig.tsx +227 -0
  203. package/src/client/triggers/schedule/constants.ts +4 -0
  204. package/src/client/triggers/schedule/index.tsx +78 -0
  205. package/src/client/triggers/schedule/locale/Cron.zh-CN.ts +79 -0
  206. package/src/client/utils.ts +36 -0
  207. package/src/client/variable.tsx +318 -0
  208. package/src/index.ts +1 -0
  209. package/src/server/Plugin.ts +355 -0
  210. package/src/server/Processor.ts +354 -0
  211. package/src/server/__tests__/Plugin.test.ts +398 -0
  212. package/src/server/__tests__/Processor.test.ts +474 -0
  213. package/src/server/__tests__/actions/workflows.test.ts +419 -0
  214. package/src/server/__tests__/collections/categories.ts +27 -0
  215. package/src/server/__tests__/collections/comments.ts +24 -0
  216. package/src/server/__tests__/collections/posts.ts +42 -0
  217. package/src/server/__tests__/collections/replies.ts +9 -0
  218. package/src/server/__tests__/collections/tags.ts +15 -0
  219. package/src/server/__tests__/index.ts +89 -0
  220. package/src/server/__tests__/instructions/aggregate.test.ts +294 -0
  221. package/src/server/__tests__/instructions/calculation.test.ts +265 -0
  222. package/src/server/__tests__/instructions/condition.test.ts +335 -0
  223. package/src/server/__tests__/instructions/create.test.ts +129 -0
  224. package/src/server/__tests__/instructions/delay.test.ts +182 -0
  225. package/src/server/__tests__/instructions/destroy.test.ts +58 -0
  226. package/src/server/__tests__/instructions/loop.test.ts +331 -0
  227. package/src/server/__tests__/instructions/manual.test.ts +1173 -0
  228. package/src/server/__tests__/instructions/parallel.test.ts +445 -0
  229. package/src/server/__tests__/instructions/query.test.ts +359 -0
  230. package/src/server/__tests__/instructions/request.test.ts +247 -0
  231. package/src/server/__tests__/instructions/sql.test.ts +162 -0
  232. package/src/server/__tests__/instructions/update.test.ts +189 -0
  233. package/src/server/__tests__/triggers/collection.test.ts +333 -0
  234. package/src/server/__tests__/triggers/schedule.test.ts +369 -0
  235. package/src/server/actions/index.ts +25 -0
  236. package/src/server/actions/nodes.ts +214 -0
  237. package/src/server/actions/workflows.ts +178 -0
  238. package/src/server/collections/executions.ts +35 -0
  239. package/src/server/collections/flow_nodes.ts +54 -0
  240. package/src/server/collections/jobs.ts +31 -0
  241. package/src/server/collections/workflows.ts +88 -0
  242. package/src/server/constants.ts +26 -0
  243. package/src/server/fields/expression-field.ts +11 -0
  244. package/src/server/fields/index.ts +7 -0
  245. package/src/server/functions/index.ts +16 -0
  246. package/src/server/index.ts +6 -0
  247. package/src/server/instructions/aggregate.ts +42 -0
  248. package/src/server/instructions/calculation.ts +41 -0
  249. package/src/server/instructions/condition.ts +172 -0
  250. package/src/server/instructions/create.ts +39 -0
  251. package/src/server/instructions/delay.ts +105 -0
  252. package/src/server/instructions/destroy.ts +23 -0
  253. package/src/server/instructions/index.ts +64 -0
  254. package/src/server/instructions/loop.ts +99 -0
  255. package/src/server/instructions/manual/actions.ts +91 -0
  256. package/src/server/instructions/manual/collecions/jobs.ts +17 -0
  257. package/src/server/instructions/manual/collecions/users.ts +15 -0
  258. package/src/server/instructions/manual/collecions/users_jobs.ts +50 -0
  259. package/src/server/instructions/manual/forms/create.ts +23 -0
  260. package/src/server/instructions/manual/forms/index.ts +12 -0
  261. package/src/server/instructions/manual/forms/update.ts +23 -0
  262. package/src/server/instructions/manual/index.ts +184 -0
  263. package/src/server/instructions/parallel.ts +121 -0
  264. package/src/server/instructions/query.ts +42 -0
  265. package/src/server/instructions/request.ts +88 -0
  266. package/src/server/instructions/sql.ts +25 -0
  267. package/src/server/instructions/update.ts +24 -0
  268. package/src/server/migrations/20221129153547-calculation-variables.ts +64 -0
  269. package/src/server/migrations/20230221032941-change-request-body-type.ts +76 -0
  270. package/src/server/migrations/20230221071831-calculation-expression.ts +102 -0
  271. package/src/server/migrations/20230221121203-condition-calculation.ts +82 -0
  272. package/src/server/migrations/20230221162902-jsonb-to-json.ts +51 -0
  273. package/src/server/migrations/20230411034722-manual-multi-form.ts +282 -0
  274. package/src/server/migrations/20230612021134-manual-collection-block.ts +138 -0
  275. package/src/server/migrations/20230710115902-manual-action-values.ts +78 -0
  276. package/src/server/triggers/collection.ts +146 -0
  277. package/src/server/triggers/index.ts +22 -0
  278. package/src/server/triggers/schedule.ts +567 -0
  279. package/src/server/types/Execution.ts +26 -0
  280. package/src/server/types/FlowNode.ts +21 -0
  281. package/src/server/types/Job.ts +18 -0
  282. package/src/server/types/Workflow.ts +36 -0
  283. package/src/server/types/index.ts +4 -0
  284. package/src/server/utils.ts +17 -0
  285. package/lib/client/triggers/schedule/DateFieldsSelect.d.ts +0 -2
  286. /package/lib/server/{models → types}/Execution.js +0 -0
  287. /package/lib/server/{models → types}/FlowNode.js +0 -0
  288. /package/lib/server/{models → types}/Job.js +0 -0
  289. /package/lib/server/{models → types}/Workflow.js +0 -0
@@ -0,0 +1,172 @@
1
+ import { evaluators } from '@nocobase/evaluators';
2
+ import { Registry } from '@nocobase/utils';
3
+ import { Instruction } from '.';
4
+ import { Processor } from '..';
5
+ import { JOB_STATUS } from '../constants';
6
+ import type { FlowNodeModel } from '../types';
7
+
8
+ type Comparer = (a: any, b: any) => boolean;
9
+
10
+ export const calculators = new Registry<Comparer>();
11
+
12
+ // built-in functions
13
+ function equal(a, b) {
14
+ return a === b;
15
+ }
16
+
17
+ function notEqual(a, b) {
18
+ return a !== b;
19
+ }
20
+
21
+ function gt(a, b) {
22
+ return a > b;
23
+ }
24
+
25
+ function gte(a, b) {
26
+ return a >= b;
27
+ }
28
+
29
+ function lt(a, b) {
30
+ return a < b;
31
+ }
32
+
33
+ function lte(a, b) {
34
+ return a <= b;
35
+ }
36
+
37
+ calculators.register('equal', equal);
38
+ calculators.register('notEqual', notEqual);
39
+ calculators.register('gt', gt);
40
+ calculators.register('gte', gte);
41
+ calculators.register('lt', lt);
42
+ calculators.register('lte', lte);
43
+
44
+ calculators.register('===', equal);
45
+ calculators.register('!==', notEqual);
46
+ calculators.register('>', gt);
47
+ calculators.register('>=', gte);
48
+ calculators.register('<', lt);
49
+ calculators.register('<=', lte);
50
+
51
+ function includes(a, b) {
52
+ return a.includes(b);
53
+ }
54
+
55
+ function notIncludes(a, b) {
56
+ return !a.includes(b);
57
+ }
58
+
59
+ function startsWith(a: string, b: string) {
60
+ return a.startsWith(b);
61
+ }
62
+
63
+ function notStartsWith(a: string, b: string) {
64
+ return !a.startsWith(b);
65
+ }
66
+
67
+ function endsWith(a: string, b: string) {
68
+ return a.endsWith(b);
69
+ }
70
+
71
+ function notEndsWith(a: string, b: string) {
72
+ return !a.endsWith(b);
73
+ }
74
+
75
+ calculators.register('includes', includes);
76
+ calculators.register('notIncludes', notIncludes);
77
+ calculators.register('startsWith', startsWith);
78
+ calculators.register('notStartsWith', notStartsWith);
79
+ calculators.register('endsWith', endsWith);
80
+ calculators.register('notEndsWith', notEndsWith);
81
+
82
+ type CalculationItem = {
83
+ calculator?: string;
84
+ operands?: [any?, any?];
85
+ };
86
+
87
+ type CalculationGroup = {
88
+ group: {
89
+ type: 'and' | 'or';
90
+ calculations?: Calculation[];
91
+ };
92
+ };
93
+
94
+ type Calculation = CalculationItem | CalculationGroup;
95
+
96
+ function calculate(calculation: CalculationItem = {}) {
97
+ type NewType = Comparer;
98
+
99
+ let fn: NewType;
100
+ if (!(calculation.calculator && (fn = calculators.get(calculation.calculator)))) {
101
+ throw new Error(`no calculator function registered for "${calculation.calculator}"`);
102
+ }
103
+ return Boolean(fn(...(calculation.operands ?? [])));
104
+ }
105
+
106
+ function logicCalculate(calculation?: Calculation) {
107
+ if (!calculation) {
108
+ return true;
109
+ }
110
+
111
+ if (typeof calculation['group'] === 'object') {
112
+ const method = calculation['group'].type === 'and' ? 'every' : 'some';
113
+ return (calculation['group'].calculations ?? [])[method]((item: Calculation) => logicCalculate(item));
114
+ }
115
+
116
+ return calculate(calculation as CalculationItem);
117
+ }
118
+
119
+ export default {
120
+ async run(node: FlowNodeModel, prevJob, processor: Processor) {
121
+ const { engine, calculation, expression, rejectOnFalse } = node.config || {};
122
+ const evaluator = evaluators.get(engine);
123
+
124
+ let result = true;
125
+
126
+ try {
127
+ result = evaluator
128
+ ? evaluator(expression, processor.getScope())
129
+ : logicCalculate(processor.getParsedValue(calculation, node));
130
+ } catch (e) {
131
+ return {
132
+ result: e.toString(),
133
+ status: JOB_STATUS.ERROR,
134
+ };
135
+ }
136
+
137
+ if (!result && rejectOnFalse) {
138
+ return {
139
+ status: JOB_STATUS.FAILED,
140
+ result,
141
+ };
142
+ }
143
+
144
+ const job = {
145
+ status: JOB_STATUS.RESOLVED,
146
+ result,
147
+ // TODO(optimize): try unify the building of job
148
+ nodeId: node.id,
149
+ upstreamId: (prevJob && prevJob.id) || null,
150
+ };
151
+
152
+ const branchNode = processor.nodes.find((item) => item.upstream === node && Boolean(item.branchIndex) === result);
153
+
154
+ if (!branchNode) {
155
+ return job;
156
+ }
157
+
158
+ const savedJob = await processor.saveJob(job);
159
+
160
+ return processor.run(branchNode, savedJob);
161
+ },
162
+
163
+ async resume(node: FlowNodeModel, branchJob, processor: Processor) {
164
+ if (branchJob.status === JOB_STATUS.RESOLVED) {
165
+ // return to continue node.downstream
166
+ return branchJob;
167
+ }
168
+
169
+ // pass control to upper scope by ending current scope
170
+ return processor.end(node, branchJob);
171
+ },
172
+ } as Instruction;
@@ -0,0 +1,39 @@
1
+ import { JOB_STATUS } from '../constants';
2
+ import { toJSON } from '../utils';
3
+ import type { FlowNodeModel } from '../types';
4
+
5
+ export default {
6
+ async run(node: FlowNodeModel, input, processor) {
7
+ const { collection, params: { appends = [], ...params } = {} } = node.config;
8
+
9
+ const { repository, model } = (<typeof FlowNodeModel>node.constructor).database.getCollection(collection);
10
+ const options = processor.getParsedValue(params, node);
11
+ const created = await repository.create({
12
+ ...options,
13
+ context: {
14
+ executionId: processor.execution.id,
15
+ },
16
+ transaction: processor.transaction,
17
+ });
18
+
19
+ let result = created;
20
+ if (created && appends.length) {
21
+ const includeFields = appends.reduce((set, field) => {
22
+ set.add(field.split('.')[0]);
23
+ set.add(field);
24
+ return set;
25
+ }, new Set());
26
+ result = await repository.findOne({
27
+ filterByTk: created[model.primaryKeyAttribute],
28
+ appends: Array.from(includeFields),
29
+ transaction: processor.transaction,
30
+ });
31
+ }
32
+
33
+ return {
34
+ // NOTE: get() for non-proxied instance (#380)
35
+ result: toJSON(result),
36
+ status: JOB_STATUS.RESOLVED,
37
+ };
38
+ },
39
+ };
@@ -0,0 +1,105 @@
1
+ import Plugin from '..';
2
+ import { EXECUTION_STATUS, JOB_STATUS } from '../constants';
3
+ import Processor from '../Processor';
4
+ import { Instruction } from '.';
5
+ import type { ExecutionModel, JobModel } from '../types';
6
+
7
+ type ValueOf<T> = T[keyof T];
8
+
9
+ interface DelayConfig {
10
+ endStatus: ValueOf<typeof JOB_STATUS>;
11
+ duration: number;
12
+ }
13
+
14
+ export default class implements Instruction {
15
+ timers: Map<number, NodeJS.Timeout> = new Map();
16
+
17
+ constructor(protected plugin: Plugin) {
18
+ plugin.app.on('beforeStart', this.load);
19
+ plugin.app.on('beforeStop', this.unload);
20
+ }
21
+
22
+ load = async () => {
23
+ const { model } = this.plugin.db.getCollection('jobs');
24
+ const jobs = (await model.findAll({
25
+ where: {
26
+ status: JOB_STATUS.PENDING,
27
+ },
28
+ include: [
29
+ {
30
+ association: 'execution',
31
+ attributes: [],
32
+ where: {
33
+ status: EXECUTION_STATUS.STARTED,
34
+ },
35
+ required: true,
36
+ },
37
+ {
38
+ association: 'node',
39
+ attributes: ['config'],
40
+ where: {
41
+ type: 'delay',
42
+ },
43
+ required: true,
44
+ },
45
+ ],
46
+ })) as JobModel[];
47
+
48
+ jobs.forEach((job) => {
49
+ this.schedule(job);
50
+ });
51
+ };
52
+
53
+ unload = () => {
54
+ for (const timer of this.timers.values()) {
55
+ clearTimeout(timer);
56
+ }
57
+
58
+ this.timers = new Map();
59
+ };
60
+
61
+ schedule(job) {
62
+ const now = new Date();
63
+ const createdAt = Date.parse(job.createdAt);
64
+ const delay = createdAt + job.node.config.duration - now.getTime();
65
+ if (delay > 0) {
66
+ const trigger = this.trigger.bind(this, job);
67
+ this.timers.set(job.id, setTimeout(trigger, delay));
68
+ } else {
69
+ this.trigger(job);
70
+ }
71
+ }
72
+
73
+ async trigger(job) {
74
+ if (!job.execution) {
75
+ job.execution = await job.getExecution();
76
+ }
77
+ if (job.execution.status === EXECUTION_STATUS.STARTED) {
78
+ this.plugin.resume(job);
79
+ }
80
+ if (this.timers.get(job.id)) {
81
+ this.timers.delete(job.id);
82
+ }
83
+ }
84
+
85
+ run = async (node, prevJob, processor: Processor) => {
86
+ const job = await processor.saveJob({
87
+ status: JOB_STATUS.PENDING,
88
+ result: null,
89
+ nodeId: node.id,
90
+ upstreamId: prevJob?.id ?? null,
91
+ });
92
+ job.node = node;
93
+
94
+ // add to schedule
95
+ this.schedule(job);
96
+
97
+ return processor.exit();
98
+ };
99
+
100
+ resume = async (node, prevJob, processor: Processor) => {
101
+ const { endStatus } = node.config as DelayConfig;
102
+ prevJob.set('status', endStatus);
103
+ return prevJob;
104
+ };
105
+ }
@@ -0,0 +1,23 @@
1
+ import { JOB_STATUS } from '../constants';
2
+ import type { FlowNodeModel } from '../types';
3
+
4
+ export default {
5
+ async run(node: FlowNodeModel, input, processor) {
6
+ const { collection, params = {} } = node.config;
7
+
8
+ const repo = (<typeof FlowNodeModel>node.constructor).database.getRepository(collection);
9
+ const options = processor.getParsedValue(params, node);
10
+ const result = await repo.destroy({
11
+ ...options,
12
+ context: {
13
+ executionId: processor.execution.id,
14
+ },
15
+ transaction: processor.transaction,
16
+ });
17
+
18
+ return {
19
+ result,
20
+ status: JOB_STATUS.RESOLVED,
21
+ };
22
+ },
23
+ };
@@ -0,0 +1,64 @@
1
+ import path from 'path';
2
+
3
+ import { requireModule } from '@nocobase/utils';
4
+
5
+ import Plugin from '..';
6
+ import Processor from '../Processor';
7
+
8
+ import type { FlowNodeModel } from '../types';
9
+
10
+ export type Job = {
11
+ status: number;
12
+ result?: unknown;
13
+ [key: string]: unknown;
14
+ } | null;
15
+
16
+ export type InstructionResult = Job | Promise<Job>;
17
+
18
+ export type Runner = (node: FlowNodeModel, input: any, processor: Processor) => InstructionResult;
19
+
20
+ // what should a instruction do?
21
+ // - base on input and context, do any calculations or system call (io), and produce a result or pending.
22
+ export interface Instruction {
23
+ run: Runner;
24
+
25
+ // for start node in main flow (or branch) to resume when manual sub branch triggered
26
+ resume?: Runner;
27
+
28
+ getScope?: (node: FlowNodeModel, job: any, processor: Processor) => any;
29
+ }
30
+
31
+ type InstructionConstructor<T> = { new (p: Plugin): T };
32
+
33
+ export default function <T extends Instruction>(plugin, more: { [key: string]: T | InstructionConstructor<T> } = {}) {
34
+ const { instructions } = plugin;
35
+
36
+ const natives = [
37
+ 'calculation',
38
+ 'condition',
39
+ 'parallel',
40
+ 'loop',
41
+ 'delay',
42
+ 'manual',
43
+ 'query',
44
+ 'create',
45
+ 'update',
46
+ 'destroy',
47
+ 'aggregate',
48
+ 'request',
49
+ 'sql',
50
+ ].reduce(
51
+ (result, key) =>
52
+ Object.assign(result, {
53
+ [key]: requireModule(path.isAbsolute(key) ? key : path.join(__dirname, key)),
54
+ }),
55
+ {},
56
+ );
57
+
58
+ for (const [name, instruction] of Object.entries({ ...more, ...natives })) {
59
+ instructions.register(
60
+ name,
61
+ typeof instruction === 'function' ? new (instruction as InstructionConstructor<T>)(plugin) : instruction,
62
+ );
63
+ }
64
+ }
@@ -0,0 +1,99 @@
1
+ import Processor from '../Processor';
2
+ import { JOB_STATUS } from '../constants';
3
+ import type { FlowNodeModel, JobModel } from '../types';
4
+
5
+ function getTargetLength(target) {
6
+ let length = 0;
7
+ if (typeof target === 'number') {
8
+ if (target < 0) {
9
+ throw new Error('Loop target in number type must be greater than 0');
10
+ }
11
+ length = Math.floor(target);
12
+ } else {
13
+ const targets = (Array.isArray(target) ? target : [target]).filter((t) => t != null);
14
+ length = targets.length;
15
+ }
16
+ return length;
17
+ }
18
+
19
+ export default {
20
+ async run(node: FlowNodeModel, prevJob: JobModel, processor: Processor) {
21
+ const [branch] = processor.getBranches(node);
22
+ const target = processor.getParsedValue(node.config.target, node);
23
+ const length = getTargetLength(target);
24
+
25
+ if (!branch || !length) {
26
+ return {
27
+ status: JOB_STATUS.RESOLVED,
28
+ result: 0,
29
+ };
30
+ }
31
+
32
+ const job = await processor.saveJob({
33
+ status: JOB_STATUS.PENDING,
34
+ // save loop index
35
+ result: 0,
36
+ nodeId: node.id,
37
+ upstreamId: prevJob?.id ?? null,
38
+ });
39
+
40
+ // TODO: add loop scope to stack
41
+ // processor.stack.push({
42
+ // label: node.title,
43
+ // value: node.id
44
+ // });
45
+
46
+ await processor.run(branch, job);
47
+
48
+ return null;
49
+ },
50
+
51
+ async resume(node: FlowNodeModel, branchJob, processor: Processor) {
52
+ const job = processor.findBranchParentJob(branchJob, node) as JobModel;
53
+ const loop = processor.nodesMap.get(job.nodeId);
54
+ const [branch] = processor.getBranches(node);
55
+
56
+ const { result, status } = job;
57
+ // if loop has been done (resolved / rejected), do not care newly executed branch jobs.
58
+ if (status !== JOB_STATUS.PENDING) {
59
+ return processor.exit();
60
+ }
61
+
62
+ const nextIndex = result + 1;
63
+
64
+ const target = processor.getParsedValue(loop.config.target, node);
65
+ // branchJob.status === JOB_STATUS.RESOLVED means branchJob is done, try next loop or exit as resolved
66
+ if (branchJob.status > JOB_STATUS.PENDING) {
67
+ job.set({ result: nextIndex });
68
+
69
+ const length = getTargetLength(target);
70
+ if (nextIndex < length) {
71
+ await processor.saveJob(job);
72
+ await processor.run(branch, job);
73
+ return null;
74
+ }
75
+ }
76
+
77
+ // branchJob.status < JOB_STATUS.PENDING means branchJob is rejected, any rejection should cause loop rejected
78
+ job.set({
79
+ status: branchJob.status,
80
+ });
81
+
82
+ return job;
83
+ },
84
+
85
+ getScope(node, index, processor) {
86
+ const target = processor.getParsedValue(node.config.target, node);
87
+ const targets = (Array.isArray(target) ? target : [target]).filter((t) => t != null);
88
+ const length = getTargetLength(target);
89
+ const item = typeof target === 'number' ? index : targets[index];
90
+
91
+ const result = {
92
+ item,
93
+ index,
94
+ length,
95
+ };
96
+
97
+ return result;
98
+ },
99
+ };
@@ -0,0 +1,91 @@
1
+ import { Context, utils } from '@nocobase/actions';
2
+
3
+ import Plugin from '../..';
4
+ import { EXECUTION_STATUS, JOB_STATUS } from '../../constants';
5
+ import ManualInstruction from '.';
6
+
7
+ export async function submit(context: Context, next) {
8
+ const repository = utils.getRepositoryFromParams(context);
9
+ const { filterByTk, values } = context.action.params;
10
+ const { currentUser } = context.state;
11
+
12
+ if (!currentUser) {
13
+ return context.throw(401);
14
+ }
15
+
16
+ const plugin: Plugin = context.app.pm.get('workflow') as Plugin;
17
+ const instruction = plugin.instructions.get('manual') as ManualInstruction;
18
+
19
+ const userJob = await repository.findOne({
20
+ filterByTk,
21
+ // filter: {
22
+ // userId: currentUser?.id
23
+ // },
24
+ appends: ['job', 'node', 'execution', 'workflow'],
25
+ context,
26
+ });
27
+
28
+ if (!userJob) {
29
+ return context.throw(404);
30
+ }
31
+
32
+ const { forms = {} } = userJob.node.config;
33
+ const [formKey] = Object.keys(values.result ?? {}).filter(key => key !== '_');
34
+ const actionKey = values.result?._;
35
+
36
+ const actionItem = forms[formKey]?.actions?.find((item) => item.key === actionKey);
37
+ // NOTE: validate status
38
+ if (
39
+ userJob.status !== JOB_STATUS.PENDING ||
40
+ userJob.job.status !== JOB_STATUS.PENDING ||
41
+ userJob.execution.status !== EXECUTION_STATUS.STARTED ||
42
+ !userJob.workflow.enabled ||
43
+ !actionKey || actionItem?.status == null
44
+ ) {
45
+ return context.throw(400);
46
+ }
47
+
48
+ userJob.execution.workflow = userJob.workflow;
49
+ const processor = plugin.createProcessor(userJob.execution);
50
+ await processor.prepare();
51
+
52
+ // NOTE: validate assignee
53
+ const assignees = processor.getParsedValue(userJob.node.config.assignees ?? []);
54
+ if (!assignees.includes(currentUser.id) || userJob.userId !== currentUser.id) {
55
+ return context.throw(403);
56
+ }
57
+ const presetValues = processor.getParsedValue(actionItem.values ?? {}, null, {
58
+ currentUser: currentUser.toJSON(),
59
+ currentRecord: values.result[formKey],
60
+ currentTime: new Date(),
61
+ });
62
+
63
+ userJob.set({
64
+ status: actionItem.status,
65
+ result: actionItem.status > JOB_STATUS.PENDING
66
+ ? { [formKey]: Object.assign(values.result[formKey], presetValues), _: actionKey }
67
+ : Object.assign(userJob.result ?? {}, values.result),
68
+ });
69
+
70
+ const handler = instruction.formTypes.get(forms[formKey].type);
71
+ if (handler && userJob.status) {
72
+ await handler.call(instruction, userJob, forms[formKey], processor);
73
+ }
74
+
75
+ await userJob.save({ transaction: processor.transaction });
76
+
77
+ await processor.exit();
78
+
79
+ context.body = userJob;
80
+ context.status = 202;
81
+
82
+ await next();
83
+
84
+ userJob.job.execution = userJob.execution;
85
+ userJob.job.latestUserJob = userJob;
86
+
87
+ // NOTE: resume the process and no `await` for quick returning
88
+ processor.logger.info(`manual node (${userJob.nodeId}) action trigger execution (${userJob.execution.id}) to resume`);
89
+
90
+ plugin.resume(userJob.job);
91
+ }
@@ -0,0 +1,17 @@
1
+ export default {
2
+ name: 'jobs',
3
+ fields: [
4
+ {
5
+ type: 'belongsToMany',
6
+ name: 'users',
7
+ through: 'users_jobs',
8
+ },
9
+ {
10
+ type: 'hasMany',
11
+ name: 'usersJobs',
12
+ target: 'users_jobs',
13
+ foreignKey: 'jobId',
14
+ onDelete: 'CASCADE',
15
+ },
16
+ ],
17
+ };
@@ -0,0 +1,15 @@
1
+ export default {
2
+ name: 'users',
3
+ fields: [
4
+ {
5
+ type: 'belongsToMany',
6
+ name: 'jobs',
7
+ through: 'users_jobs',
8
+ },
9
+ {
10
+ type: 'hasMany',
11
+ name: 'usersJobs',
12
+ target: 'users_jobs',
13
+ },
14
+ ],
15
+ };
@@ -0,0 +1,50 @@
1
+ import { CollectionOptions } from '@nocobase/database';
2
+
3
+ export default {
4
+ namespace: 'workflow.executionLogs',
5
+ name: 'users_jobs',
6
+ duplicator: 'optional',
7
+ fields: [
8
+ {
9
+ type: 'bigInt',
10
+ name: 'id',
11
+ primaryKey: true,
12
+ autoIncrement: true,
13
+ },
14
+ {
15
+ type: 'belongsTo',
16
+ name: 'job',
17
+ target: 'jobs',
18
+ foreignKey: 'jobId',
19
+ primaryKey: false,
20
+ },
21
+ {
22
+ type: 'belongsTo',
23
+ name: 'user',
24
+ target: 'users',
25
+ foreignKey: 'userId',
26
+ primaryKey: false,
27
+ },
28
+ {
29
+ type: 'belongsTo',
30
+ name: 'execution',
31
+ },
32
+ {
33
+ type: 'belongsTo',
34
+ name: 'node',
35
+ target: 'flow_nodes',
36
+ },
37
+ {
38
+ type: 'belongsTo',
39
+ name: 'workflow',
40
+ },
41
+ {
42
+ type: 'integer',
43
+ name: 'status',
44
+ },
45
+ {
46
+ type: 'jsonb',
47
+ name: 'result',
48
+ },
49
+ ],
50
+ } as CollectionOptions;
@@ -0,0 +1,23 @@
1
+ import { Processor } from '../../..';
2
+ import ManualInstruction from '..';
3
+
4
+ export default async function (this: ManualInstruction, instance, { collection }, processor: Processor) {
5
+ const repo = this.plugin.db.getRepository(collection);
6
+ if (!repo) {
7
+ throw new Error(`collection ${collection} for create data on manual node not found`);
8
+ }
9
+
10
+ const { _, ...form } = instance.result;
11
+ const [values] = Object.values(form);
12
+ await repo.create({
13
+ values: {
14
+ ...((values as { [key: string]: any }) ?? {}),
15
+ createdBy: instance.userId,
16
+ updatedBy: instance.userId,
17
+ },
18
+ context: {
19
+ executionId: processor.execution.id,
20
+ },
21
+ transaction: processor.transaction,
22
+ });
23
+ }