@nocobase/plugin-workflow 0.11.0-alpha.1 → 0.11.1-alpha.2

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 (133) hide show
  1. package/lib/client/AddButton.js +13 -4
  2. package/lib/client/Branch.js +4 -2
  3. package/lib/client/CanvasContent.js +6 -4
  4. package/lib/client/ExecutionCanvas.js +18 -7
  5. package/lib/client/ExecutionPage.js +4 -2
  6. package/lib/client/WorkflowCanvas.js +16 -6
  7. package/lib/client/WorkflowPage.js +4 -2
  8. package/lib/client/WorkflowProvider.js +2 -2
  9. package/lib/client/components/CollectionBlockInitializer.js +3 -3
  10. package/lib/client/components/CollectionFieldset.js +7 -1
  11. package/lib/client/components/FieldsSelect.js +4 -1
  12. package/lib/client/components/NodeDescription.js +36 -22
  13. package/lib/client/index.js +3 -3
  14. package/lib/client/locale/zh-CN.d.ts +5 -1
  15. package/lib/client/locale/zh-CN.js +6 -2
  16. package/lib/client/nodes/aggregate.d.ts +6 -1
  17. package/lib/client/nodes/aggregate.js +4 -3
  18. package/lib/client/nodes/calculation.d.ts +5 -3
  19. package/lib/client/nodes/calculation.js +6 -5
  20. package/lib/client/nodes/condition.d.ts +1 -7
  21. package/lib/client/nodes/condition.js +12 -23
  22. package/lib/client/nodes/create.d.ts +2 -4
  23. package/lib/client/nodes/create.js +1 -3
  24. package/lib/client/nodes/index.d.ts +1 -2
  25. package/lib/client/nodes/index.js +24 -24
  26. package/lib/client/nodes/loop.js +19 -28
  27. package/lib/client/nodes/manual/FormBlockInitializer.js +6 -5
  28. package/lib/client/nodes/manual/SchemaConfig.d.ts +1 -2
  29. package/lib/client/nodes/manual/SchemaConfig.js +175 -21
  30. package/lib/client/nodes/manual/WorkflowTodo.js +39 -46
  31. package/lib/client/nodes/manual/forms/create.js +8 -1
  32. package/lib/client/nodes/manual/forms/custom.js +11 -4
  33. package/lib/client/nodes/manual/forms/update.js +8 -1
  34. package/lib/client/nodes/manual/index.d.ts +6 -1
  35. package/lib/client/nodes/manual/index.js +5 -4
  36. package/lib/client/nodes/parallel.js +7 -4
  37. package/lib/client/nodes/query.d.ts +2 -5
  38. package/lib/client/nodes/query.js +1 -3
  39. package/lib/client/nodes/sql.d.ts +26 -0
  40. package/lib/client/{triggers/schedule/DateFieldsSelect.js → nodes/sql.js} +37 -46
  41. package/lib/client/schemas/collection.d.ts +2 -3
  42. package/lib/client/schemas/collection.js +8 -7
  43. package/lib/client/style.d.ts +18 -13
  44. package/lib/client/style.js +312 -289
  45. package/lib/client/triggers/collection.d.ts +9 -10
  46. package/lib/client/triggers/collection.js +4 -0
  47. package/lib/client/triggers/index.d.ts +2 -3
  48. package/lib/client/triggers/index.js +10 -5
  49. package/lib/client/triggers/schedule/OnField.js +35 -23
  50. package/lib/client/triggers/schedule/ScheduleConfig.js +7 -7
  51. package/lib/client/triggers/schedule/index.d.ts +0 -1
  52. package/lib/client/triggers/schedule/index.js +31 -19
  53. package/lib/client/variable.d.ts +29 -11
  54. package/lib/client/variable.js +39 -24
  55. package/lib/server/Plugin.d.ts +1 -3
  56. package/lib/server/Plugin.js +10 -6
  57. package/lib/server/Processor.d.ts +1 -1
  58. package/lib/server/Processor.js +2 -2
  59. package/lib/server/instructions/create.d.ts +1 -1
  60. package/lib/server/instructions/create.js +13 -13
  61. package/lib/server/instructions/index.js +1 -1
  62. package/lib/server/instructions/manual/actions.js +19 -7
  63. package/lib/server/instructions/manual/forms/create.js +7 -1
  64. package/lib/server/instructions/manual/forms/update.js +7 -1
  65. package/lib/server/instructions/query.js +8 -1
  66. package/lib/server/instructions/request.d.ts +1 -1
  67. package/lib/server/instructions/request.js +4 -2
  68. package/lib/server/instructions/sql.d.ts +12 -0
  69. package/lib/server/instructions/sql.js +34 -0
  70. package/lib/server/migrations/20230710115902-manual-action-values.d.ts +4 -0
  71. package/lib/server/migrations/20230710115902-manual-action-values.js +97 -0
  72. package/lib/server/triggers/collection.js +13 -11
  73. package/lib/server/utils.d.ts +2 -0
  74. package/lib/server/utils.js +21 -0
  75. package/package.json +12 -11
  76. package/src/client/AddButton.tsx +17 -5
  77. package/src/client/Branch.tsx +4 -2
  78. package/src/client/CanvasContent.tsx +6 -4
  79. package/src/client/ExecutionCanvas.tsx +11 -13
  80. package/src/client/ExecutionPage.tsx +3 -2
  81. package/src/client/WorkflowCanvas.tsx +14 -13
  82. package/src/client/WorkflowPage.tsx +3 -2
  83. package/src/client/WorkflowProvider.tsx +2 -2
  84. package/src/client/components/CollectionBlockInitializer.tsx +3 -3
  85. package/src/client/components/CollectionFieldset.tsx +5 -3
  86. package/src/client/components/FieldsSelect.tsx +5 -1
  87. package/src/client/components/NodeDescription.tsx +30 -23
  88. package/src/client/index.tsx +3 -3
  89. package/src/client/locale/zh-CN.ts +8 -2
  90. package/src/client/nodes/aggregate.tsx +4 -4
  91. package/src/client/nodes/calculation.tsx +4 -5
  92. package/src/client/nodes/condition.tsx +7 -34
  93. package/src/client/nodes/create.tsx +0 -1
  94. package/src/client/nodes/index.tsx +21 -25
  95. package/src/client/nodes/loop.tsx +16 -31
  96. package/src/client/nodes/manual/FormBlockInitializer.tsx +6 -5
  97. package/src/client/nodes/manual/SchemaConfig.tsx +162 -18
  98. package/src/client/nodes/manual/WorkflowTodo.tsx +43 -47
  99. package/src/client/nodes/manual/forms/create.tsx +5 -1
  100. package/src/client/nodes/manual/forms/custom.tsx +7 -3
  101. package/src/client/nodes/manual/forms/update.tsx +5 -1
  102. package/src/client/nodes/manual/index.tsx +5 -5
  103. package/src/client/nodes/parallel.tsx +6 -5
  104. package/src/client/nodes/query.tsx +0 -1
  105. package/src/client/nodes/sql.tsx +37 -0
  106. package/src/client/schemas/collection.ts +6 -6
  107. package/src/client/style.tsx +324 -289
  108. package/src/client/triggers/collection.tsx +4 -0
  109. package/src/client/triggers/index.tsx +14 -10
  110. package/src/client/triggers/schedule/OnField.tsx +29 -15
  111. package/src/client/triggers/schedule/ScheduleConfig.tsx +21 -19
  112. package/src/client/triggers/schedule/index.tsx +25 -19
  113. package/src/client/variable.tsx +48 -26
  114. package/src/server/Plugin.ts +13 -9
  115. package/src/server/Processor.ts +2 -2
  116. package/src/server/__tests__/collections/categories.ts +4 -0
  117. package/src/server/__tests__/instructions/manual.test.ts +391 -72
  118. package/src/server/__tests__/instructions/request.test.ts +30 -0
  119. package/src/server/__tests__/instructions/sql.test.ts +162 -0
  120. package/src/server/__tests__/triggers/collection.test.ts +35 -0
  121. package/src/server/instructions/create.ts +13 -11
  122. package/src/server/instructions/index.ts +1 -0
  123. package/src/server/instructions/manual/actions.ts +16 -4
  124. package/src/server/instructions/manual/forms/create.ts +2 -1
  125. package/src/server/instructions/manual/forms/update.ts +3 -2
  126. package/src/server/instructions/query.ts +12 -1
  127. package/src/server/instructions/request.ts +2 -1
  128. package/src/server/instructions/sql.ts +25 -0
  129. package/src/server/migrations/20230710115902-manual-action-values.ts +78 -0
  130. package/src/server/triggers/collection.ts +15 -11
  131. package/src/server/utils.ts +17 -0
  132. package/lib/client/triggers/schedule/DateFieldsSelect.d.ts +0 -2
  133. package/src/client/triggers/schedule/DateFieldsSelect.tsx +0 -28
