@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.
- package/lib/client/AddButton.js +13 -4
- package/lib/client/Branch.js +4 -2
- package/lib/client/CanvasContent.js +6 -4
- package/lib/client/ExecutionCanvas.js +18 -7
- package/lib/client/ExecutionPage.js +4 -2
- package/lib/client/WorkflowCanvas.js +16 -6
- package/lib/client/WorkflowPage.js +4 -2
- package/lib/client/WorkflowProvider.js +2 -2
- package/lib/client/components/CollectionBlockInitializer.js +3 -3
- package/lib/client/components/CollectionFieldset.js +7 -1
- package/lib/client/components/FieldsSelect.js +4 -1
- package/lib/client/components/NodeDescription.js +36 -22
- package/lib/client/index.js +3 -3
- 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 +6 -1
- package/lib/client/nodes/aggregate.js +4 -3
- package/lib/client/nodes/calculation.d.ts +5 -3
- package/lib/client/nodes/calculation.js +6 -5
- package/lib/client/nodes/condition.d.ts +1 -7
- package/lib/client/nodes/condition.js +12 -23
- package/lib/client/nodes/create.d.ts +2 -4
- package/lib/client/nodes/create.js +1 -3
- package/lib/client/nodes/index.d.ts +1 -2
- package/lib/client/nodes/index.js +24 -24
- package/lib/client/nodes/loop.js +19 -28
- package/lib/client/nodes/manual/FormBlockInitializer.js +6 -5
- package/lib/client/nodes/manual/SchemaConfig.d.ts +1 -2
- package/lib/client/nodes/manual/SchemaConfig.js +175 -21
- package/lib/client/nodes/manual/WorkflowTodo.js +39 -46
- package/lib/client/nodes/manual/forms/create.js +8 -1
- package/lib/client/nodes/manual/forms/custom.js +11 -4
- 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 +7 -4
- package/lib/client/nodes/query.d.ts +2 -5
- package/lib/client/nodes/query.js +1 -3
- package/lib/client/nodes/sql.d.ts +26 -0
- package/lib/client/{triggers/schedule/DateFieldsSelect.js → nodes/sql.js} +37 -46
- package/lib/client/schemas/collection.d.ts +2 -3
- package/lib/client/schemas/collection.js +8 -7
- package/lib/client/style.d.ts +18 -13
- package/lib/client/style.js +312 -289
- package/lib/client/triggers/collection.d.ts +9 -10
- package/lib/client/triggers/collection.js +4 -0
- package/lib/client/triggers/index.d.ts +2 -3
- package/lib/client/triggers/index.js +10 -5
- package/lib/client/triggers/schedule/OnField.js +35 -23
- package/lib/client/triggers/schedule/ScheduleConfig.js +7 -7
- package/lib/client/triggers/schedule/index.d.ts +0 -1
- package/lib/client/triggers/schedule/index.js +31 -19
- package/lib/client/variable.d.ts +29 -11
- package/lib/client/variable.js +39 -24
- package/lib/server/Plugin.d.ts +1 -3
- package/lib/server/Plugin.js +10 -6
- package/lib/server/Processor.d.ts +1 -1
- package/lib/server/Processor.js +2 -2
- package/lib/server/instructions/create.d.ts +1 -1
- package/lib/server/instructions/create.js +13 -13
- package/lib/server/instructions/index.js +1 -1
- 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/update.js +7 -1
- package/lib/server/instructions/query.js +8 -1
- package/lib/server/instructions/request.d.ts +1 -1
- package/lib/server/instructions/request.js +4 -2
- package/lib/server/instructions/sql.d.ts +12 -0
- package/lib/server/instructions/sql.js +34 -0
- 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.js +13 -11
- package/lib/server/utils.d.ts +2 -0
- package/lib/server/utils.js +21 -0
- package/package.json +12 -11
- package/src/client/AddButton.tsx +17 -5
- package/src/client/Branch.tsx +4 -2
- package/src/client/CanvasContent.tsx +6 -4
- package/src/client/ExecutionCanvas.tsx +11 -13
- package/src/client/ExecutionPage.tsx +3 -2
- package/src/client/WorkflowCanvas.tsx +14 -13
- package/src/client/WorkflowPage.tsx +3 -2
- package/src/client/WorkflowProvider.tsx +2 -2
- package/src/client/components/CollectionBlockInitializer.tsx +3 -3
- package/src/client/components/CollectionFieldset.tsx +5 -3
- package/src/client/components/FieldsSelect.tsx +5 -1
- package/src/client/components/NodeDescription.tsx +30 -23
- package/src/client/index.tsx +3 -3
- package/src/client/locale/zh-CN.ts +8 -2
- package/src/client/nodes/aggregate.tsx +4 -4
- package/src/client/nodes/calculation.tsx +4 -5
- package/src/client/nodes/condition.tsx +7 -34
- package/src/client/nodes/create.tsx +0 -1
- package/src/client/nodes/index.tsx +21 -25
- package/src/client/nodes/loop.tsx +16 -31
- package/src/client/nodes/manual/FormBlockInitializer.tsx +6 -5
- package/src/client/nodes/manual/SchemaConfig.tsx +162 -18
- package/src/client/nodes/manual/WorkflowTodo.tsx +43 -47
- package/src/client/nodes/manual/forms/create.tsx +5 -1
- package/src/client/nodes/manual/forms/custom.tsx +7 -3
- package/src/client/nodes/manual/forms/update.tsx +5 -1
- package/src/client/nodes/manual/index.tsx +5 -5
- package/src/client/nodes/parallel.tsx +6 -5
- package/src/client/nodes/query.tsx +0 -1
- package/src/client/nodes/sql.tsx +37 -0
- package/src/client/schemas/collection.ts +6 -6
- package/src/client/style.tsx +324 -289
- package/src/client/triggers/collection.tsx +4 -0
- package/src/client/triggers/index.tsx +14 -10
- package/src/client/triggers/schedule/OnField.tsx +29 -15
- package/src/client/triggers/schedule/ScheduleConfig.tsx +21 -19
- package/src/client/triggers/schedule/index.tsx +25 -19
- package/src/client/variable.tsx +48 -26
- package/src/server/Plugin.ts +13 -9
- package/src/server/Processor.ts +2 -2
- package/src/server/__tests__/collections/categories.ts +4 -0
- package/src/server/__tests__/instructions/manual.test.ts +391 -72
- package/src/server/__tests__/instructions/request.test.ts +30 -0
- package/src/server/__tests__/instructions/sql.test.ts +162 -0
- package/src/server/__tests__/triggers/collection.test.ts +35 -0
- package/src/server/instructions/create.ts +13 -11
- package/src/server/instructions/index.ts +1 -0
- package/src/server/instructions/manual/actions.ts +16 -4
- package/src/server/instructions/manual/forms/create.ts +2 -1
- package/src/server/instructions/manual/forms/update.ts +3 -2
- package/src/server/instructions/query.ts +12 -1
- package/src/server/instructions/request.ts +2 -1
- package/src/server/instructions/sql.ts +25 -0
- package/src/server/migrations/20230710115902-manual-action-values.ts +78 -0
- package/src/server/triggers/collection.ts +15 -11
- package/src/server/utils.ts +17 -0
- package/lib/client/triggers/schedule/DateFieldsSelect.d.ts +0 -2
- 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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
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:
|
|
35
|
+
result: toJSON(result),
|
|
34
36
|
status: JOB_STATUS.RESOLVED,
|
|
35
37
|
};
|
|
36
38
|
},
|
|
@@ -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
|
-
!
|
|
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:
|
|
58
|
-
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
|
|
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
|
|
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:
|
|
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,
|
|
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.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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:
|
|
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,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
|
-
);
|