@nocobase/plugin-workflow-action-trigger 0.20.0-alpha.6

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 (41) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +9 -0
  3. package/README.zh-CN.md +9 -0
  4. package/client.d.ts +2 -0
  5. package/client.js +1 -0
  6. package/dist/client/ActionTrigger.d.ts +54 -0
  7. package/dist/client/index.d.ts +4 -0
  8. package/dist/client/index.js +1 -0
  9. package/dist/externalVersion.js +13 -0
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +39 -0
  12. package/dist/locale/en-US.json +9 -0
  13. package/dist/locale/index.d.ts +3 -0
  14. package/dist/locale/index.js +39 -0
  15. package/dist/locale/ko_KR.json +9 -0
  16. package/dist/locale/zh-CN.json +10 -0
  17. package/dist/server/ActionTrigger.d.ts +9 -0
  18. package/dist/server/ActionTrigger.js +130 -0
  19. package/dist/server/Plugin.d.ts +4 -0
  20. package/dist/server/Plugin.js +41 -0
  21. package/dist/server/index.d.ts +1 -0
  22. package/dist/server/index.js +33 -0
  23. package/dist/server/migrations/20240227172623-change-name.d.ts +6 -0
  24. package/dist/server/migrations/20240227172623-change-name.js +42 -0
  25. package/package.json +28 -0
  26. package/server.d.ts +2 -0
  27. package/server.js +1 -0
  28. package/src/client/ActionTrigger.tsx +132 -0
  29. package/src/client/__e2e__/configuration.test.ts +661 -0
  30. package/src/client/__e2e__/workflowCRUD.test.ts +178 -0
  31. package/src/client/index.ts +100 -0
  32. package/src/index.ts +2 -0
  33. package/src/locale/en-US.json +9 -0
  34. package/src/locale/index.ts +12 -0
  35. package/src/locale/ko_KR.json +9 -0
  36. package/src/locale/zh-CN.json +10 -0
  37. package/src/server/ActionTrigger.ts +132 -0
  38. package/src/server/Plugin.ts +11 -0
  39. package/src/server/__tests__/trigger.test.ts +503 -0
  40. package/src/server/index.ts +1 -0
  41. package/src/server/migrations/20240227172623-change-name.ts +22 -0