@@ -213,5 +213,35 @@ describe('workflow > instructions > request', () => {
213
213
  expect(job.status).toEqual(JOB_STATUS.RESOLVED);
214
214
  expect(job.result.data).toEqual({ title });
215
215
  });
216
+
217
+ it('request inside loop',async () => {
218
+ const n1 = await workflow.createNode({
219
+ type: 'loop',
220
+ config: {
221
+ target: 2,
222
+ },
223
+ });
224
+
225
+ const n2 = await workflow.createNode({
226
+ type: 'request',
227
+ upstreamId: n1.id,
228
+ branchIndex: 0,
229
+ config: {
230
+ url: URL_DATA,
231
+ method: 'GET',
232
+ }
233
+ });
234
+
235
+ await PostRepo.create({ values: { title: 't1' } });
236
+
237
+ await sleep(500);
238
+
239
+ const [execution] = await workflow.getExecutions();
240
+ expect(execution.status).toEqual(EXECUTION_STATUS.RESOLVED);
241
+ const jobs = await execution.getJobs({ order: [['id', 'ASC']] });
242
+ expect(jobs.length).toBe(3);
243
+ expect(jobs.map(item => item.status)).toEqual(Array(3).fill(JOB_STATUS.RESOLVED));
244
+ expect(jobs[0].result).toBe(2);
245
+ });
216
246
  });
