@nocobase/plugin-workflow-aggregate 0.17.0-alpha.4
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/LICENSE +661 -0
- package/README.md +9 -0
- package/README.zh-CN.md +9 -0
- package/client.d.ts +2 -0
- package/client.js +1 -0
- package/dist/client/AggregateInstruction.d.ts +212 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.js +4 -0
- package/dist/externalVersion.js +10 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +39 -0
- package/dist/locale/en-US.json +12 -0
- package/dist/locale/index.d.ts +3 -0
- package/dist/locale/index.js +39 -0
- package/dist/locale/zh-CN.json +12 -0
- package/dist/server/AggregateInstruction.d.ts +7 -0
- package/dist/server/AggregateInstruction.js +54 -0
- package/dist/server/Plugin.d.ts +6 -0
- package/dist/server/Plugin.js +42 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +33 -0
- package/package.json +24 -0
- package/server.d.ts +2 -0
- package/server.js +1 -0
- package/src/client/AggregateInstruction.tsx +399 -0
- package/src/client/index.ts +19 -0
- package/src/index.ts +2 -0
- package/src/locale/en-US.json +12 -0
- package/src/locale/index.ts +12 -0
- package/src/locale/zh-CN.json +12 -0
- package/src/server/AggregateInstruction.ts +39 -0
- package/src/server/Plugin.ts +14 -0
- package/src/server/__tests__/instruction.test.ts +298 -0
- package/src/server/index.ts +1 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { useForm } from '@formily/react';
|
|
2
|
+
import { Cascader } from 'antd';
|
|
3
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
SchemaComponentContext,
|
|
7
|
+
SchemaInitializerItemType,
|
|
8
|
+
css,
|
|
9
|
+
useCollectionDataSource,
|
|
10
|
+
useCollectionFilterOptions,
|
|
11
|
+
useCollectionManager,
|
|
12
|
+
useCompile,
|
|
13
|
+
} from '@nocobase/client';
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
FieldsSelect,
|
|
17
|
+
FilterDynamicComponent,
|
|
18
|
+
ValueBlock,
|
|
19
|
+
BaseTypeSets,
|
|
20
|
+
defaultFieldNames,
|
|
21
|
+
nodesOptions,
|
|
22
|
+
triggerOptions,
|
|
23
|
+
Instruction,
|
|
24
|
+
} from '@nocobase/plugin-workflow/client';
|
|
25
|
+
|
|
26
|
+
import { NAMESPACE, useLang } from '../locale';
|
|
27
|
+
|
|
28
|
+
function matchToManyField(field): boolean {
|
|
29
|
+
// const fieldPrefix = `${field.name}.`;
|
|
30
|
+
return ['hasMany', 'belongsToMany'].includes(field.type);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function useAssociatedFields() {
|
|
34
|
+
const compile = useCompile();
|
|
35
|
+
return [nodesOptions, triggerOptions].map((item) => {
|
|
36
|
+
const children = item
|
|
37
|
+
.useOptions({
|
|
38
|
+
types: [matchToManyField],
|
|
39
|
+
appends: null,
|
|
40
|
+
depth: 4,
|
|
41
|
+
})
|
|
42
|
+
?.filter(Boolean);
|
|
43
|
+
return {
|
|
44
|
+
label: compile(item.label),
|
|
45
|
+
value: item.value,
|
|
46
|
+
key: item.value,
|
|
47
|
+
children: compile(children),
|
|
48
|
+
disabled: children && !children.length,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function AssociatedConfig({ value, onChange, ...props }): JSX.Element {
|
|
54
|
+
const { setValuesIn } = useForm();
|
|
55
|
+
const { getCollection } = useCollectionManager();
|
|
56
|
+
const baseOptions = useAssociatedFields();
|
|
57
|
+
const [options, setOptions] = useState(baseOptions);
|
|
58
|
+
|
|
59
|
+
const { associatedKey = '', name: fieldName } = value ?? {};
|
|
60
|
+
let p = [];
|
|
61
|
+
const matched = associatedKey.match(/^{{(.*)}}$/);
|
|
62
|
+
if (matched) {
|
|
63
|
+
p = [...matched[1].trim().split('.').slice(0, -1), fieldName];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const loadData = async (selectedOptions) => {
|
|
67
|
+
const option = selectedOptions[selectedOptions.length - 1];
|
|
68
|
+
if (!option.children?.length && !option.isLeaf && option.loadChildren) {
|
|
69
|
+
await option.loadChildren(option);
|
|
70
|
+
setOptions((prev) => [...prev]);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
const run = async () => {
|
|
76
|
+
if (!p || options.length <= 1) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
let prevOption = null;
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < p.length; i++) {
|
|
82
|
+
const key = p[i];
|
|
83
|
+
try {
|
|
84
|
+
if (i === 0) {
|
|
85
|
+
prevOption = options.find((item) => item.value === key);
|
|
86
|
+
} else {
|
|
87
|
+
if (prevOption.loadChildren && !prevOption.children?.length) {
|
|
88
|
+
await prevOption.loadChildren(prevOption);
|
|
89
|
+
}
|
|
90
|
+
prevOption = prevOption.children.find((item) => item.value === key);
|
|
91
|
+
}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.error(err);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
setOptions([...options]);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
run();
|
|
100
|
+
// NOTE: watch `options.length` and it only happens once
|
|
101
|
+
}, [value, options.length]);
|
|
102
|
+
|
|
103
|
+
const onSelectChange = useCallback(
|
|
104
|
+
(path, option) => {
|
|
105
|
+
if (!path?.length) {
|
|
106
|
+
setValuesIn('collection', null);
|
|
107
|
+
onChange({});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// const associationFieldName = path.pop();
|
|
112
|
+
const { field } = option.pop();
|
|
113
|
+
if (!field || !['hasMany', 'belongsToMany'].includes(field.type)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// need to get:
|
|
117
|
+
// * source collection (from node.config)
|
|
118
|
+
// * target collection (from field name)
|
|
119
|
+
const { collectionName, target, name } = field;
|
|
120
|
+
|
|
121
|
+
const collection = getCollection(collectionName);
|
|
122
|
+
const primaryKeyField = collection.fields.find((f) => f.primaryKey);
|
|
123
|
+
|
|
124
|
+
setValuesIn('collection', target);
|
|
125
|
+
|
|
126
|
+
onChange({
|
|
127
|
+
name,
|
|
128
|
+
// primary key data path
|
|
129
|
+
associatedKey: `{{${path.slice(0, -1).join('.')}.${primaryKeyField.name}}}`,
|
|
130
|
+
// data associated collection name
|
|
131
|
+
associatedCollection: collectionName,
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
[onChange],
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return <Cascader {...props} value={p} options={options} onChange={onSelectChange} loadData={loadData as any} />;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// based on collection:
|
|
141
|
+
// { collection, field }
|
|
142
|
+
|
|
143
|
+
// based on data associated collection
|
|
144
|
+
// { key: '{{$context.data.id}}', collection: "collection.association", field }
|
|
145
|
+
// select data based
|
|
146
|
+
|
|
147
|
+
export default class extends Instruction {
|
|
148
|
+
title = `{{t("Aggregate", { ns: "${NAMESPACE}" })}}`;
|
|
149
|
+
type = 'aggregate';
|
|
150
|
+
group = 'collection';
|
|
151
|
+
description = `{{t("Counting, summing, finding maximum, minimum, and average values for multiple records of a collection or associated data of a record.", { ns: "${NAMESPACE}" })}}`;
|
|
152
|
+
fieldset = {
|
|
153
|
+
aggregator: {
|
|
154
|
+
type: 'string',
|
|
155
|
+
title: `{{t("Aggregator function", { ns: "${NAMESPACE}" })}}`,
|
|
156
|
+
'x-decorator': 'FormItem',
|
|
157
|
+
'x-component': 'Radio.Group',
|
|
158
|
+
enum: [
|
|
159
|
+
{ label: 'COUNT', value: 'count' },
|
|
160
|
+
{ label: 'SUM', value: 'sum' },
|
|
161
|
+
{ label: 'AVG', value: 'avg' },
|
|
162
|
+
{ label: 'MIN', value: 'min' },
|
|
163
|
+
{ label: 'MAX', value: 'max' },
|
|
164
|
+
],
|
|
165
|
+
required: true,
|
|
166
|
+
default: 'count',
|
|
167
|
+
},
|
|
168
|
+
associated: {
|
|
169
|
+
type: 'boolean',
|
|
170
|
+
title: `{{t("Target type", { ns: "${NAMESPACE}" })}}`,
|
|
171
|
+
'x-decorator': 'FormItem',
|
|
172
|
+
'x-component': 'Radio.Group',
|
|
173
|
+
enum: [
|
|
174
|
+
{ label: `{{t("Data of collection", { ns: "${NAMESPACE}" })}}`, value: false },
|
|
175
|
+
{ label: `{{t("Data of associated collection", { ns: "${NAMESPACE}" })}}`, value: true },
|
|
176
|
+
],
|
|
177
|
+
required: true,
|
|
178
|
+
default: false,
|
|
179
|
+
'x-reactions': [
|
|
180
|
+
{
|
|
181
|
+
target: 'collection',
|
|
182
|
+
effects: ['onFieldValueChange'],
|
|
183
|
+
fulfill: {
|
|
184
|
+
state: {
|
|
185
|
+
value: null,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
target: 'association',
|
|
191
|
+
effects: ['onFieldValueChange'],
|
|
192
|
+
fulfill: {
|
|
193
|
+
state: {
|
|
194
|
+
value: null,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
collectionField: {
|
|
201
|
+
type: 'void',
|
|
202
|
+
'x-decorator': 'SchemaComponentContext.Provider',
|
|
203
|
+
'x-decorator-props': {
|
|
204
|
+
value: { designable: false },
|
|
205
|
+
},
|
|
206
|
+
'x-component': 'Grid',
|
|
207
|
+
properties: {
|
|
208
|
+
row: {
|
|
209
|
+
type: 'void',
|
|
210
|
+
'x-component': 'Grid.Row',
|
|
211
|
+
properties: {
|
|
212
|
+
target: {
|
|
213
|
+
type: 'void',
|
|
214
|
+
'x-component': 'Grid.Col',
|
|
215
|
+
properties: {
|
|
216
|
+
collection: {
|
|
217
|
+
type: 'string',
|
|
218
|
+
required: true,
|
|
219
|
+
'x-decorator': 'FormItem',
|
|
220
|
+
'x-component': 'CollectionSelect',
|
|
221
|
+
title: `{{t("Data of collection", { ns: "${NAMESPACE}" })}}`,
|
|
222
|
+
'x-reactions': [
|
|
223
|
+
{
|
|
224
|
+
dependencies: ['associated'],
|
|
225
|
+
fulfill: {
|
|
226
|
+
state: {
|
|
227
|
+
display: '{{$deps[0] ? "hidden" : "visible"}}',
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
target: 'params.field',
|
|
233
|
+
effects: ['onFieldValueChange'],
|
|
234
|
+
fulfill: {
|
|
235
|
+
state: {
|
|
236
|
+
value: null,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
target: 'params.filter',
|
|
242
|
+
effects: ['onFieldValueChange'],
|
|
243
|
+
fulfill: {
|
|
244
|
+
state: {
|
|
245
|
+
value: null,
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
association: {
|
|
252
|
+
type: 'object',
|
|
253
|
+
title: `{{t("Data of associated collection", { ns: "${NAMESPACE}" })}}`,
|
|
254
|
+
'x-decorator': 'FormItem',
|
|
255
|
+
'x-component': 'AssociatedConfig',
|
|
256
|
+
'x-component-props': {
|
|
257
|
+
changeOnSelect: true,
|
|
258
|
+
},
|
|
259
|
+
'x-reactions': [
|
|
260
|
+
{
|
|
261
|
+
dependencies: ['associated'],
|
|
262
|
+
fulfill: {
|
|
263
|
+
state: {
|
|
264
|
+
visible: '{{!!$deps[0]}}',
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
required: true,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
field: {
|
|
274
|
+
type: 'void',
|
|
275
|
+
'x-component': 'Grid.Col',
|
|
276
|
+
properties: {
|
|
277
|
+
'params.field': {
|
|
278
|
+
type: 'string',
|
|
279
|
+
title: `{{t("Field to aggregate", { ns: "${NAMESPACE}" })}}`,
|
|
280
|
+
'x-decorator': 'FormItem',
|
|
281
|
+
'x-component': 'FieldsSelect',
|
|
282
|
+
'x-component-props': {
|
|
283
|
+
filter(field) {
|
|
284
|
+
return (
|
|
285
|
+
!field.hidden &&
|
|
286
|
+
field.interface &&
|
|
287
|
+
!['belongsTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(field.type)
|
|
288
|
+
);
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
required: true,
|
|
292
|
+
'x-reactions': [
|
|
293
|
+
{
|
|
294
|
+
dependencies: ['collection'],
|
|
295
|
+
fulfill: {
|
|
296
|
+
state: {
|
|
297
|
+
visible: '{{!!$deps[0]}}',
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
params: {
|
|
310
|
+
type: 'object',
|
|
311
|
+
properties: {
|
|
312
|
+
distinct: {
|
|
313
|
+
type: 'boolean',
|
|
314
|
+
title: `{{t("Distinct", { ns: "${NAMESPACE}" })}}`,
|
|
315
|
+
'x-decorator': 'FormItem',
|
|
316
|
+
'x-component': 'Checkbox',
|
|
317
|
+
'x-reactions': [
|
|
318
|
+
{
|
|
319
|
+
dependencies: ['collection', 'aggregator'],
|
|
320
|
+
fulfill: {
|
|
321
|
+
state: {
|
|
322
|
+
visible: '{{!!$deps[0] && ["count"].includes($deps[1])}}',
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
},
|
|
328
|
+
filter: {
|
|
329
|
+
type: 'object',
|
|
330
|
+
title: '{{t("Filter")}}',
|
|
331
|
+
'x-decorator': 'FormItem',
|
|
332
|
+
'x-component': 'Filter',
|
|
333
|
+
'x-component-props': {
|
|
334
|
+
useProps() {
|
|
335
|
+
const { values } = useForm();
|
|
336
|
+
const options = useCollectionFilterOptions(values?.collection);
|
|
337
|
+
return {
|
|
338
|
+
options,
|
|
339
|
+
className: css`
|
|
340
|
+
position: relative;
|
|
341
|
+
width: 100%;
|
|
342
|
+
`,
|
|
343
|
+
};
|
|
344
|
+
},
|
|
345
|
+
dynamicComponent: 'FilterDynamicComponent',
|
|
346
|
+
},
|
|
347
|
+
'x-reactions': [
|
|
348
|
+
{
|
|
349
|
+
dependencies: ['collection'],
|
|
350
|
+
fulfill: {
|
|
351
|
+
state: {
|
|
352
|
+
visible: '{{!!$deps[0]}}',
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
scope = {
|
|
362
|
+
useCollectionDataSource,
|
|
363
|
+
};
|
|
364
|
+
components = {
|
|
365
|
+
SchemaComponentContext,
|
|
366
|
+
FilterDynamicComponent,
|
|
367
|
+
FieldsSelect,
|
|
368
|
+
ValueBlock,
|
|
369
|
+
AssociatedConfig,
|
|
370
|
+
};
|
|
371
|
+
useVariables({ key, title }, { types, fieldNames = defaultFieldNames }) {
|
|
372
|
+
if (
|
|
373
|
+
types &&
|
|
374
|
+
!types.some((type) => type in BaseTypeSets || Object.values(BaseTypeSets).some((set) => set.has(type)))
|
|
375
|
+
) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
[fieldNames.value]: key,
|
|
380
|
+
[fieldNames.label]: title,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
useInitializers(node): SchemaInitializerItemType | null {
|
|
384
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
385
|
+
const resultTitle = useLang('Query result');
|
|
386
|
+
if (!node.config.collection) {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
name: `#${node.id}`,
|
|
392
|
+
type: 'item',
|
|
393
|
+
title: node.title ?? `#${node.id}`,
|
|
394
|
+
Component: ValueBlock.Initializer,
|
|
395
|
+
node,
|
|
396
|
+
resultTitle,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Plugin } from '@nocobase/client';
|
|
2
|
+
import WorkflowPlugin from '@nocobase/plugin-workflow/client';
|
|
3
|
+
|
|
4
|
+
import AggregateInstruction from './AggregateInstruction';
|
|
5
|
+
|
|
6
|
+
export default class extends Plugin {
|
|
7
|
+
async afterAdd() {
|
|
8
|
+
// await this.app.pm.add()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async beforeLoad() {}
|
|
12
|
+
|
|
13
|
+
// You can get and modify the app instance here
|
|
14
|
+
async load() {
|
|
15
|
+
const workflow = this.app.pm.get('workflow') as WorkflowPlugin;
|
|
16
|
+
const aggregateInstruction = new AggregateInstruction();
|
|
17
|
+
workflow.instructions.register(aggregateInstruction.type, aggregateInstruction);
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Aggregate": "Aggregate",
|
|
3
|
+
"Counting, summing, finding maximum, minimum, and average values for multiple records of a collection or associated data of a record.":
|
|
4
|
+
"Counting, summing, finding maximum, minimum, and average values for multiple records of a collection or associated data of a record.",
|
|
5
|
+
"Aggregator function": "Aggregator function",
|
|
6
|
+
"Target type": "Target type",
|
|
7
|
+
"Data of collection": "Data of collection",
|
|
8
|
+
"Data of associated collection": "Data of associated collection",
|
|
9
|
+
"Field to aggregate": "Field to aggregate",
|
|
10
|
+
"Distinct": "Distinct",
|
|
11
|
+
"Query result": "Query result"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useTranslation } from 'react-i18next';
|
|
2
|
+
|
|
3
|
+
export const NAMESPACE = 'workflow-aggregate';
|
|
4
|
+
|
|
5
|
+
export function useLang(key: string, options = {}) {
|
|
6
|
+
const { t } = usePluginTranslation(options);
|
|
7
|
+
return t(key);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function usePluginTranslation(options) {
|
|
11
|
+
return useTranslation(NAMESPACE, options);
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Aggregate": "聚合查询",
|
|
3
|
+
"Counting, summing, finding maximum, minimum, and average values for multiple records of a collection or associated data of a record.":
|
|
4
|
+
"对一个数据表里的多条数据或者一条数据里的关系数据进行统计、求和、求最大值、最小值、平均值。",
|
|
5
|
+
"Aggregator function": "聚合函数",
|
|
6
|
+
"Target type": "目标类型",
|
|
7
|
+
"Data of collection": "数据表数据",
|
|
8
|
+
"Data of associated collection": "关联数据表数据",
|
|
9
|
+
"Field to aggregate": "聚合字段",
|
|
10
|
+
"Distinct": "去重",
|
|
11
|
+
"Query result": "查询结果"
|
|
12
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { BelongsToManyRepository, DataTypes, HasManyRepository } from '@nocobase/database';
|
|
2
|
+
import { Processor, Instruction, JOB_STATUS, FlowNodeModel } from '@nocobase/plugin-workflow';
|
|
3
|
+
|
|
4
|
+
const aggregators = {
|
|
5
|
+
count: 'count',
|
|
6
|
+
sum: 'sum',
|
|
7
|
+
avg: 'avg',
|
|
8
|
+
min: 'min',
|
|
9
|
+
max: 'max',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default class extends Instruction {
|
|
13
|
+
async run(node: FlowNodeModel, input, processor: Processor) {
|
|
14
|
+
const { aggregator, associated, collection, association = {}, params = {} } = node.config;
|
|
15
|
+
const options = processor.getParsedValue(params, node.id);
|
|
16
|
+
const { database } = <typeof FlowNodeModel>node.constructor;
|
|
17
|
+
const repo = associated
|
|
18
|
+
? database.getRepository<HasManyRepository | BelongsToManyRepository>(
|
|
19
|
+
`${association?.associatedCollection}.${association.name}`,
|
|
20
|
+
processor.getParsedValue(association?.associatedKey, node.id),
|
|
21
|
+
)
|
|
22
|
+
: database.getRepository(collection);
|
|
23
|
+
|
|
24
|
+
if (!options.dataType && aggregator === 'avg') {
|
|
25
|
+
options.dataType = DataTypes.DOUBLE;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const result = await repo.aggregate({
|
|
29
|
+
...options,
|
|
30
|
+
method: aggregators[aggregator],
|
|
31
|
+
transaction: processor.transaction,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
result: options.dataType === DataTypes.DOUBLE ? Number(result) : result,
|
|
36
|
+
status: JOB_STATUS.RESOLVED,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Plugin } from '@nocobase/server';
|
|
2
|
+
import WorkflowPlugin from '@nocobase/plugin-workflow';
|
|
3
|
+
|
|
4
|
+
import AggregateInstruction from './AggregateInstruction';
|
|
5
|
+
|
|
6
|
+
export default class extends Plugin {
|
|
7
|
+
workflow: WorkflowPlugin;
|
|
8
|
+
|
|
9
|
+
async load() {
|
|
10
|
+
const workflowPlugin = this.app.getPlugin('workflow') as WorkflowPlugin;
|
|
11
|
+
this.workflow = workflowPlugin;
|
|
12
|
+
workflowPlugin.instructions.register('aggregate', new AggregateInstruction(workflowPlugin));
|
|
13
|
+
}
|
|
14
|
+
}
|