@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,12 @@
1
+ import { Processor } from '../../..';
2
+ import ManualInstruction from '..';
3
+
4
+ import create from './create';
5
+ import update from './update';
6
+
7
+ export type FormHandler = (this: ManualInstruction, instance, formConfig, processor: Processor) => Promise<void>;
8
+
9
+ export default function ({ formTypes }) {
10
+ formTypes.register('create', create);
11
+ formTypes.register('update', update);
12
+ }
@@ -0,0 +1,23 @@
1
+ import { Processor } from '../../..';
2
+ import ManualInstruction from '..';
3
+
4
+ export default async function (this: ManualInstruction, instance, { collection, filter = {} }, processor: Processor) {
5
+ const repo = this.plugin.db.getRepository(collection);
6
+ if (!repo) {
7
+ throw new Error(`collection ${collection} for update data on manual node not found`);
8
+ }
9
+
10
+ const { _, ...form } = instance.result;
11
+ const [values] = Object.values(form);
12
+ await repo.update({
13
+ filter: processor.getParsedValue(filter),
14
+ values: {
15
+ ...((values as { [key: string]: any }) ?? {}),
16
+ updatedBy: instance.userId,
17
+ },
18
+ context: {
19
+ executionId: processor.execution.id,
20
+ },
21
+ transaction: processor.transaction,
22
+ });
23
+ }
@@ -0,0 +1,184 @@
1
+ import actions from '@nocobase/actions';
2
+ import { HandlerType } from '@nocobase/resourcer';
3
+ import { Registry } from '@nocobase/utils';
4
+
5
+ import Plugin from '../..';
6
+ import { JOB_STATUS } from '../../constants';
7
+ import { Instruction } from '..';
8
+ import jobsCollection from './collecions/jobs';
9
+ import usersCollection from './collecions/users';
10
+ import usersJobsCollection from './collecions/users_jobs';
11
+ import { submit } from './actions';
12
+ import initFormTypes, { FormHandler } from './forms';
13
+
14
+ type FormType = {
15
+ type: 'custom' | 'create' | 'update';
16
+ actions: number[];
17
+ options: {
18
+ [key: string]: any;
19
+ };
20
+ };
21
+
22
+ export interface ManualConfig {
23
+ schema: { [key: string]: any };
24
+ forms: { [key: string]: FormType };
25
+ assignees?: (number | string)[];
26
+ mode?: number;
27
+ }
28
+
29
+ const MULTIPLE_ASSIGNED_MODE = {
30
+ SINGLE: Symbol('single'),
31
+ ALL: Symbol('all'),
32
+ ANY: Symbol('any'),
33
+ ALL_PERCENTAGE: Symbol('all percentage'),
34
+ ANY_PERCENTAGE: Symbol('any percentage'),
35
+ };
36
+
37
+ const Modes = {
38
+ [MULTIPLE_ASSIGNED_MODE.SINGLE]: {
39
+ getStatus(distribution, assignees) {
40
+ const done = distribution.find((item) => item.status !== JOB_STATUS.PENDING && item.count > 0);
41
+ return done ? done.status : null;
42
+ },
43
+ },
44
+ [MULTIPLE_ASSIGNED_MODE.ALL]: {
45
+ getStatus(distribution, assignees) {
46
+ const resolved = distribution.find((item) => item.status === JOB_STATUS.RESOLVED);
47
+ if (resolved && resolved.count === assignees.length) {
48
+ return JOB_STATUS.RESOLVED;
49
+ }
50
+ const rejected = distribution.find((item) => item.status < JOB_STATUS.PENDING);
51
+ if (rejected && rejected.count) {
52
+ return rejected.status;
53
+ }
54
+
55
+ return null;
56
+ },
57
+ },
58
+ [MULTIPLE_ASSIGNED_MODE.ANY]: {
59
+ getStatus(distribution, assignees) {
60
+ const resolved = distribution.find((item) => item.status === JOB_STATUS.RESOLVED);
61
+ if (resolved && resolved.count) {
62
+ return JOB_STATUS.RESOLVED;
63
+ }
64
+ const rejectedCount = distribution.reduce(
65
+ (count, item) => (item.status < JOB_STATUS.PENDING ? count + item.count : count),
66
+ 0,
67
+ );
68
+ // NOTE: all failures are considered as rejected for now
69
+ if (rejectedCount === assignees.length) {
70
+ return JOB_STATUS.REJECTED;
71
+ }
72
+
73
+ return null;
74
+ },
75
+ },
76
+ };
77
+
78
+ function getMode(mode) {
79
+ switch (true) {
80
+ case mode === 1:
81
+ return Modes[MULTIPLE_ASSIGNED_MODE.ALL];
82
+ case mode === -1:
83
+ return Modes[MULTIPLE_ASSIGNED_MODE.ANY];
84
+ case mode > 0:
85
+ return Modes[MULTIPLE_ASSIGNED_MODE.ALL_PERCENTAGE];
86
+ case mode < 0:
87
+ return Modes[MULTIPLE_ASSIGNED_MODE.ANY_PERCENTAGE];
88
+ default:
89
+ return Modes[MULTIPLE_ASSIGNED_MODE.SINGLE];
90
+ }
91
+ }
92
+
93
+ export default class implements Instruction {
94
+ formTypes = new Registry<FormHandler>();
95
+
96
+ constructor(protected plugin: Plugin) {
97
+ plugin.db.collection(usersJobsCollection);
98
+ plugin.db.extendCollection(usersCollection);
99
+ plugin.db.extendCollection(jobsCollection);
100
+
101
+ plugin.app.resource({
102
+ name: 'users_jobs',
103
+ actions: {
104
+ list: {
105
+ filter: {
106
+ $or: [
107
+ {
108
+ 'workflow.enabled': true,
109
+ },
110
+ {
111
+ 'workflow.enabled': false,
112
+ status: {
113
+ $ne: JOB_STATUS.PENDING,
114
+ },
115
+ },
116
+ ],
117
+ },
118
+ handler: actions.list as HandlerType,
119
+ },
120
+ submit,
121
+ },
122
+ });
123
+
124
+ initFormTypes(this);
125
+ }
126
+
127
+ async run(node, prevJob, processor) {
128
+ const { mode, ...config } = node.config as ManualConfig;
129
+ const assignees = [...new Set(processor.getParsedValue(config.assignees) || [])];
130
+
131
+ const job = await processor.saveJob({
132
+ status: JOB_STATUS.PENDING,
133
+ result: mode ? [] : null,
134
+ nodeId: node.id,
135
+ upstreamId: prevJob?.id ?? null,
136
+ });
137
+
138
+ // NOTE: batch create users jobs
139
+ const UserJobModel = processor.options.plugin.db.getModel('users_jobs');
140
+ await UserJobModel.bulkCreate(
141
+ assignees.map((userId) => ({
142
+ userId,
143
+ jobId: job.id,
144
+ nodeId: node.id,
145
+ executionId: job.executionId,
146
+ workflowId: node.workflowId,
147
+ status: JOB_STATUS.PENDING,
148
+ })),
149
+ {
150
+ transaction: processor.transaction,
151
+ },
152
+ );
153
+
154
+ return job;
155
+ }
156
+
157
+ async resume(node, job, processor) {
158
+ // NOTE: check all users jobs related if all done then continue as parallel
159
+ const { assignees = [], mode } = node.config as ManualConfig;
160
+
161
+ const UserJobModel = processor.options.plugin.db.getModel('users_jobs');
162
+ const distribution = await UserJobModel.count({
163
+ where: {
164
+ jobId: job.id,
165
+ },
166
+ group: ['status'],
167
+ transaction: processor.transaction,
168
+ });
169
+
170
+ const submitted = distribution.reduce(
171
+ (count, item) => (item.status !== JOB_STATUS.PENDING ? count + item.count : count),
172
+ 0,
173
+ );
174
+ const status = job.status || (getMode(mode).getStatus(distribution, assignees) ?? JOB_STATUS.PENDING);
175
+ const result = mode ? (submitted || 0) / assignees.length : job.latestUserJob?.result ?? job.result;
176
+ processor.logger.debug(`manual resume job and next status: ${status}`);
177
+ job.set({
178
+ status,
179
+ result,
180
+ });
181
+
182
+ return job;
183
+ }
184
+ }
@@ -0,0 +1,121 @@
1
+ import Processor from '../Processor';
2
+ import { JOB_STATUS } from '../constants';
3
+ import type { FlowNodeModel, JobModel } from '../types';
4
+
5
+ export const PARALLEL_MODE = {
6
+ ALL: 'all',
7
+ ANY: 'any',
8
+ RACE: 'race',
9
+ } as const;
10
+
11
+ const Modes = {
12
+ [PARALLEL_MODE.ALL]: {
13
+ next(previous) {
14
+ return previous.status >= JOB_STATUS.PENDING;
15
+ },
16
+ getStatus(result) {
17
+ const failedStatus = result.find((status) => status != null && status < JOB_STATUS.PENDING);
18
+ if (typeof failedStatus !== 'undefined') {
19
+ return failedStatus;
20
+ }
21
+ if (result.every((status) => status != null && status === JOB_STATUS.RESOLVED)) {
22
+ return JOB_STATUS.RESOLVED;
23
+ }
24
+ return JOB_STATUS.PENDING;
25
+ },
26
+ },
27
+ [PARALLEL_MODE.ANY]: {
28
+ next(previous) {
29
+ return previous.status <= JOB_STATUS.PENDING;
30
+ },
31
+ getStatus(result) {
32
+ if (result.some((status) => status != null && status === JOB_STATUS.RESOLVED)) {
33
+ return JOB_STATUS.RESOLVED;
34
+ }
35
+ if (result.some((status) => (status != null ? status === JOB_STATUS.PENDING : true))) {
36
+ return JOB_STATUS.PENDING;
37
+ }
38
+ return JOB_STATUS.FAILED;
39
+ },
40
+ },
41
+ [PARALLEL_MODE.RACE]: {
42
+ next(previous) {
43
+ return previous.status === JOB_STATUS.PENDING;
44
+ },
45
+ getStatus(result) {
46
+ if (result.some((status) => status != null && status === JOB_STATUS.RESOLVED)) {
47
+ return JOB_STATUS.RESOLVED;
48
+ }
49
+ const failedStatus = result.find((status) => status != null && status < JOB_STATUS.PENDING);
50
+ if (typeof failedStatus !== 'undefined') {
51
+ return failedStatus;
52
+ }
53
+ return JOB_STATUS.PENDING;
54
+ },
55
+ },
56
+ };
57
+
58
+ export default {
59
+ async run(node: FlowNodeModel, prevJob: JobModel, processor: Processor) {
60
+ const branches = processor.getBranches(node);
61
+
62
+ const job = await processor.saveJob({
63
+ status: JOB_STATUS.PENDING,
64
+ result: Array(branches.length).fill(null),
65
+ nodeId: node.id,
66
+ upstreamId: prevJob?.id ?? null,
67
+ });
68
+
69
+ // NOTE:
70
+ // use `reduce` but not `Promise.all` here to avoid racing manupulating db.
71
+ // for users, this is almost equivalent to `Promise.all`,
72
+ // because of the delay is not significant sensible.
73
+ // another benifit of this is, it could handle sequenced branches in future.
74
+ const { mode = PARALLEL_MODE.ALL } = node.config;
75
+ await branches.reduce(
76
+ (promise: Promise<any>, branch, i) =>
77
+ promise.then(async (previous) => {
78
+ if (i && !Modes[mode].next(previous)) {
79
+ return previous;
80
+ }
81
+ await processor.run(branch, job);
82
+
83
+ // find last job of the branch
84
+ return processor.findBranchLastJob(branch);
85
+ }),
86
+ Promise.resolve(),
87
+ );
88
+
89
+ return null;
90
+ },
91
+
92
+ async resume(node: FlowNodeModel, branchJob, processor: Processor) {
93
+ const job = processor.findBranchParentJob(branchJob, node) as JobModel;
94
+
95
+ const { result, status } = job;
96
+ // if parallel has been done (resolved / rejected), do not care newly executed branch jobs.
97
+ if (status !== JOB_STATUS.PENDING) {
98
+ return processor.exit();
99
+ }
100
+
101
+ // find the index of the node which start the branch
102
+ const jobNode = processor.nodesMap.get(branchJob.nodeId) as FlowNodeModel;
103
+ const branchStartNode = processor.findBranchStartNode(jobNode, node) as FlowNodeModel;
104
+ const branches = processor.getBranches(node);
105
+ const branchIndex = branches.indexOf(branchStartNode);
106
+ const { mode = PARALLEL_MODE.ALL } = node.config || {};
107
+
108
+ const newResult = [...result.slice(0, branchIndex), branchJob.status, ...result.slice(branchIndex + 1)];
109
+ job.set({
110
+ result: newResult,
111
+ status: Modes[mode].getStatus(newResult),
112
+ });
113
+
114
+ if (job.status === JOB_STATUS.PENDING) {
115
+ await job.save({ transaction: processor.transaction });
116
+ return processor.exit();
117
+ }
118
+
119
+ return job;
120
+ },
121
+ };
@@ -0,0 +1,42 @@
1
+ import Processor from '../Processor';
2
+ import { JOB_STATUS } from '../constants';
3
+ import { toJSON } from '../utils';
4
+ import type { FlowNodeModel } from '../types';
5
+
6
+ export default {
7
+ async run(node: FlowNodeModel, input, processor: Processor) {
8
+ const { collection, multiple, params = {}, failOnEmpty = false } = node.config;
9
+
10
+ const repo = (<typeof FlowNodeModel>node.constructor).database.getRepository(collection);
11
+ const options = processor.getParsedValue(params, node);
12
+ const appends = options.appends
13
+ ? Array.from(
14
+ options.appends.reduce((set, field) => {
15
+ set.add(field.split('.')[0]);
16
+ set.add(field);
17
+ return set;
18
+ }, new Set()),
19
+ )
20
+ : options.appends;
21
+ const result = await (multiple ? repo.find : repo.findOne).call(repo, {
22
+ ...options,
23
+ appends: appends,
24
+ transaction: processor.transaction,
25
+ });
26
+
27
+ if (failOnEmpty && (multiple ? !result.length : !result)) {
28
+ return {
29
+ result,
30
+ status: JOB_STATUS.FAILED,
31
+ };
32
+ }
33
+
34
+ // NOTE: `toJSON()` to avoid getting undefined value from Proxied model instance (#380)
35
+ // e.g. Object.prototype.hasOwnProperty.call(result, 'id') // false
36
+ // so the properties can not be get by json-templates(object-path)
37
+ return {
38
+ result: toJSON(result),
39
+ status: JOB_STATUS.RESOLVED,
40
+ };
41
+ },
42
+ };
@@ -0,0 +1,88 @@
1
+ import axios, { AxiosRequestConfig } from 'axios';
2
+
3
+ import { Instruction } from './index';
4
+ import { JOB_STATUS } from '../constants';
5
+ import Processor from '../Processor';
6
+ import type { FlowNodeModel } from '../types';
7
+
8
+ export interface Header {
9
+ name: string;
10
+ value: string;
11
+ }
12
+
13
+ export type RequestConfig = Pick<AxiosRequestConfig, 'url' | 'method' | 'params' | 'data' | 'timeout'> & {
14
+ headers: Array<Header>;
15
+ ignoreFail: boolean;
16
+ };
17
+
18
+ async function request(config) {
19
+ // default headers
20
+ const { url, method = 'POST', data, timeout = 5000 } = config;
21
+ const headers = (config.headers ?? []).reduce((result, header) => {
22
+ if (header.name.toLowerCase() === 'content-type') {
23
+ return result;
24
+ }
25
+ return Object.assign(result, { [header.name]: header.value });
26
+ }, {});
27
+ const params = (config.params ?? []).reduce(
28
+ (result, param) => Object.assign(result, { [param.name]: param.value }),
29
+ {},
30
+ );
31
+
32
+ // TODO(feat): only support JSON type for now, should support others in future
33
+ headers['Content-Type'] = 'application/json';
34
+
35
+ return axios.request({
36
+ url,
37
+ method,
38
+ headers,
39
+ params,
40
+ data,
41
+ timeout,
42
+ });
43
+ }
44
+
45
+ export default class implements Instruction {
46
+ constructor(public plugin) {}
47
+
48
+ async run(node: FlowNodeModel, prevJob, processor: Processor) {
49
+ const job = await processor.saveJob({
50
+ status: JOB_STATUS.PENDING,
51
+ nodeId: node.id,
52
+ upstreamId: prevJob?.id ?? null,
53
+ });
54
+
55
+ const config = processor.getParsedValue(node.config, node) as RequestConfig;
56
+
57
+ // eslint-disable-next-line promise/catch-or-return
58
+ request(config)
59
+ .then((response) => {
60
+ job.set({
61
+ status: JOB_STATUS.RESOLVED,
62
+ result: response.data,
63
+ });
64
+ })
65
+ .catch((error) => {
66
+ job.set({
67
+ status: JOB_STATUS.FAILED,
68
+ result: error.isAxiosError ? error.toJSON() : error.message,
69
+ });
70
+ })
71
+ .finally(() => {
72
+ processor.logger.info(`request (#${node.id}) response received, status: ${job.get('status')}`);
73
+ this.plugin.resume(job);
74
+ });
75
+
76
+ processor.logger.info(`request (#${node.id}) sent to "${config.url}", waiting for response...`);
77
+
78
+ return processor.exit();
79
+ }
80
+
81
+ async resume(node: FlowNodeModel, job, processor: Processor) {
82
+ const { ignoreFail } = node.config as RequestConfig;
83
+ if (ignoreFail) {
84
+ job.set('status', JOB_STATUS.RESOLVED);
85
+ }
86
+ return job;
87
+ }
88
+ }
@@ -0,0 +1,25 @@
1
+ import { Processor, JOB_STATUS } from '..';
2
+ import type { FlowNodeModel } from '../types';
3
+
4
+ export default {
5
+ async run(node: FlowNodeModel, input, processor: Processor) {
6
+ const { sequelize } = (<typeof FlowNodeModel>node.constructor).database;
7
+ const sql = processor.getParsedValue(node.config.sql ?? '', node).trim();
8
+ if (!sql) {
9
+ return {
10
+ status: JOB_STATUS.RESOLVED,
11
+ }
12
+ }
13
+
14
+ const result = await sequelize.query(sql, {
15
+ transaction: processor.transaction,
16
+ // plain: true,
17
+ // model: db.getCollection(node.config.collection).model
18
+ });
19
+
20
+ return {
21
+ result,
22
+ status: JOB_STATUS.RESOLVED,
23
+ };
24
+ },
25
+ };
@@ -0,0 +1,24 @@
1
+ import Processor from '../Processor';
2
+ import { JOB_STATUS } from '../constants';
3
+ import type { FlowNodeModel } from '../types';
4
+
5
+ export default {
6
+ async run(node: FlowNodeModel, input, processor: Processor) {
7
+ const { collection, params = {} } = node.config;
8
+
9
+ const repo = (<typeof FlowNodeModel>node.constructor).database.getRepository(collection);
10
+ const options = processor.getParsedValue(params, node);
11
+ const result = await repo.update({
12
+ ...options,
13
+ context: {
14
+ executionId: processor.execution.id,
15
+ },
16
+ transaction: processor.transaction,
17
+ });
18
+
19
+ return {
20
+ result: result.length ?? result,
21
+ status: JOB_STATUS.RESOLVED,
22
+ };
23
+ },
24
+ };
@@ -0,0 +1,64 @@
1
+ import { Migration } from '@nocobase/server';
2
+
3
+ const VTypes = {
4
+ constant(operand) {
5
+ return operand.value;
6
+ },
7
+ $jobsMapByNodeId({ options }) {
8
+ const paths = [options.nodeId, options.path].filter(Boolean);
9
+ return paths ? `{{$jobsMapByNodeId.${paths.join('.')}}}` : null;
10
+ },
11
+ $context({ options }) {
12
+ return `{{$context.${options.path}}}`;
13
+ },
14
+ };
15
+
16
+ function migrateConfig(config) {
17
+ if (Array.isArray(config)) {
18
+ return config.map((item) => migrateConfig(item));
19
+ }
20
+ if (typeof config !== 'object') {
21
+ return config;
22
+ }
23
+ if (!config) {
24
+ return config;
25
+ }
26
+ if (config.type && VTypes[config.type] && (config.options || config.value)) {
27
+ return VTypes[config.type](config);
28
+ }
29
+ return Object.keys(config).reduce((memo, key) => ({ ...memo, [key]: migrateConfig(config[key]) }), {});
30
+ }
31
+
32
+ export default class extends Migration {
33
+ async up() {
34
+ const match = await this.app.version.satisfies('<=0.8.0-alpha.13');
35
+ if (!match) {
36
+ return;
37
+ }
38
+ const NodeRepo = this.context.db.getRepository('flow_nodes');
39
+ await this.context.db.sequelize.transaction(async (transaction) => {
40
+ const nodes = await NodeRepo.find({
41
+ filter: {
42
+ type: {
43
+ $or: ['calculation', 'condition'],
44
+ },
45
+ },
46
+ transaction,
47
+ });
48
+ console.log('%d nodes need to be migrated.', nodes.length);
49
+
50
+ await nodes.reduce((promise, node) => {
51
+ return node.update(
52
+ {
53
+ config: migrateConfig(node.config),
54
+ },
55
+ {
56
+ transaction,
57
+ },
58
+ );
59
+ }, Promise.resolve());
60
+ });
61
+ }
62
+
63
+ async down() {}
64
+ }
@@ -0,0 +1,76 @@
1
+ import { Migration } from '@nocobase/server';
2
+
3
+ const EJS_RE = /"?<%=\s*(ctx|node)([\w\.\[\]-]+)\s*.*%>"?/;
4
+
5
+ function migrateData(input) {
6
+ if (typeof input !== 'string') {
7
+ return input;
8
+ }
9
+ if (!input) {
10
+ return null;
11
+ }
12
+ const typeMap = {
13
+ ctx: '$context',
14
+ node: '$jobsMapByNodeId',
15
+ };
16
+ return input.replace(EJS_RE, (_, type, path) => {
17
+ if (type === 'ctx') {
18
+ return `"{{$context${path}}}"`;
19
+ }
20
+ if (type === 'node') {
21
+ return `"{{$jobsMapByNodeId${path.replace('[', '.').replace(']', '.').replace(/\.$/, '')}}}"`;
22
+ }
23
+ return _;
24
+ });
25
+ }
26
+
27
+ export default class extends Migration {
28
+ async up() {
29
+ const match = await this.app.version.satisfies('<0.9.0-alpha.3');
30
+ if (!match) {
31
+ return;
32
+ }
33
+
34
+ const NodeRepo = this.context.db.getRepository('flow_nodes');
35
+
36
+ await this.context.db.sequelize.transaction(async (transaction) => {
37
+ const nodes = await NodeRepo.find({
38
+ filter: {
39
+ type: 'request',
40
+ },
41
+ transaction,
42
+ });
43
+ console.log('%d nodes need to be migrated.', nodes.length);
44
+
45
+ await nodes.reduce(
46
+ (promise, node) =>
47
+ promise.then(async () => {
48
+ if (typeof node.config.data !== 'string') {
49
+ return;
50
+ }
51
+ let data = migrateData(node.config.data);
52
+ try {
53
+ data = JSON.parse(node.config.data);
54
+ return node.update(
55
+ {
56
+ config: {
57
+ ...node.config,
58
+ data,
59
+ },
60
+ },
61
+ {
62
+ transaction,
63
+ },
64
+ );
65
+ } catch (error) {
66
+ console.error(
67
+ `flow_node #${node.id} config migrating failed! you should migrate its format from ejs to json-templates manually in your db.`,
68
+ );
69
+ }
70
+ }),
71
+ Promise.resolve(),
72
+ );
73
+ });
74
+ }
75
+ async down() {}
76
+ }