@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.
- package/client.d.ts +2 -3
- package/client.js +1 -30
- package/lib/client/AddButton.js +13 -11
- package/lib/client/Branch.js +10 -8
- package/lib/client/CanvasContent.js +12 -10
- package/lib/client/ExecutionCanvas.js +37 -33
- package/lib/client/ExecutionPage.js +4 -9
- package/lib/client/WorkflowCanvas.js +18 -15
- package/lib/client/WorkflowPage.js +4 -9
- package/lib/client/WorkflowProvider.js +1 -40
- package/lib/client/components/CollectionBlockInitializer.js +3 -3
- package/lib/client/components/CollectionFieldset.d.ts +1 -1
- package/lib/client/components/CollectionFieldset.js +15 -16
- package/lib/client/components/Duration.js +5 -5
- package/lib/client/components/DynamicExpression.d.ts +3 -3
- package/lib/client/components/FieldsSelect.d.ts +1 -1
- package/lib/client/components/FieldsSelect.js +10 -7
- package/lib/client/components/NodeDescription.js +45 -31
- package/lib/client/components/RadioWithTooltip.js +13 -20
- package/lib/client/components/ValueBlock.js +14 -21
- package/lib/client/components/renderEngineReference.js +1 -8
- package/lib/client/index.d.ts +12 -4
- package/lib/client/index.js +78 -15
- package/lib/client/locale/zh-CN.d.ts +5 -1
- package/lib/client/locale/zh-CN.js +6 -2
- package/lib/client/nodes/aggregate.d.ts +8 -3
- package/lib/client/nodes/aggregate.js +5 -4
- package/lib/client/nodes/calculation.d.ts +6 -4
- package/lib/client/nodes/calculation.js +22 -28
- package/lib/client/nodes/condition.d.ts +2 -10
- package/lib/client/nodes/condition.js +19 -37
- package/lib/client/nodes/create.d.ts +5 -6
- package/lib/client/nodes/create.js +1 -3
- package/lib/client/nodes/destroy.d.ts +1 -1
- package/lib/client/nodes/index.d.ts +2 -3
- package/lib/client/nodes/index.js +95 -102
- package/lib/client/nodes/loop.d.ts +1 -1
- package/lib/client/nodes/loop.js +46 -54
- package/lib/client/nodes/manual/FormBlockInitializer.js +6 -5
- package/lib/client/nodes/manual/ModeConfig.js +23 -30
- package/lib/client/nodes/manual/SchemaConfig.d.ts +4 -5
- package/lib/client/nodes/manual/SchemaConfig.js +180 -25
- package/lib/client/nodes/manual/WorkflowTodo.js +95 -110
- package/lib/client/nodes/manual/WorkflowTodoBlockInitializer.d.ts +2 -5
- package/lib/client/nodes/manual/WorkflowTodoBlockInitializer.js +6 -5
- package/lib/client/nodes/manual/forms/create.js +8 -1
- package/lib/client/nodes/manual/forms/custom.js +22 -22
- package/lib/client/nodes/manual/forms/update.js +8 -1
- package/lib/client/nodes/manual/index.d.ts +6 -1
- package/lib/client/nodes/manual/index.js +5 -4
- package/lib/client/nodes/parallel.js +23 -20
- package/lib/client/nodes/query.d.ts +3 -5
- package/lib/client/nodes/query.js +1 -3
- package/lib/client/nodes/request.d.ts +2 -2
- package/lib/client/nodes/request.js +7 -7
- package/lib/client/nodes/sql.d.ts +26 -0
- package/lib/client/{triggers/schedule/DateFieldsSelect.js → nodes/sql.js} +37 -46
- package/lib/client/nodes/update.d.ts +2 -2
- package/lib/client/nodes/update.js +1 -1
- package/lib/client/schemas/collection.d.ts +3 -4
- package/lib/client/schemas/collection.js +11 -17
- package/lib/client/style.d.ts +18 -13
- package/lib/client/style.js +315 -292
- package/lib/client/triggers/collection.d.ts +13 -13
- package/lib/client/triggers/collection.js +5 -1
- package/lib/client/triggers/index.d.ts +3 -4
- package/lib/client/triggers/index.js +51 -53
- package/lib/client/triggers/schedule/EndsByField.js +11 -11
- package/lib/client/triggers/schedule/OnField.js +45 -33
- package/lib/client/triggers/schedule/RepeatField.js +4 -4
- package/lib/client/triggers/schedule/ScheduleConfig.js +24 -31
- package/lib/client/triggers/schedule/index.d.ts +1 -1
- package/lib/client/triggers/schedule/index.js +32 -20
- package/lib/client/variable.d.ts +31 -13
- package/lib/client/variable.js +44 -29
- package/lib/server/Plugin.d.ts +3 -6
- package/lib/server/Plugin.js +15 -12
- package/lib/server/Processor.d.ts +3 -5
- package/lib/server/Processor.js +2 -2
- package/lib/server/actions/nodes.js +7 -7
- package/lib/server/fields/expression-field.d.ts +1 -2
- package/lib/server/fields/expression-field.js +1 -8
- package/lib/server/functions/index.d.ts +2 -3
- package/lib/server/index.d.ts +1 -0
- package/lib/server/index.js +12 -0
- package/lib/server/instructions/aggregate.d.ts +1 -1
- package/lib/server/instructions/aggregate.js +5 -5
- package/lib/server/instructions/condition.d.ts +2 -1
- package/lib/server/instructions/create.d.ts +2 -2
- package/lib/server/instructions/create.js +13 -13
- package/lib/server/instructions/delay.d.ts +3 -3
- package/lib/server/instructions/delay.js +66 -64
- package/lib/server/instructions/destroy.d.ts +1 -1
- package/lib/server/instructions/index.d.ts +5 -5
- package/lib/server/instructions/index.js +1 -1
- package/lib/server/instructions/loop.d.ts +1 -2
- package/lib/server/instructions/manual/actions.js +19 -7
- package/lib/server/instructions/manual/forms/create.js +7 -1
- package/lib/server/instructions/manual/forms/index.d.ts +1 -1
- package/lib/server/instructions/manual/forms/update.js +7 -1
- package/lib/server/instructions/manual/index.d.ts +1 -1
- package/lib/server/instructions/parallel.d.ts +1 -2
- package/lib/server/instructions/query.d.ts +1 -1
- package/lib/server/instructions/query.js +8 -1
- package/lib/server/instructions/request.d.ts +3 -3
- package/lib/server/instructions/request.js +5 -2
- package/lib/server/instructions/sql.d.ts +12 -0
- package/lib/server/instructions/sql.js +34 -0
- package/lib/server/instructions/update.d.ts +1 -1
- package/lib/server/migrations/20230221071831-calculation-expression.js +1 -1
- package/lib/server/migrations/20230221121203-condition-calculation.js +1 -1
- package/lib/server/migrations/20230221162902-jsonb-to-json.js +7 -7
- package/lib/server/migrations/20230411034722-manual-multi-form.js +1 -8
- package/lib/server/migrations/20230710115902-manual-action-values.d.ts +4 -0
- package/lib/server/migrations/20230710115902-manual-action-values.js +97 -0
- package/lib/server/triggers/collection.d.ts +1 -1
- package/lib/server/triggers/collection.js +15 -13
- package/lib/server/triggers/index.d.ts +1 -1
- package/lib/server/triggers/schedule.d.ts +1 -1
- package/lib/server/triggers/schedule.js +18 -18
- package/lib/server/{models → types}/Execution.d.ts +2 -3
- package/lib/server/{models → types}/FlowNode.d.ts +1 -2
- package/lib/server/{models → types}/Job.d.ts +1 -2
- package/lib/server/{models → types}/Workflow.d.ts +1 -2
- package/lib/server/types/index.d.ts +4 -0
- package/lib/server/types/index.js +5 -0
- package/lib/server/utils.d.ts +2 -0
- package/lib/server/utils.js +21 -0
- package/package.json +39 -18
- package/server.d.ts +2 -3
- package/server.js +1 -30
- package/src/client/AddButton.tsx +111 -0
- package/src/client/Branch.tsx +37 -0
- package/src/client/CanvasContent.tsx +25 -0
- package/src/client/ExecutionCanvas.tsx +166 -0
- package/src/client/ExecutionLink.tsx +16 -0
- package/src/client/ExecutionPage.tsx +45 -0
- package/src/client/ExecutionResourceProvider.tsx +21 -0
- package/src/client/FlowContext.ts +7 -0
- package/src/client/WorkflowCanvas.tsx +221 -0
- package/src/client/WorkflowLink.tsx +16 -0
- package/src/client/WorkflowPage.tsx +52 -0
- package/src/client/WorkflowProvider.tsx +84 -0
- package/src/client/components/CollectionBlockInitializer.tsx +71 -0
- package/src/client/components/CollectionFieldset.tsx +160 -0
- package/src/client/components/Duration.tsx +45 -0
- package/src/client/components/DynamicExpression.tsx +53 -0
- package/src/client/components/FieldsSelect.tsx +32 -0
- package/src/client/components/FilterDynamicComponent.tsx +15 -0
- package/src/client/components/NodeDescription.tsx +51 -0
- package/src/client/components/NullRender.tsx +3 -0
- package/src/client/components/OpenDrawer.tsx +24 -0
- package/src/client/components/RadioWithTooltip.tsx +38 -0
- package/src/client/components/ValueBlock.tsx +67 -0
- package/src/client/components/renderEngineReference.tsx +30 -0
- package/src/client/constants.tsx +91 -0
- package/src/client/index.tsx +51 -0
- package/src/client/interfaces/expression.tsx +25 -0
- package/src/client/locale/en-US.ts +136 -0
- package/src/client/locale/es-ES.ts +129 -0
- package/src/client/locale/index.ts +18 -0
- package/src/client/locale/ja-JP.ts +90 -0
- package/src/client/locale/pt-BR.ts +136 -0
- package/src/client/locale/ru-RU.ts +90 -0
- package/src/client/locale/tr-TR.ts +90 -0
- package/src/client/locale/zh-CN.ts +248 -0
- package/src/client/nodes/aggregate.tsx +327 -0
- package/src/client/nodes/calculation.tsx +216 -0
- package/src/client/nodes/condition.tsx +463 -0
- package/src/client/nodes/create.tsx +85 -0
- package/src/client/nodes/delay.tsx +37 -0
- package/src/client/nodes/destroy.tsx +34 -0
- package/src/client/nodes/index.tsx +485 -0
- package/src/client/nodes/loop.tsx +144 -0
- package/src/client/nodes/manual/AssigneesSelect.tsx +33 -0
- package/src/client/nodes/manual/DetailsBlockProvider.tsx +80 -0
- package/src/client/nodes/manual/FormBlockInitializer.tsx +69 -0
- package/src/client/nodes/manual/FormBlockProvider.tsx +75 -0
- package/src/client/nodes/manual/ModeConfig.tsx +84 -0
- package/src/client/nodes/manual/SchemaConfig.tsx +509 -0
- package/src/client/nodes/manual/WorkflowTodo.tsx +607 -0
- package/src/client/nodes/manual/WorkflowTodoBlockInitializer.tsx +28 -0
- package/src/client/nodes/manual/forms/create.tsx +92 -0
- package/src/client/nodes/manual/forms/custom.tsx +392 -0
- package/src/client/nodes/manual/forms/update.tsx +134 -0
- package/src/client/nodes/manual/index.tsx +162 -0
- package/src/client/nodes/manual/utils.ts +28 -0
- package/src/client/nodes/parallel.tsx +138 -0
- package/src/client/nodes/query.tsx +88 -0
- package/src/client/nodes/request.tsx +185 -0
- package/src/client/nodes/sql.tsx +37 -0
- package/src/client/nodes/update.tsx +99 -0
- package/src/client/schemas/collection.ts +75 -0
- package/src/client/schemas/executions.tsx +169 -0
- package/src/client/schemas/workflows.ts +364 -0
- package/src/client/style.tsx +347 -0
- package/src/client/triggers/collection.tsx +190 -0
- package/src/client/triggers/index.tsx +311 -0
- package/src/client/triggers/schedule/EndsByField.tsx +40 -0
- package/src/client/triggers/schedule/OnField.tsx +64 -0
- package/src/client/triggers/schedule/RepeatField.tsx +116 -0
- package/src/client/triggers/schedule/ScheduleConfig.tsx +227 -0
- package/src/client/triggers/schedule/constants.ts +4 -0
- package/src/client/triggers/schedule/index.tsx +78 -0
- package/src/client/triggers/schedule/locale/Cron.zh-CN.ts +79 -0
- package/src/client/utils.ts +36 -0
- package/src/client/variable.tsx +318 -0
- package/src/index.ts +1 -0
- package/src/server/Plugin.ts +355 -0
- package/src/server/Processor.ts +354 -0
- package/src/server/__tests__/Plugin.test.ts +398 -0
- package/src/server/__tests__/Processor.test.ts +474 -0
- package/src/server/__tests__/actions/workflows.test.ts +419 -0
- package/src/server/__tests__/collections/categories.ts +27 -0
- package/src/server/__tests__/collections/comments.ts +24 -0
- package/src/server/__tests__/collections/posts.ts +42 -0
- package/src/server/__tests__/collections/replies.ts +9 -0
- package/src/server/__tests__/collections/tags.ts +15 -0
- package/src/server/__tests__/index.ts +89 -0
- package/src/server/__tests__/instructions/aggregate.test.ts +294 -0
- package/src/server/__tests__/instructions/calculation.test.ts +265 -0
- package/src/server/__tests__/instructions/condition.test.ts +335 -0
- package/src/server/__tests__/instructions/create.test.ts +129 -0
- package/src/server/__tests__/instructions/delay.test.ts +182 -0
- package/src/server/__tests__/instructions/destroy.test.ts +58 -0
- package/src/server/__tests__/instructions/loop.test.ts +331 -0
- package/src/server/__tests__/instructions/manual.test.ts +1173 -0
- package/src/server/__tests__/instructions/parallel.test.ts +445 -0
- package/src/server/__tests__/instructions/query.test.ts +359 -0
- package/src/server/__tests__/instructions/request.test.ts +247 -0
- package/src/server/__tests__/instructions/sql.test.ts +162 -0
- package/src/server/__tests__/instructions/update.test.ts +189 -0
- package/src/server/__tests__/triggers/collection.test.ts +333 -0
- package/src/server/__tests__/triggers/schedule.test.ts +369 -0
- package/src/server/actions/index.ts +25 -0
- package/src/server/actions/nodes.ts +214 -0
- package/src/server/actions/workflows.ts +178 -0
- package/src/server/collections/executions.ts +35 -0
- package/src/server/collections/flow_nodes.ts +54 -0
- package/src/server/collections/jobs.ts +31 -0
- package/src/server/collections/workflows.ts +88 -0
- package/src/server/constants.ts +26 -0
- package/src/server/fields/expression-field.ts +11 -0
- package/src/server/fields/index.ts +7 -0
- package/src/server/functions/index.ts +16 -0
- package/src/server/index.ts +6 -0
- package/src/server/instructions/aggregate.ts +42 -0
- package/src/server/instructions/calculation.ts +41 -0
- package/src/server/instructions/condition.ts +172 -0
- package/src/server/instructions/create.ts +39 -0
- package/src/server/instructions/delay.ts +105 -0
- package/src/server/instructions/destroy.ts +23 -0
- package/src/server/instructions/index.ts +64 -0
- package/src/server/instructions/loop.ts +99 -0
- package/src/server/instructions/manual/actions.ts +91 -0
- package/src/server/instructions/manual/collecions/jobs.ts +17 -0
- package/src/server/instructions/manual/collecions/users.ts +15 -0
- package/src/server/instructions/manual/collecions/users_jobs.ts +50 -0
- package/src/server/instructions/manual/forms/create.ts +23 -0
- package/src/server/instructions/manual/forms/index.ts +12 -0
- package/src/server/instructions/manual/forms/update.ts +23 -0
- package/src/server/instructions/manual/index.ts +184 -0
- package/src/server/instructions/parallel.ts +121 -0
- package/src/server/instructions/query.ts +42 -0
- package/src/server/instructions/request.ts +88 -0
- package/src/server/instructions/sql.ts +25 -0
- package/src/server/instructions/update.ts +24 -0
- package/src/server/migrations/20221129153547-calculation-variables.ts +64 -0
- package/src/server/migrations/20230221032941-change-request-body-type.ts +76 -0
- package/src/server/migrations/20230221071831-calculation-expression.ts +102 -0
- package/src/server/migrations/20230221121203-condition-calculation.ts +82 -0
- package/src/server/migrations/20230221162902-jsonb-to-json.ts +51 -0
- package/src/server/migrations/20230411034722-manual-multi-form.ts +282 -0
- package/src/server/migrations/20230612021134-manual-collection-block.ts +138 -0
- package/src/server/migrations/20230710115902-manual-action-values.ts +78 -0
- package/src/server/triggers/collection.ts +146 -0
- package/src/server/triggers/index.ts +22 -0
- package/src/server/triggers/schedule.ts +567 -0
- package/src/server/types/Execution.ts +26 -0
- package/src/server/types/FlowNode.ts +21 -0
- package/src/server/types/Job.ts +18 -0
- package/src/server/types/Workflow.ts +36 -0
- package/src/server/types/index.ts +4 -0
- package/src/server/utils.ts +17 -0
- package/lib/client/triggers/schedule/DateFieldsSelect.d.ts +0 -2
- /package/lib/server/{models → types}/Execution.js +0 -0
- /package/lib/server/{models → types}/FlowNode.js +0 -0
- /package/lib/server/{models → types}/Job.js +0 -0
- /package/lib/server/{models → types}/Workflow.js +0 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { Application } from '@nocobase/server';
|
|
2
|
+
import Database from '@nocobase/database';
|
|
3
|
+
import { getApp, sleep } from '..';
|
|
4
|
+
|
|
5
|
+
describe.skip('workflow > triggers > schedule', () => {
|
|
6
|
+
let app: Application;
|
|
7
|
+
let db: Database;
|
|
8
|
+
let PostRepo;
|
|
9
|
+
let CategoryRepo;
|
|
10
|
+
let WorkflowModel;
|
|
11
|
+
let WorkflowRepo;
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
app = await getApp();
|
|
15
|
+
|
|
16
|
+
db = app.db;
|
|
17
|
+
const workflow = db.getCollection('workflows');
|
|
18
|
+
WorkflowModel = workflow.model;
|
|
19
|
+
WorkflowRepo = workflow.repository;
|
|
20
|
+
PostRepo = db.getCollection('posts').repository;
|
|
21
|
+
CategoryRepo = db.getCollection('categories').repository;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => app.destroy());
|
|
25
|
+
|
|
26
|
+
describe('constant mode', () => {
|
|
27
|
+
it('neither startsOn nor repeat configurated', async () => {
|
|
28
|
+
const workflow = await WorkflowModel.create({
|
|
29
|
+
enabled: true,
|
|
30
|
+
type: 'schedule',
|
|
31
|
+
config: {
|
|
32
|
+
mode: 0,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
await sleep(3000);
|
|
37
|
+
|
|
38
|
+
const executions = await workflow.getExecutions();
|
|
39
|
+
expect(executions.length).toBe(0);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('on every 2 seconds', async () => {
|
|
43
|
+
const now = new Date();
|
|
44
|
+
// NOTE: align to even(0, 2, ...) + 0.5 seconds to start
|
|
45
|
+
await sleep((2.5 - (now.getSeconds() % 2)) * 1000 - now.getMilliseconds());
|
|
46
|
+
|
|
47
|
+
const workflow = await WorkflowModel.create({
|
|
48
|
+
enabled: true,
|
|
49
|
+
type: 'schedule',
|
|
50
|
+
config: {
|
|
51
|
+
mode: 0,
|
|
52
|
+
startsOn: now.toISOString(),
|
|
53
|
+
repeat: '*/2 * * * * *',
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await sleep(4000);
|
|
58
|
+
// sleep 1.5s at 2s trigger 1st time
|
|
59
|
+
// sleep 3.5s at 4s trigger 2nd time
|
|
60
|
+
|
|
61
|
+
const executions = await workflow.getExecutions();
|
|
62
|
+
expect(executions.length).toBe(2);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('on every even seconds and limit 1', async () => {
|
|
66
|
+
const now = new Date();
|
|
67
|
+
// NOTE: align to even(0, 2, ...) + 0.5 seconds to start
|
|
68
|
+
await sleep((2.5 - (now.getSeconds() % 2)) * 1000 - now.getMilliseconds());
|
|
69
|
+
|
|
70
|
+
const workflow = await WorkflowModel.create({
|
|
71
|
+
enabled: true,
|
|
72
|
+
type: 'schedule',
|
|
73
|
+
config: {
|
|
74
|
+
mode: 0,
|
|
75
|
+
startsOn: now.toISOString(),
|
|
76
|
+
repeat: '*/2 * * * * *',
|
|
77
|
+
limit: 1,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await sleep(5000);
|
|
82
|
+
|
|
83
|
+
const executions = await workflow.getExecutions();
|
|
84
|
+
expect(executions.length).toBe(1);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('on every 2 seconds after created and limit 1', async () => {
|
|
88
|
+
const now = new Date();
|
|
89
|
+
// NOTE: align to even(0, 2, ...) + 0.5 seconds to start
|
|
90
|
+
await sleep((2.5 - (now.getSeconds() % 2)) * 1000 - now.getMilliseconds());
|
|
91
|
+
|
|
92
|
+
const workflow = await WorkflowModel.create({
|
|
93
|
+
enabled: true,
|
|
94
|
+
type: 'schedule',
|
|
95
|
+
config: {
|
|
96
|
+
mode: 0,
|
|
97
|
+
startsOn: now.toISOString(),
|
|
98
|
+
repeat: 2000,
|
|
99
|
+
limit: 1,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await sleep(5000);
|
|
104
|
+
|
|
105
|
+
const executions = await workflow.getExecutions();
|
|
106
|
+
expect(executions.length).toBe(1);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('on certain second', async () => {
|
|
110
|
+
const now = new Date();
|
|
111
|
+
const startsOn = now.toISOString();
|
|
112
|
+
now.setSeconds(now.getSeconds() + 3);
|
|
113
|
+
now.setMilliseconds(0);
|
|
114
|
+
|
|
115
|
+
const workflow = await WorkflowModel.create({
|
|
116
|
+
enabled: true,
|
|
117
|
+
type: 'schedule',
|
|
118
|
+
config: {
|
|
119
|
+
mode: 0,
|
|
120
|
+
startsOn,
|
|
121
|
+
repeat: `${now.getSeconds()} * * * * *`,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await sleep(5000);
|
|
126
|
+
|
|
127
|
+
const executions = await workflow.getExecutions();
|
|
128
|
+
expect(executions.length).toBe(1);
|
|
129
|
+
expect(executions[0].context.date).toBe(now.toISOString());
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('multiple workflows trigger at same time', async () => {
|
|
133
|
+
const now = new Date();
|
|
134
|
+
const startsOn = now.toISOString();
|
|
135
|
+
now.setSeconds(now.getSeconds() + 2);
|
|
136
|
+
now.setMilliseconds(0);
|
|
137
|
+
|
|
138
|
+
let w1, w2;
|
|
139
|
+
await db.sequelize.transaction(async (transaction) => {
|
|
140
|
+
w1 = await WorkflowRepo.create({
|
|
141
|
+
values: {
|
|
142
|
+
enabled: true,
|
|
143
|
+
type: 'schedule',
|
|
144
|
+
config: {
|
|
145
|
+
mode: 0,
|
|
146
|
+
startsOn,
|
|
147
|
+
repeat: `${now.getSeconds()} * * * * *`,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
transaction,
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
await db.sequelize.transaction(async (transaction) => {
|
|
155
|
+
w2 = await WorkflowRepo.create({
|
|
156
|
+
values: {
|
|
157
|
+
enabled: true,
|
|
158
|
+
type: 'schedule',
|
|
159
|
+
config: {
|
|
160
|
+
mode: 0,
|
|
161
|
+
startsOn,
|
|
162
|
+
repeat: `${now.getSeconds()} * * * * *`,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
transaction,
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await sleep(3000);
|
|
170
|
+
await WorkflowModel.update({ enabled: false }, { where: { enabled: true } });
|
|
171
|
+
|
|
172
|
+
const [e1] = await w1.getExecutions();
|
|
173
|
+
expect(e1).toBeDefined();
|
|
174
|
+
expect(e1.context.date).toBe(now.toISOString());
|
|
175
|
+
|
|
176
|
+
const [e2] = await w2.getExecutions();
|
|
177
|
+
expect(e2).toBeDefined();
|
|
178
|
+
expect(e2.context.date).toBe(now.toISOString());
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('collection field mode', () => {
|
|
183
|
+
it('starts on post.createdAt with offset', async () => {
|
|
184
|
+
const workflow = await WorkflowModel.create({
|
|
185
|
+
enabled: true,
|
|
186
|
+
type: 'schedule',
|
|
187
|
+
config: {
|
|
188
|
+
mode: 1,
|
|
189
|
+
collection: 'posts',
|
|
190
|
+
startsOn: {
|
|
191
|
+
field: 'createdAt',
|
|
192
|
+
offset: 2,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const now = new Date();
|
|
198
|
+
await sleep((2.5 - (now.getSeconds() % 2)) * 1000 - now.getMilliseconds());
|
|
199
|
+
|
|
200
|
+
const post = await PostRepo.create({ values: { title: 't1' } });
|
|
201
|
+
|
|
202
|
+
await sleep(1000);
|
|
203
|
+
const executions = await workflow.getExecutions();
|
|
204
|
+
expect(executions.length).toBe(0);
|
|
205
|
+
|
|
206
|
+
await sleep(2000);
|
|
207
|
+
const [execution] = await workflow.getExecutions();
|
|
208
|
+
expect(execution).toBeDefined();
|
|
209
|
+
expect(execution.context.data.id).toBe(post.id);
|
|
210
|
+
|
|
211
|
+
const triggerTime = new Date(post.createdAt.getTime() + 2000);
|
|
212
|
+
triggerTime.setMilliseconds(0);
|
|
213
|
+
expect(execution.context.date).toBe(triggerTime.toISOString());
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('starts on post.createdAt and repeat by cron', async () => {
|
|
217
|
+
const workflow = await WorkflowModel.create({
|
|
218
|
+
enabled: true,
|
|
219
|
+
type: 'schedule',
|
|
220
|
+
config: {
|
|
221
|
+
mode: 1,
|
|
222
|
+
collection: 'posts',
|
|
223
|
+
startsOn: {
|
|
224
|
+
field: 'createdAt',
|
|
225
|
+
},
|
|
226
|
+
repeat: '*/2 * * * * *',
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const now = new Date();
|
|
231
|
+
await sleep((2.5 - (now.getSeconds() % 2)) * 1000 - now.getMilliseconds());
|
|
232
|
+
const startTime = new Date();
|
|
233
|
+
startTime.setMilliseconds(500);
|
|
234
|
+
|
|
235
|
+
const post = await PostRepo.create({ values: { title: 't1' } });
|
|
236
|
+
|
|
237
|
+
await sleep(5000);
|
|
238
|
+
// sleep 1.5s at 2s trigger 1st time
|
|
239
|
+
// sleep 3.5s at 4s trigger 2nd time
|
|
240
|
+
|
|
241
|
+
const executions = await workflow.getExecutions({ order: [['createdAt', 'ASC']] });
|
|
242
|
+
expect(executions.length).toBe(2);
|
|
243
|
+
const d1 = Date.parse(executions[0].context.date);
|
|
244
|
+
expect(d1 - 1500).toBe(startTime.getTime());
|
|
245
|
+
const d2 = Date.parse(executions[1].context.date);
|
|
246
|
+
expect(d2 - 3500).toBe(startTime.getTime());
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('starts on post.createdAt and repeat with endsOn at certain time', async () => {
|
|
250
|
+
const now = new Date();
|
|
251
|
+
await sleep((2.5 - (now.getSeconds() % 2)) * 1000 - now.getMilliseconds());
|
|
252
|
+
const startTime = new Date();
|
|
253
|
+
startTime.setMilliseconds(500);
|
|
254
|
+
|
|
255
|
+
const workflow = await WorkflowModel.create({
|
|
256
|
+
enabled: true,
|
|
257
|
+
type: 'schedule',
|
|
258
|
+
config: {
|
|
259
|
+
mode: 1,
|
|
260
|
+
collection: 'posts',
|
|
261
|
+
startsOn: {
|
|
262
|
+
field: 'createdAt',
|
|
263
|
+
},
|
|
264
|
+
repeat: '*/2 * * * * *',
|
|
265
|
+
endsOn: new Date(startTime.getTime() + 2500).toISOString(),
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const post = await PostRepo.create({ values: { title: 't1' } });
|
|
270
|
+
|
|
271
|
+
await sleep(5000);
|
|
272
|
+
|
|
273
|
+
const executions = await workflow.getExecutions();
|
|
274
|
+
expect(executions.length).toBe(1);
|
|
275
|
+
const d1 = Date.parse(executions[0].context.date);
|
|
276
|
+
expect(d1 - 1500).toBe(startTime.getTime());
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('starts on post.createdAt and repeat with endsOn by offset', async () => {
|
|
280
|
+
const workflow = await WorkflowModel.create({
|
|
281
|
+
enabled: true,
|
|
282
|
+
type: 'schedule',
|
|
283
|
+
config: {
|
|
284
|
+
mode: 1,
|
|
285
|
+
collection: 'posts',
|
|
286
|
+
startsOn: {
|
|
287
|
+
field: 'createdAt',
|
|
288
|
+
},
|
|
289
|
+
repeat: '*/2 * * * * *',
|
|
290
|
+
endsOn: {
|
|
291
|
+
field: 'createdAt',
|
|
292
|
+
offset: 3,
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const now = new Date();
|
|
298
|
+
await sleep((2.5 - (now.getSeconds() % 2)) * 1000 - now.getMilliseconds());
|
|
299
|
+
const startTime = new Date();
|
|
300
|
+
startTime.setMilliseconds(500);
|
|
301
|
+
|
|
302
|
+
const post = await PostRepo.create({ values: { title: 't1' } });
|
|
303
|
+
|
|
304
|
+
await sleep(5000);
|
|
305
|
+
const executions = await workflow.getExecutions();
|
|
306
|
+
expect(executions.length).toBe(1);
|
|
307
|
+
const d1 = Date.parse(executions[0].context.date);
|
|
308
|
+
expect(d1 - 1500).toBe(startTime.getTime());
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('starts on post.createdAt and repeat by number', async () => {
|
|
312
|
+
const workflow = await WorkflowModel.create({
|
|
313
|
+
enabled: true,
|
|
314
|
+
type: 'schedule',
|
|
315
|
+
config: {
|
|
316
|
+
mode: 1,
|
|
317
|
+
collection: 'posts',
|
|
318
|
+
startsOn: {
|
|
319
|
+
field: 'createdAt',
|
|
320
|
+
},
|
|
321
|
+
repeat: 2000,
|
|
322
|
+
endsOn: {
|
|
323
|
+
field: 'createdAt',
|
|
324
|
+
offset: 3,
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const now = new Date();
|
|
330
|
+
await sleep((2.5 - (now.getSeconds() % 2)) * 1000 - now.getMilliseconds());
|
|
331
|
+
const startTime = new Date();
|
|
332
|
+
startTime.setMilliseconds(500);
|
|
333
|
+
|
|
334
|
+
const post = await PostRepo.create({ values: { title: 't1' } });
|
|
335
|
+
|
|
336
|
+
await sleep(5000);
|
|
337
|
+
const executions = await workflow.getExecutions();
|
|
338
|
+
expect(executions.length).toBe(1);
|
|
339
|
+
const d1 = Date.parse(executions[0].context.date);
|
|
340
|
+
expect(d1 - 1500).toBe(startTime.getTime());
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('appends', async () => {
|
|
344
|
+
const category = await CategoryRepo.create({ values: { name: 'c1' } });
|
|
345
|
+
|
|
346
|
+
const workflow = await WorkflowModel.create({
|
|
347
|
+
enabled: true,
|
|
348
|
+
type: 'schedule',
|
|
349
|
+
config: {
|
|
350
|
+
mode: 1,
|
|
351
|
+
collection: 'posts',
|
|
352
|
+
startsOn: {
|
|
353
|
+
field: 'createdAt',
|
|
354
|
+
offset: 2,
|
|
355
|
+
},
|
|
356
|
+
appends: ['category'],
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const post = await PostRepo.create({ values: { title: 't1', categoryId: category.id } });
|
|
361
|
+
|
|
362
|
+
await sleep(5000);
|
|
363
|
+
|
|
364
|
+
const executions = await workflow.getExecutions();
|
|
365
|
+
expect(executions.length).toBe(1);
|
|
366
|
+
expect(executions[0].context.data.category.id).toBe(category.id);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as workflows from './workflows';
|
|
2
|
+
import * as nodes from './nodes';
|
|
3
|
+
|
|
4
|
+
function make(name, mod) {
|
|
5
|
+
return Object.keys(mod).reduce(
|
|
6
|
+
(result, key) => ({
|
|
7
|
+
...result,
|
|
8
|
+
[`${name}:${key}`]: mod[key],
|
|
9
|
+
}),
|
|
10
|
+
{},
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function ({ app }) {
|
|
15
|
+
app.actions({
|
|
16
|
+
...make('workflows', workflows),
|
|
17
|
+
...make('workflows.nodes', {
|
|
18
|
+
create: nodes.create,
|
|
19
|
+
destroy: nodes.destroy,
|
|
20
|
+
}),
|
|
21
|
+
...make('flow_nodes', {
|
|
22
|
+
update: nodes.update,
|
|
23
|
+
}),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { Context, utils } from '@nocobase/actions';
|
|
2
|
+
import { MultipleRelationRepository, Op } from '@nocobase/database';
|
|
3
|
+
import type { WorkflowModel } from '../types';
|
|
4
|
+
|
|
5
|
+
export async function create(context: Context, next) {
|
|
6
|
+
const { db } = context;
|
|
7
|
+
const repository = utils.getRepositoryFromParams(context) as MultipleRelationRepository;
|
|
8
|
+
const { whitelist, blacklist, updateAssociationValues, values, associatedIndex: workflowId } = context.action.params;
|
|
9
|
+
|
|
10
|
+
context.body = await db.sequelize.transaction(async (transaction) => {
|
|
11
|
+
const workflow = (await repository.getSourceModel(transaction)) as WorkflowModel;
|
|
12
|
+
if (workflow.executed) {
|
|
13
|
+
context.throw(400, 'Node could not be created in executed workflow');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const instance = await repository.create({
|
|
17
|
+
values,
|
|
18
|
+
whitelist,
|
|
19
|
+
blacklist,
|
|
20
|
+
updateAssociationValues,
|
|
21
|
+
context,
|
|
22
|
+
transaction,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (!instance.upstreamId) {
|
|
26
|
+
const previousHead = await repository.findOne({
|
|
27
|
+
filter: {
|
|
28
|
+
id: {
|
|
29
|
+
$ne: instance.id,
|
|
30
|
+
},
|
|
31
|
+
upstreamId: null,
|
|
32
|
+
},
|
|
33
|
+
transaction,
|
|
34
|
+
});
|
|
35
|
+
if (previousHead) {
|
|
36
|
+
await previousHead.setUpstream(instance, { transaction });
|
|
37
|
+
await instance.setDownstream(previousHead, { transaction });
|
|
38
|
+
instance.set('downstream', previousHead);
|
|
39
|
+
}
|
|
40
|
+
return instance;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const upstream = await instance.getUpstream({ transaction });
|
|
44
|
+
|
|
45
|
+
if (instance.branchIndex == null) {
|
|
46
|
+
const downstream = await upstream.getDownstream({ transaction });
|
|
47
|
+
|
|
48
|
+
if (downstream) {
|
|
49
|
+
await downstream.setUpstream(instance, { transaction });
|
|
50
|
+
await instance.setDownstream(downstream, { transaction });
|
|
51
|
+
instance.set('downstream', downstream);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await upstream.update(
|
|
55
|
+
{
|
|
56
|
+
downstreamId: instance.id,
|
|
57
|
+
},
|
|
58
|
+
{ transaction },
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
upstream.set('downstream', instance);
|
|
62
|
+
} else {
|
|
63
|
+
const [downstream] = await upstream.getBranches({
|
|
64
|
+
where: {
|
|
65
|
+
id: {
|
|
66
|
+
[Op.ne]: instance.id,
|
|
67
|
+
},
|
|
68
|
+
branchIndex: instance.branchIndex,
|
|
69
|
+
},
|
|
70
|
+
transaction,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (downstream) {
|
|
74
|
+
await downstream.update(
|
|
75
|
+
{
|
|
76
|
+
upstreamId: instance.id,
|
|
77
|
+
branchIndex: null,
|
|
78
|
+
},
|
|
79
|
+
{ transaction },
|
|
80
|
+
);
|
|
81
|
+
await instance.setDownstream(downstream, { transaction });
|
|
82
|
+
instance.set('downstream', downstream);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
instance.set('upstream', upstream);
|
|
87
|
+
|
|
88
|
+
return instance;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
await next();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function searchBranchNodes(nodes, from): any[] {
|
|
95
|
+
const branchHeads = nodes.filter((item: any) => item.upstreamId === from.id && item.branchIndex != null);
|
|
96
|
+
return branchHeads.reduce(
|
|
97
|
+
(flatten: any[], head) => flatten.concat(searchBranchDownstreams(nodes, head)),
|
|
98
|
+
[],
|
|
99
|
+
) as any[];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function searchBranchDownstreams(nodes, from) {
|
|
103
|
+
let result = [];
|
|
104
|
+
for (let search = from; search; search = search.downstream) {
|
|
105
|
+
result = [...result, search, ...searchBranchNodes(nodes, search)];
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function destroy(context: Context, next) {
|
|
111
|
+
const { db } = context;
|
|
112
|
+
const repository = utils.getRepositoryFromParams(context) as MultipleRelationRepository;
|
|
113
|
+
const { filterByTk } = context.action.params;
|
|
114
|
+
|
|
115
|
+
context.body = await db.sequelize.transaction(async (transaction) => {
|
|
116
|
+
const workflow = (await repository.getSourceModel(transaction)) as WorkflowModel;
|
|
117
|
+
if (workflow.executed) {
|
|
118
|
+
context.throw(400, 'Nodes in executed workflow could not be deleted');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const fields = ['id', 'upstreamId', 'downstreamId', 'branchIndex'];
|
|
122
|
+
const instance = await repository.findOne({
|
|
123
|
+
filterByTk,
|
|
124
|
+
fields: [...fields, 'workflowId'],
|
|
125
|
+
appends: ['upstream', 'downstream'],
|
|
126
|
+
transaction,
|
|
127
|
+
});
|
|
128
|
+
const { upstream, downstream } = instance.get();
|
|
129
|
+
|
|
130
|
+
if (upstream && upstream.downstreamId === instance.id) {
|
|
131
|
+
await upstream.update(
|
|
132
|
+
{
|
|
133
|
+
downstreamId: instance.downstreamId,
|
|
134
|
+
},
|
|
135
|
+
{ transaction },
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (downstream) {
|
|
140
|
+
await downstream.update(
|
|
141
|
+
{
|
|
142
|
+
upstreamId: instance.upstreamId,
|
|
143
|
+
branchIndex: instance.branchIndex,
|
|
144
|
+
},
|
|
145
|
+
{ transaction },
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const nodes = await repository.find({
|
|
150
|
+
filter: {
|
|
151
|
+
workflowId: instance.workflowId,
|
|
152
|
+
},
|
|
153
|
+
fields,
|
|
154
|
+
transaction,
|
|
155
|
+
});
|
|
156
|
+
const nodesMap = new Map();
|
|
157
|
+
// make map
|
|
158
|
+
nodes.forEach((item) => {
|
|
159
|
+
nodesMap.set(item.id, item);
|
|
160
|
+
});
|
|
161
|
+
// overwrite
|
|
162
|
+
nodesMap.set(instance.id, instance);
|
|
163
|
+
// make linked list
|
|
164
|
+
nodes.forEach((item) => {
|
|
165
|
+
if (item.upstreamId) {
|
|
166
|
+
item.upstream = nodesMap.get(item.upstreamId);
|
|
167
|
+
}
|
|
168
|
+
if (item.downstreamId) {
|
|
169
|
+
item.downstream = nodesMap.get(item.downstreamId);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const branchNodes = searchBranchNodes(nodes, instance);
|
|
174
|
+
|
|
175
|
+
await repository.destroy({
|
|
176
|
+
filterByTk: [instance.id, ...branchNodes.map((item) => item.id)],
|
|
177
|
+
transaction,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return instance;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await next();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function update(context: Context, next) {
|
|
187
|
+
const { db } = context;
|
|
188
|
+
const repository = utils.getRepositoryFromParams(context);
|
|
189
|
+
const { filterByTk, values, whitelist, blacklist, filter, updateAssociationValues } = context.action.params;
|
|
190
|
+
context.body = await db.sequelize.transaction(async (transaction) => {
|
|
191
|
+
// TODO(optimize): duplicated instance query
|
|
192
|
+
const { workflow } = await repository.findOne({
|
|
193
|
+
filterByTk,
|
|
194
|
+
appends: ['workflow.executed'],
|
|
195
|
+
transaction,
|
|
196
|
+
});
|
|
197
|
+
if (workflow.executed) {
|
|
198
|
+
context.throw(400, 'Nodes in executed workflow could not be reconfigured');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return repository.update({
|
|
202
|
+
filterByTk,
|
|
203
|
+
values,
|
|
204
|
+
whitelist,
|
|
205
|
+
blacklist,
|
|
206
|
+
filter,
|
|
207
|
+
updateAssociationValues,
|
|
208
|
+
context,
|
|
209
|
+
transaction,
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await next();
|
|
214
|
+
}
|