217
247
  });
@@ -0,0 +1,162 @@
1
+ import { Application } from '@nocobase/server';
2
+ import Database from '@nocobase/database';
3
+ import { getApp, sleep } from '..';
4
+ import { EXECUTION_STATUS, JOB_STATUS } from '../../constants';
5
+
6
+ describe('workflow > instructions > sql', () => {
7
+ let app: Application;
8
+ let db: Database;
9
+ let PostRepo;
10
+ let PostCollection;
11
+ let ReplyRepo;
12
+ let WorkflowModel;
13
+ let workflow;
14
+
15
+ beforeEach(async () => {
16
+ app = await getApp();
17
+
18
+ db = app.db;
19
+ WorkflowModel = db.getCollection('workflows').model;
20
+ PostCollection = db.getCollection('posts');
21
+ PostRepo = PostCollection.repository;
22
+ ReplyRepo = db.getCollection('replies').repository;
23
+
24
+ workflow = await WorkflowModel.create({
25
+ title: 'test workflow',
26
+ enabled: true,
27
+ type: 'collection',
28
+ config: {
29
+ mode: 1,
30
+ collection: 'posts',
31
+ },
32
+ });
33
+ });
34
+
35
+ afterEach(async () => await app.destroy());
36
+
37
+ describe('invalid', () => {
38
+ it('no sql', async () => {
39
+ const n1 = await workflow.createNode({
40
+ type: 'sql',
41
+ config: {},
42
+ });
43
+
44
+ const post = await PostRepo.create({ values: { title: 't1' } });
45
+
46
+ await sleep(500);
47
+
48
+ const [execution] = await workflow.getExecutions();
49
+ const [sqlJob] = await execution.getJobs({ order: [['id', 'ASC']] });
50
+ expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
51
+ expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED);
52
+ });
53
+
54
+ it('empty sql', async () => {
55
+ const n1 = await workflow.createNode({
56
+ type: 'sql',
57
+ config: {
58
+ sql: '',
59
+ },
60
+ });
61
+
62
+ const post = await PostRepo.create({ values: { title: 't1' } });
63
+
64
+ await sleep(500);
65
+
66
+ const [execution] = await workflow.getExecutions();
67
+ const [sqlJob] = await execution.getJobs({ order: [['id', 'ASC']] });
68
+ expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
69
+ expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED);
70
+ });
71
+
72
+ it('invalid sql', async () => {
73
+ const n1 = await workflow.createNode({
74
+ type: 'sql',
75
+ config: {
76
+ sql: '1',
77
+ },
78
+ });
79
+
80
+ const post = await PostRepo.create({ values: { title: 't1' } });
81
+
82
+ await sleep(500);
83
+
84
+ const [execution] = await workflow.getExecutions();
85
+ const [sqlJob] = await execution.getJobs({ order: [['id', 'ASC']] });
86
+ expect(execution.status).toBe(EXECUTION_STATUS.ERROR);
87
+ expect(sqlJob.status).toBe(JOB_STATUS.ERROR);
88
+ });
89
+ });
90
+
91
+ describe('sql with variables', () => {
92
+ it('update', async () => {
93
+ const queryInterface = db.sequelize.getQueryInterface();
94
+ const n1 = await workflow.createNode({
95
+ type: 'sql',
96
+ config: {
97
+ sql: `update ${PostCollection.quotedTableName()} set ${queryInterface.quoteIdentifier('read')}={{$context.data.id}} where ${queryInterface.quoteIdentifier('id')}={{$context.data.id}}`,
98
+ },
99
+ });
100
+
101
+ const n2 = await workflow.createNode({
102
+ type: 'query',
103
+ config: {
104
+ collection: 'posts',
105
+ params: {
106
+ filter: {
107
+ id: '{{ $context.data.id }}',
108
+ }
109
+ }
110
+ },
111
+ upstreamId: n1.id,
112
+ });
113
+
114
+ await n1.setDownstream(n2);
115
+
116
+ const post = await PostRepo.create({ values: { title: 't1' } });
117
+
118
+ await sleep(500);
119
+
120
+ const [execution] = await workflow.getExecutions();
121
+ const [sqlJob, queryJob] = await execution.getJobs({ order: [['id', 'ASC']] });
122
+ expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED);
123
+ expect(queryJob.status).toBe(JOB_STATUS.RESOLVED);
124
+ expect(queryJob.result.read).toBe(post.id);
125
+ });
126
+
127
+ it('delete', async () => {
128
+ const queryInterface = db.sequelize.getQueryInterface();
129
+ const n1 = await workflow.createNode({
130
+ type: 'sql',
131
+ config: {
132
+ sql: `delete from ${PostCollection.quotedTableName()} where ${queryInterface.quoteIdentifier('id')}={{$context.data.id}};`,
133
+ },
134
+ });
135
+
136
+ const n2 = await workflow.createNode({
137
+ type: 'query',
138
+ config: {
139
+ collection: 'posts',
140
+ params: {
141
+ filter: {
142
+ id: '{{ $context.data.id }}',
143
+ }
144
+ }
145
+ },
146
+ upstreamId: n1.id,
147
+ });
148
+
149
+ await n1.setDownstream(n2);
150
+
151
+ const post = await PostRepo.create({ values: { title: 't1' } });
152
+
153
+ await sleep(500);
154
+
155
+ const [execution] = await workflow.getExecutions();
156
+ const [sqlJob, queryJob] = await execution.getJobs({ order: [['id', 'ASC']] });
157
+ expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED);
158
+ expect(queryJob.status).toBe(JOB_STATUS.RESOLVED);
159
+ expect(queryJob.result).toBeNull();
160
+ });
161
+ });
162
+ });
@@ -294,5 +294,40 @@ describe('workflow > triggers > collection', () => {
294
294
  const [job] = await execution.getJobs();
295
295
  expect(job.result.data.tags.length).toBe(1);
296
296
  });