@@ -0,0 +1,503 @@
1
+ import Database from '@nocobase/database';
2
+ import { EXECUTION_STATUS } from '@nocobase/plugin-workflow';
3
+ import { getApp, sleep } from '@nocobase/plugin-workflow-test';
4
+ import { MockServer } from '@nocobase/test';
5
+
6
+ import Plugin from '..';
7
+
8
+ describe('workflow > action-trigger', () => {
9
+ let app: MockServer;
10
+ let db: Database;
11
+ let agent;
12
+ let PostRepo;
13
+ let CommentRepo;
14
+ let WorkflowModel;
15
+ let UserModel;
16
+ let users;
17
+ let userAgents;
18
+
19
+ beforeEach(async () => {
20
+ app = await getApp({
21
+ plugins: ['users', 'auth', Plugin],
22
+ });
23
+ await app.pm.get('auth').install();
24
+ agent = app.agent();
25
+ db = app.db;
26
+ WorkflowModel = db.getCollection('workflows').model;
27
+ PostRepo = db.getCollection('posts').repository;
28
+ CommentRepo = db.getCollection('comments').repository;
29
+ UserModel = db.getCollection('users').model;
30
+
31
+ users = await UserModel.bulkCreate([
32
+ { id: 2, nickname: 'a' },
33
+ { id: 3, nickname: 'b' },
34
+ ]);
35
+
36
+ userAgents = users.map((user) => app.agent().login(user));
37
+ });
38
+
39
+ afterEach(() => app.destroy());
40
+
41
+ describe('create', () => {
42
+ it('enabled / disabled', async () => {
43
+ const workflow = await WorkflowModel.create({
44
+ enabled: true,
45
+ type: 'action',
46
+ config: {
47
+ collection: 'posts',
48
+ },
49
+ });
50
+
51
+ const res1 = await userAgents[0].resource('posts').create({
52
+ values: { title: 't1' },
53
+ triggerWorkflows: `${workflow.key}`,
54
+ });
55
+ expect(res1.status).toBe(200);
56
+
57
+ await sleep(500);
58
+
59
+ const e1 = await workflow.getExecutions();
60
+ expect(e1.length).toBe(1);
61
+ expect(e1[0].status).toBe(EXECUTION_STATUS.RESOLVED);
62
+ expect(e1[0].context.data).toMatchObject({ title: 't1' });
63
+
64
+ await workflow.update({
65
+ enabled: false,
66
+ });
67
+
68
+ const res2 = await userAgents[0].resource('posts').create({
69
+ values: { title: 't2' },
70
+ triggerWorkflows: `${workflow.key}`,
71
+ });
72
+ expect(res2.status).toBe(200);
73
+
74
+ await sleep(500);
75
+
76
+ const e2 = await workflow.getExecutions({ order: [['id', 'ASC']] });
77
+ expect(e2.length).toBe(1);
78
+
79
+ await workflow.update({
80
+ enabled: true,
81
+ });
82
+
83
+ const res3 = await userAgents[0].resource('posts').create({
84
+ values: { title: 't3' },
85
+ triggerWorkflows: `${workflow.key}`,
86
+ });
87
+ expect(res2.status).toBe(200);
88
+
89
+ await sleep(500);
90
+
91
+ const e3 = await workflow.getExecutions({ order: [['id', 'ASC']] });
92
+ expect(e3.length).toBe(2);
93
+ expect(e3[1].status).toBe(EXECUTION_STATUS.RESOLVED);
94
+ expect(e3[1].context.data).toMatchObject({ title: 't3' });
95
+ });
96
+
97
+ it('only trigger if params provided matching collection config', async () => {
98
+ const workflow = await WorkflowModel.create({
99
+ enabled: true,
100
+ type: 'action',
101
+ config: {
102
+ collection: 'posts',
103
+ },
104
+ });
105
+
106
+ const res1 = await userAgents[0].resource('posts').create({
107
+ values: { title: 't1' },
108
+ triggerWorkflows: `${workflow.key}`,
109
+ });
110
+ expect(res1.status).toBe(200);
111
+
112
+ await sleep(500);
113
+
114
+ const e1 = await workflow.getExecutions();
115
+ expect(e1.length).toBe(1);
116
+ expect(e1[0].status).toBe(EXECUTION_STATUS.RESOLVED);
117
+ expect(e1[0].context.data).toMatchObject({ title: 't1' });
118
+
119
+ await workflow.update({
120
+ config: {
121
+ collection: 'comments',
122
+ },
123
+ });
124
+
125
+ const res2 = await userAgents[0].resource('posts').create({
126
+ values: { title: 't2' },
127
+ triggerWorkflows: `${workflow.key}`,
128
+ });
129
+ expect(res2.status).toBe(200);
130
+
131
+ await sleep(500);
132
+
133
+ const e2 = await workflow.getExecutions({ order: [['id', 'ASC']] });
134
+ expect(e2.length).toBe(1);
135
+ // expect(e2[1].status).toBe(EXECUTION_STATUS.RESOLVED);
136
+ // expect(e2[1].context.data).toMatchObject({ title: 't2' });
137
+ });
138
+
139
+ it('system fields could be accessed', async () => {
140
+ const workflow = await WorkflowModel.create({
141
+ enabled: true,
142
+ type: 'action',
143
+ config: {
144
+ collection: 'posts',
145
+ },
146
+ });
147
+
148
+ const res1 = await userAgents[0].resource('posts').create({
149
+ values: { title: 't1' },
150
+ triggerWorkflows: `${workflow.key}`,
151
+ });
152
+ expect(res1.status).toBe(200);
153
+
154
+ await sleep(500);
155
+
156
+ const e1 = await workflow.getExecutions();
157
+ expect(e1.length).toBe(1);
158
+ expect(e1[0].status).toBe(EXECUTION_STATUS.RESOLVED);
159
+ expect(e1[0].context.data).toHaveProperty('createdAt');
160
+ });
161
+
162
+ it('appends', async () => {
163
+ const workflow = await WorkflowModel.create({
164
+ enabled: true,
165
+ type: 'action',
166
+ config: {
167
+ collection: 'posts',
168
+ appends: ['createdBy'],
169
+ },
170
+ });
171
+
172
+ const res1 = await userAgents[0].resource('posts').create({
173
+ values: { title: 't1' },
174
+ triggerWorkflows: `${workflow.key}`,
175
+ });
176
+ expect(res1.status).toBe(200);
177
+
178
+ await sleep(500);
179
+
180
+ const e1 = await workflow.getExecutions();
181
+ expect(e1.length).toBe(1);
182
+ expect(e1[0].status).toBe(EXECUTION_STATUS.RESOLVED);
183
+ expect(e1[0].context.data).toHaveProperty('createdBy');
184
+ });
185
+
186
+ it('user submitted form', async () => {
187
+ const workflow = await WorkflowModel.create({
188
+ enabled: true,
189
+ type: 'action',
190
+ config: {
191
+ collection: 'posts',
192
+ },
193
+ });
194
+
195
+ const res1 = await userAgents[0].resource('posts').create({
196
+ values: { title: 't1' },
197
+ triggerWorkflows: `${workflow.key}`,
198
+ });
199
+ expect(res1.status).toBe(200);
200
+
201
+ await sleep(500);
202
+
203
+ const [e1] = await workflow.getExecutions();
204
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
205
+ expect(e1.context.user).toBeDefined();
206
+ expect(e1.context.user.id).toBe(users[0].id);
207
+ });
208
+ });
209
+
210
+ describe('update', () => {
211
+ it('trigger after updated', async () => {
212
+ const workflow = await WorkflowModel.create({
213
+ enabled: true,
214
+ type: 'action',
215
+ config: {
216
+ collection: 'posts',
217
+ },
218
+ });
219
+
220
+ const res1 = await userAgents[0].resource('posts').create({
221
+ values: { title: 't1' },
222
+ });
223
+ expect(res1.status).toBe(200);
224
+
225
+ await sleep(500);
226
+
227
+ const e1 = await workflow.getExecutions();
228
+ expect(e1.length).toBe(0);
229
+
230
+ const res2 = await userAgents[0].resource('posts').update({
231
+ filterByTk: res1.body.data.id,
232
+ values: { title: 't2' },
233
+ triggerWorkflows: `${workflow.key}`,
234
+ });
235
+
236
+ await sleep(500);
237
+
238
+ const [e2] = await workflow.getExecutions();
239
+ expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED);
240
+ expect(e2.context.data).toHaveProperty('title', 't2');
241
+ });
242
+ });
243
+
244
+ describe.skip('destroy', () => {
245
+ it('trigger after destroyed', async () => {
246
+ const workflow = await WorkflowModel.create({
247
+ enabled: true,
248
+ type: 'action',
249
+ config: {
250
+ collection: 'posts',
251
+ },
252
+ });
253
+
254
+ const p1 = await PostRepo.create({
255
+ values: { title: 't1' },
256
+ });
257
+ const p2 = await PostRepo.create({
258
+ values: { title: 't2' },
259
+ });
260
+ const res1 = await userAgents[0].resource('posts').destroy({
261
+ filterByTk: p1.id,
262
+ });
263
+ expect(res1.status).toBe(200);
264
+
265
+ await sleep(500);
266
+
267
+ const e1 = await workflow.getExecutions();
268
+ expect(e1.length).toBe(0);
269
+
270
+ const res2 = await userAgents[0].resource('posts').destroy({
271
+ filterByTk: p2.id,
272
+ triggerWorkflows: `${workflow.key}`,
273
+ });
274
+
275
+ await sleep(500);
276
+
277
+ const [e2] = await workflow.getExecutions();
278
+ expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED);
279
+ expect(e2.context.data).toBe(1);
280
+ });
281
+ });
282
+
283
+ describe('directly trigger', () => {
284
+ it('trigger data', async () => {
285
+ const workflow = await WorkflowModel.create({
286
+ enabled: true,
287
+ type: 'action',
288
+ });
289
+
290
+ const res1 = await userAgents[0].resource('workflows').trigger({
291
+ values: { title: 't1' },
292
+ triggerWorkflows: `${workflow.key}`,
293
+ });
294
+ expect(res1.status).toBe(202);
295
+
296
+ await sleep(500);
297
+
298
+ const [e1] = await workflow.getExecutions();
299
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
300
+ expect(e1.context.data).toMatchObject({ title: 't1' });
301
+ });
302
+
303
+ it('multi trigger', async () => {
304
+ const w1 = await WorkflowModel.create({
305
+ enabled: true,
306
+ type: 'action',
307
+ });
308
+
309
+ const w2 = await WorkflowModel.create({
310
+ enabled: true,
311
+ type: 'action',
312
+ });
313
+
314
+ const res1 = await userAgents[0].resource('workflows').trigger({
315
+ values: { title: 't1' },
316
+ triggerWorkflows: `${w1.key},${w2.key}`,
317
+ });
318
+ expect(res1.status).toBe(202);
319
+
320
+ await sleep(500);
321
+
322
+ const [e1] = await w1.getExecutions();
323
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
324
+ expect(e1.context.data).toMatchObject({ title: 't1' });
325
+
326
+ const [e2] = await w2.getExecutions();
327
+ expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED);
328
+ expect(e2.context.data).toMatchObject({ title: 't1' });
329
+ });
330
+
331
+ it('user submitted form', async () => {
332
+ const workflow = await WorkflowModel.create({
333
+ enabled: true,
334
+ type: 'action',
335
+ config: {
336
+ collection: 'posts',
337
+ appends: ['createdBy'],
338
+ },
339
+ });
340
+
341
+ const res1 = await userAgents[0].resource('posts').create({
342
+ values: { title: 't1' },
343
+ triggerWorkflows: `${workflow.key}`,
344
+ });
345
+ expect(res1.status).toBe(200);
346
+
347
+ await sleep(500);
348
+
349
+ const [e1] = await workflow.getExecutions();
350
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
351
+ expect(e1.context.user).toBeDefined();
352
+ expect(e1.context.user.id).toBe(users[0].id);
353
+ });
354
+ });
355
+
356
+ describe('context data path', () => {
357
+ it('level: 1', async () => {
358
+ const workflow = await WorkflowModel.create({
359
+ enabled: true,
360
+ type: 'action',
361
+ });
362
+
363
+ const res1 = await userAgents[0].resource('workflows').trigger({
364
+ values: { title: 't1', category: { title: 'c1' } },
365
+ triggerWorkflows: `${workflow.key}!category`,
366
+ });
367
+ expect(res1.status).toBe(202);
368
+
369
+ await sleep(500);
370
+
371
+ const [e1] = await workflow.getExecutions();
372
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
373
+ expect(e1.context.data).toMatchObject({ title: 'c1' });
374
+ });
375
+
376
+ it('level: 2', async () => {
377
+ const workflow = await WorkflowModel.create({
378
+ enabled: true,
379
+ type: 'action',
380
+ });
381
+
382
+ const res1 = await userAgents[0].resource('workflows').trigger({
383
+ values: { content: 'comment1', post: { category: { title: 'c1' } } },
384
+ triggerWorkflows: `${workflow.key}!post.category`,
385
+ });
386
+ expect(res1.status).toBe(202);
387
+
388
+ await sleep(500);
389
+
390
+ const [e1] = await workflow.getExecutions();
391
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
392
+ expect(e1.context.data).toMatchObject({ title: 'c1' });
393
+ });
394
+ });
395
+
396
+ describe('workflow key', () => {
397
+ it('revision', async () => {
398
+ const w1 = await WorkflowModel.create({
399
+ enabled: true,
400
+ type: 'action',
401
+ });
402
+
403
+ const res1 = await userAgents[0].resource('workflows').trigger({
404
+ values: { title: 't1' },
405
+ triggerWorkflows: `${w1.key}`,
406
+ });
407
+ expect(res1.status).toBe(202);
408
+
409
+ await sleep(500);
410
+
411
+ const [e1] = await w1.getExecutions();
412
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
413
+ expect(e1.context.data).toMatchObject({ title: 't1' });
414
+
415
+ const res2 = await userAgents[0].resource('workflows').revision({
416
+ filterByTk: w1.id,
417
+ filter: {
418
+ key: w1.key,
419
+ },
420
+ });
421
+ const w2 = await WorkflowModel.findByPk(res2.body.data.id);
422
+ await w2.update({
423
+ enabled: true,
424
+ });
425
+
426
+ const res3 = await userAgents[0].resource('workflows').trigger({
427
+ values: { title: 't2' },
428
+ triggerWorkflows: `${w1.key}`,
429
+ });
430
+ expect(res3.status).toBe(202);
431
+
432
+ await sleep(500);
433
+
434
+ const e2 = await w1.getExecutions();
435
+ expect(e2.length).toBe(1);
436
+ const e3 = await w2.getExecutions();
437
+ expect(e3.length).toBe(1);
438
+ expect(e3[0].status).toBe(EXECUTION_STATUS.RESOLVED);
439
+ expect(e3[0].context.data).toMatchObject({ title: 't2' });
440
+ });
441
+ });
442
+
443
+ describe('sync', () => {
444
+ it('sync form trigger', async () => {
445
+ const workflow = await WorkflowModel.create({
446
+ enabled: true,
447
+ type: 'action',
448
+ sync: true,
449
+ config: {
450
+ collection: 'posts',
451
+ },
452
+ });
453
+
454
+ const res1 = await userAgents[0].resource('posts').create({
455
+ values: { title: 't1' },
456
+ triggerWorkflows: `${workflow.key}`,
457
+ });
458
+ expect(res1.status).toBe(200);
459
+
460
+ const executions = await workflow.getExecutions();
461
+ expect(executions.length).toBe(1);
462
+ expect(executions[0].status).toBe(EXECUTION_STATUS.RESOLVED);
463
+ });
464
+
465
+ it('sync and async will all be triggered in one action', async () => {
466
+ const w1 = await WorkflowModel.create({
467
+ enabled: true,
468
+ type: 'action',
469
+ config: {
470
+ collection: 'posts',
471
+ },
472
+ });
473
+
474
+ const w2 = await WorkflowModel.create({
475
+ enabled: true,
476
+ type: 'action',
477
+ sync: true,
478
+ config: {
479
+ collection: 'posts',
480
+ },
481
+ });
482
+
483
+ const res1 = await userAgents[0].resource('posts').create({
484
+ values: { title: 't1' },
485
+ triggerWorkflows: [w1.key, w2.key].join(),
486
+ });
487
+ expect(res1.status).toBe(200);
488
+
489
+ const e1s = await w1.getExecutions();
490
+ expect(e1s.length).toBe(0);
491
+
492
+ const e2s = await w2.getExecutions();
493
+ expect(e2s.length).toBe(1);
494
+ expect(e2s[0].status).toBe(EXECUTION_STATUS.RESOLVED);
495
+
496
+ await sleep(500);
497
+
498
+ const e3s = await w1.getExecutions();
499
+ expect(e3s.length).toBe(1);
500
+ expect(e3s[0].status).toBe(EXECUTION_STATUS.RESOLVED);
501
+ });
502
+ });
503
+ });
@@ -0,0 +1 @@
1
+ export { default } from './Plugin';
@@ -0,0 +1,22 @@
1
+ import { Migration } from '@nocobase/server';
2
+
3
+ export default class extends Migration {
4
+ appVersion = '<0.20.0-alpha.6';
5
+ on = 'afterSync';
6
+ async up() {
7
+ const { db } = this.context;
8
+
9
+ const WorkflowRepo = db.getRepository('workflows');
10
+ await db.sequelize.transaction(async (transaction) => {
11
+ await WorkflowRepo.update({
12
+ filter: {
13
+ type: 'form',
14
+ },
15
+ values: {
16
+ type: 'action',
17
+ },
18
+ transaction,
19
+ });
20
+ });
21
+ }
22
+ }