297
+
298
+ describe('appends depth > 1', () => {
299
+ it('create with associtions', async () => {
300
+ const workflow = await WorkflowModel.create({
301
+ enabled: true,
302
+ type: 'collection',
303
+ config: {
304
+ mode: 1,
305
+ collection: 'categories',
306
+ appends: ['posts.tags'],
307
+ },
308
+ });
309
+
310
+ const tags = await TagRepo.create({ values: [{}] });
311
+ const tagIds = tags.map((item) => item.id);
312
+
313
+ const category = await CategoryRepo.create({
314
+ values: {
315
+ title: 't1',
316
+ posts: [
317
+ { title: 't1', tags: tagIds },
318
+ { title: 't2', tags: tagIds },
319
+ ],
320
+ },
321
+ });
322
+
323
+ await sleep(500);
324
+
325
+ const [execution] = await workflow.getExecutions();
326
+ expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
327
+ expect(execution.context.data.posts.length).toBe(2);
328
+ expect(execution.context.data.posts.map((item) => item.title)).toEqual(['t1', 't2']);
329
+ expect(execution.context.data.posts.map((item) => item.tags.map((tag) => tag.id))).toEqual([tagIds, tagIds]);
330
+ });
331
+ });
297
332
  });
298
333
  });
@@ -1,4 +1,5 @@
1
1
  import { JOB_STATUS } from '../constants';
2
+ import { toJSON } from '../utils';
2
3
  import type { FlowNodeModel } from '../types';
3
4
 
4
5
  export default {
@@ -7,7 +8,7 @@ export default {
7
8
 
8
9
  const { repository, model } = (<typeof FlowNodeModel>node.constructor).database.getCollection(collection);
9
10
  const options = processor.getParsedValue(params, node);
10
- const result = await repository.create({
11
+ const created = await repository.create({
11
12
  ...options,
12
13
  context: {
13
14
  executionId: processor.execution.id,
@@ -15,22 +16,23 @@ export default {
15
16
  transaction: processor.transaction,
16
17
  });
17
18
 
18
- if (result && appends.length) {
19
- const includeFields = appends.filter((field) => !result.get(field) || !result[field]);
20
- const included = await model.findByPk(result[model.primaryKeyAttribute], {
21
- attributes: [model.primaryKeyAttribute],
22
- include: includeFields,
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),
23
29
  transaction: processor.transaction,
24
30
  });
25
- includeFields.forEach((field) => {
26
- const value = included!.get(field);
27
- result.set(field, Array.isArray(value) ? value.map((item) => item.toJSON()) : value.toJSON(), { raw: true });
28
- });
29
31
  }
30
32
 
31
33
  return {
32
34
  // NOTE: get() for non-proxied instance (#380)
33
- result: result?.toJSON(),
35
+ result: toJSON(result),
34
36
  status: JOB_STATUS.RESOLVED,
35
37
  };
36
38
  },
@@ -46,6 +46,7 @@ export default function <T extends Instruction>(plugin, more: { [key: string]: T
46
46
  'destroy',
47
47
  'aggregate',
48
48
  'request',
49
+ 'sql',
49
50
  ].reduce(
50
51
  (result, key) =>
51
52
  Object.assign(result, {
@@ -30,15 +30,17 @@ export async function submit(context: Context, next) {
30
30
  }
31
31
 
32
32
  const { forms = {} } = userJob.node.config;
33
- const [formKey] = Object.keys(values.result ?? {});
33
+ const [formKey] = Object.keys(values.result ?? {}).filter(key => key !== '_');
34
+ const actionKey = values.result?._;
34
35
 
36
+ const actionItem = forms[formKey]?.actions?.find((item) => item.key === actionKey);
35
37
  // NOTE: validate status
36
38
  if (
37
39
  userJob.status !== JOB_STATUS.PENDING ||
38
40
  userJob.job.status !== JOB_STATUS.PENDING ||
39
41
  userJob.execution.status !== EXECUTION_STATUS.STARTED ||
40
42
  !userJob.workflow.enabled ||
41
- !forms[formKey]?.actions?.includes(values.status)
43
+ !actionKey || actionItem?.status == null
42
44
  ) {
43
45
  return context.throw(400);
44
46
  }
@@ -52,10 +54,17 @@ export async function submit(context: Context, next) {
52
54
  if (!assignees.includes(currentUser.id) || userJob.userId !== currentUser.id) {
53
55
  return context.throw(403);
54
56
  }
57
+ const presetValues = processor.getParsedValue(actionItem.values ?? {}, null, {
58
+ currentUser: currentUser.toJSON(),
59
+ currentRecord: values.result[formKey],
60
+ currentTime: new Date(),
61
+ });
55
62
 
56
63
  userJob.set({
57
- status: values.status,
58
- result: values.status ? values.result : Object.assign(userJob.result ?? {}, values.result),
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),
59
68
  });
60
69
 
61
70
  const handler = instruction.formTypes.get(forms[formKey].type);
@@ -72,8 +81,11 @@ export async function submit(context: Context, next) {
72
81
 
73
82
  await next();
74
83
 
84
+ userJob.job.execution = userJob.execution;
75
85
  userJob.job.latestUserJob = userJob;
76
86
 
77
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
+
78
90
  plugin.resume(userJob.job);
79
91
  }
@@ -7,7 +7,8 @@ export default async function (this: ManualInstruction, instance, { collection }
7
7
  throw new Error(`collection ${collection} for create data on manual node not found`);
8
8
  }
9
9
 
10
- const [values] = Object.values(instance.result);
10
+ const { _, ...form } = instance.result;
11
+ const [values] = Object.values(form);
11
12
  await repo.create({
12
13
  values: {
13
14
  ...((values as { [key: string]: any }) ?? {}),
@@ -7,11 +7,12 @@ export default async function (this: ManualInstruction, instance, { collection,
7
7
  throw new Error(`collection ${collection} for update data on manual node not found`);
8
8
  }
9
9
 
10
- const [values] = Object.values(instance.result as { [formKey: string]: { [key: string]: any } });
10
+ const { _, ...form } = instance.result;
11
+ const [values] = Object.values(form);
11
12
  await repo.update({
12
13
  filter: processor.getParsedValue(filter),
13
14
  values: {
14
- ...(values ?? {}),
15
+ ...((values as { [key: string]: any }) ?? {}),
15
16
  updatedBy: instance.userId,
16
17
  },
17
18
  context: {
@@ -1,5 +1,6 @@
1
1
  import Processor from '../Processor';
2
2
  import { JOB_STATUS } from '../constants';
3
+ import { toJSON } from '../utils';
3
4
  import type { FlowNodeModel } from '../types';
4
5
 
5
6
  export default {
@@ -8,8 +9,18 @@ export default {
8
9
 
9
10
  const repo = (<typeof FlowNodeModel>node.constructor).database.getRepository(collection);
10
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;
11
21
  const result = await (multiple ? repo.find : repo.findOne).call(repo, {
12
22
  ...options,
23
+ appends: appends,
13
24
  transaction: processor.transaction,
14
25
  });
15
26
 
@@ -24,7 +35,7 @@ export default {
24
35
  // e.g. Object.prototype.hasOwnProperty.call(result, 'id') // false
25
36
  // so the properties can not be get by json-templates(object-path)
26
37
  return {
27
- result: multiple ? result.map((item) => item.toJSON()) : result?.toJSON(),
38
+ result: toJSON(result),
28
39
  status: JOB_STATUS.RESOLVED,
29
40
  };
30
41
  },
@@ -45,10 +45,11 @@ async function request(config) {
45
45
  export default class implements Instruction {
46
46
  constructor(public plugin) {}
47
47
 
48
- async run(node: FlowNodeModel, input, processor: Processor) {
48
+ async run(node: FlowNodeModel, prevJob, processor: Processor) {
49
49
  const job = await processor.saveJob({
50
50
  status: JOB_STATUS.PENDING,
51
51
  nodeId: node.id,
52
+ upstreamId: prevJob?.id ?? null,
52
53
  });
53
54
 
54
55
  const config = processor.getParsedValue(node.config, node) as RequestConfig;
@@ -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,78 @@
1
+ import { Migration } from '@nocobase/server';
2
+
3
+ function findSchema(root, filter, onlyLeaf = false) {
4
+ const result = [];
5
+
6
+ if (!root) {
7
+ return result;
8
+ }
9
+
10
+ if (filter(root) && (!onlyLeaf || !root.properties)) {
11
+ result.push(root);
12
+ return result;
13
+ }
14
+
15
+ if (root.properties) {
16
+ Object.keys(root.properties).forEach((key) => {
17
+ result.push(...findSchema(root.properties[key], filter));
18
+ });
19
+ }
20
+ return result;
21
+ }
22
+
23
+ function migrateConfig(config): object {
24
+ const { forms = {}, schema = {} } = config;
25
+ const root = { properties: schema };
26
+ Object.keys(forms).forEach((key) => {
27
+ const form = forms[key];
28
+ const formSchema = findSchema(root, (item) => item.name === key);
29
+ const actions = findSchema(formSchema[0], (item) => item['x-component'] === 'Action');
30
+ form.actions = actions.map((action) => {
31
+ action['x-designer'] = 'ManualActionDesigner';
32
+ action['x-action-settings'] = {};
33
+ delete action['x-action'];
34
+ return {
35
+ status: action['x-decorator-props'].value,
36
+ values: {},
37
+ key: action.name,
38
+ };
39
+ });
40
+ });
41
+
42
+ return config;
43
+ }
44
+
45
+ export default class extends Migration {
46
+ async up() {
47
+ const match = await this.app.version.satisfies('<0.11.0-alpha.2');
48
+ if (!match) {
49
+ return;
50
+ }
51
+ const { db } = this.context;
52
+ const NodeRepo = db.getRepository('flow_nodes');
53
+ await db.sequelize.transaction(async (transaction) => {
54
+ const nodes = await NodeRepo.find({
55
+ filter: {
56
+ type: 'manual',
57
+ },
58
+ transaction,
59
+ });
60
+ console.log('%d nodes need to be migrated.', nodes.length);
61
+
62
+ await nodes.reduce(
63
+ (promise, node) =>
64
+ promise.then(() => {
65
+ node.set('config', {
66
+ ...migrateConfig(node.config),
67
+ });
68
+ node.changed('config', true);
69
+ return node.save({
70
+ silent: true,
71
+ transaction,
72
+ });
73
+ }),
74
+ Promise.resolve(),
75
+ );
76
+ });
77
+ }
78
+ }
@@ -1,5 +1,6 @@
1
1
  import { Collection, Model } from '@nocobase/database';
2
2
  import { Trigger } from '..';
3
+ import { toJSON } from '../utils';
3
4
  import type { WorkflowModel } from '../types';
4
5
 
5
6
  export interface CollectionChangeTriggerConfig {
@@ -66,24 +67,27 @@ async function handler(this: CollectionTrigger, workflow: WorkflowModel, data: M
66
67
  }
67
68
  }
68
69
 
70
+ let result = data;
71
+
69
72
  if (appends?.length && !(mode & MODE_BITMAP.DESTROY)) {
70
- const includeFields = appends.filter((field) => !data.get(field) || !data[field]);
71
- const included = await model.findByPk(data[model.primaryKeyAttribute], {
72
- attributes: [model.primaryKeyAttribute],
73
- include: includeFields,
73
+ const includeFields = appends.reduce((set, field) => {
74
+ set.add(field.split('.')[0]);
75
+ set.add(field);
76
+ return set;
77
+ }, new Set());
78
+ result = await repository.findOne({
79
+ filterByTk: data[model.primaryKeyAttribute],
80
+ appends: Array.from(includeFields),
74
81
  transaction,
75
82
  });
76
- includeFields.forEach((field) => {
77
- const value = included!.get(field);
78
- data.set(field, Array.isArray(value) ? value.map((item) => item.toJSON()) : value ? value.toJSON() : null, {
79
- raw: true,
80
- });
81
- });
82
83
  }
83
84
 
85
+ // TODO: `result.toJSON()` throws error
86
+ const json = toJSON(result);
87
+
84
88
  this.plugin.trigger(
85
89
  workflow,
86
- { data: data.toJSON() },
90
+ { data: json },
87
91
  {
88
92
  context,
89
93
  },
@@ -0,0 +1,17 @@
1
+ import { Model } from '@nocobase/database';
2
+
3
+ export function toJSON(data: Model | Model[]): object {
4
+ if (typeof data !== 'object' || !data) {
5
+ return data;
6
+ }
7
+ if (Array.isArray(data)) {
8
+ return data.map(toJSON);
9
+ }
10
+ const result = data.get();
11
+ Object.keys((<typeof Model>data.constructor).associations).forEach((key) => {
12
+ if (result[key] != null) {
13
+ result[key] = toJSON(result[key]);
14
+ }
15
+ });
16
+ return result;
17
+ }
@@ -1,2 +0,0 @@
1
- import React from 'react';
2
- export declare const DateFieldsSelect: React.FC<any>;
@@ -1,28 +0,0 @@
1
- import { observer, useForm } from '@formily/react';
2
- import { useCollectionManager, useCompile } from '@nocobase/client';
3
- import { Select } from 'antd';
4
- import React from 'react';
5
- import { useTranslation } from 'react-i18next';
6
-
7
- export const DateFieldsSelect: React.FC<any> = observer(
8
- (props) => {
9
- const { t } = useTranslation();
10
- const compile = useCompile();
11
- const { getCollectionFields } = useCollectionManager();
12
- const { values } = useForm();
13
- const fields = getCollectionFields(values?.collection);
14
-
15
- return (
16
- <Select popupMatchSelectWidth={false} placeholder={t('Select field')} {...props}>
17
- {fields
18
- .filter((field) => !field.hidden && (field.uiSchema ? field.type === 'date' : false))
19
- .map((field) => (
20
- <Select.Option key={field.name} value={field.name}>
21
- {compile(field.uiSchema?.title)}
22
- </Select.Option>
23
- ))}
24
- </Select>
25
- );
26
- },
27
- { displayName: 'DateFieldsSelect' },
28
- );