@nocobase/plugin-workflow-dynamic-calculation 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/DynamicCalculation.d.ts +72 -0
- package/dist/client/DynamicExpression.d.ts +6 -0
- package/dist/client/Provider.d.ts +2 -0
- package/dist/client/expression.d.ts +3 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/index.js +1 -0
- package/dist/externalVersion.js +13 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +39 -0
- package/dist/locale/en-US.json +11 -0
- package/dist/locale/index.d.ts +3 -0
- package/dist/locale/index.js +39 -0
- package/dist/locale/zh-CN.json +11 -0
- package/dist/server/DynamicCalculation.d.ts +7 -0
- package/dist/server/DynamicCalculation.js +62 -0
- package/dist/server/Plugin.d.ts +6 -0
- package/dist/server/Plugin.js +36 -0
- package/dist/server/expression-field.d.ts +7 -0
- package/dist/server/expression-field.js +32 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +33 -0
- package/package.json +33 -0
- package/server.d.ts +2 -0
- package/server.js +1 -0
- package/src/client/DynamicCalculation.tsx +98 -0
- package/src/client/DynamicExpression.tsx +57 -0
- package/src/client/Provider.tsx +22 -0
- package/src/client/expression.tsx +24 -0
- package/src/client/index.ts +28 -0
- package/src/index.ts +2 -0
- package/src/locale/en-US.json +11 -0
- package/src/locale/index.ts +12 -0
- package/src/locale/zh-CN.json +11 -0
- package/src/server/DynamicCalculation.ts +29 -0
- package/src/server/Plugin.ts +19 -0
- package/src/server/__tests__/collections/categories.ts +19 -0
- package/src/server/__tests__/instruction.test.ts +115 -0
- package/src/server/expression-field.ts +11 -0
- package/src/server/index.ts +1 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { CollectionManagerContext, useCollectionManager } from '@nocobase/client';
|
|
4
|
+
|
|
5
|
+
import expression from './expression';
|
|
6
|
+
|
|
7
|
+
export function Provider(props) {
|
|
8
|
+
const cmCtx = useCollectionManager();
|
|
9
|
+
return (
|
|
10
|
+
<CollectionManagerContext.Provider
|
|
11
|
+
value={{
|
|
12
|
+
...cmCtx,
|
|
13
|
+
interfaces: {
|
|
14
|
+
...cmCtx.interfaces,
|
|
15
|
+
expression,
|
|
16
|
+
},
|
|
17
|
+
}}
|
|
18
|
+
>
|
|
19
|
+
{props.children}
|
|
20
|
+
</CollectionManagerContext.Provider>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { IField, interfacesProperties } from '@nocobase/client';
|
|
2
|
+
|
|
3
|
+
import { NAMESPACE } from '../locale';
|
|
4
|
+
|
|
5
|
+
const { defaultProps } = interfacesProperties;
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
name: 'expression',
|
|
9
|
+
type: 'string',
|
|
10
|
+
group: 'advanced',
|
|
11
|
+
order: 1,
|
|
12
|
+
title: `{{t("Expression", { ns: "${NAMESPACE}" })}}`,
|
|
13
|
+
description: `{{t("An expression for calculation in each rows", { ns: "${NAMESPACE}" })}}`,
|
|
14
|
+
sortable: true,
|
|
15
|
+
default: {
|
|
16
|
+
type: 'text',
|
|
17
|
+
uiSchema: {
|
|
18
|
+
'x-component': 'DynamicExpression',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
properties: {
|
|
22
|
+
...defaultProps,
|
|
23
|
+
},
|
|
24
|
+
} as IField;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Plugin } from '@nocobase/client';
|
|
2
|
+
import WorkflowPlugin from '@nocobase/plugin-workflow/client';
|
|
3
|
+
|
|
4
|
+
import { Provider } from './Provider';
|
|
5
|
+
import DynamicCalculation from './DynamicCalculation';
|
|
6
|
+
import { DynamicExpression } from './DynamicExpression';
|
|
7
|
+
|
|
8
|
+
export default class extends Plugin {
|
|
9
|
+
async afterAdd() {
|
|
10
|
+
// await this.app.pm.add()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async beforeLoad() {}
|
|
14
|
+
|
|
15
|
+
// You can get and modify the app instance here
|
|
16
|
+
async load() {
|
|
17
|
+
this.app.addProvider(Provider);
|
|
18
|
+
const workflow = this.app.pm.get('workflow') as WorkflowPlugin;
|
|
19
|
+
const dynamicCalculation = new DynamicCalculation();
|
|
20
|
+
workflow.instructions.register(dynamicCalculation.type, dynamicCalculation);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
addComponents() {
|
|
24
|
+
this.app.addComponents({
|
|
25
|
+
DynamicExpression,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Dynamic Calculation": "Dynamic Calculation",
|
|
3
|
+
"Calculate an expression based on a calculation engine and obtain a value as the result. Variables in the upstream nodes can be used in the expression. The expression is dynamic one from an expression collections.": "Calculate an expression based on a calculation engine and obtain a value as the result. Variables in the upstream nodes can be used in the expression. The expression is dynamic one from an expression collections.",
|
|
4
|
+
"Select dynamic expression": "Select dynamic expression",
|
|
5
|
+
"Select the dynamic expression queried from the upstream node. You need to query it from an expression collection.": "Select the dynamic expression queried from the upstream node. You need to query it from an expression collection.",
|
|
6
|
+
"Variable datasource": "Variable datasource",
|
|
7
|
+
"Dynamic expression": "Dynamic expression",
|
|
8
|
+
"An expression for calculation in each rows": "An expression for calculation in each rows",
|
|
9
|
+
"Unconfigured": "Unconfigured",
|
|
10
|
+
"Calculation result": "Calculation result"
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useTranslation } from 'react-i18next';
|
|
2
|
+
|
|
3
|
+
export const NAMESPACE = 'workflow-dynamic-calculation';
|
|
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,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Dynamic Calculation": "动态表达式计算",
|
|
3
|
+
"Calculate an expression based on a calculation engine and obtain a value as the result. Variables in the upstream nodes can be used in the expression. The expression is dynamic one from an expression collections.": "基于计算引擎计算表达式并获取值作为结果。可以在表达式中使用上游节点的变量。表达式是从表达式表中动态获取的。",
|
|
4
|
+
"Select dynamic expression": "选择动态表达式",
|
|
5
|
+
"Select the dynamic expression queried from the upstream node. You need to query it from an expression collection.": "从上游节点中选择查询出来的动态表达式。你需要从动态表达式类型的数据表中查询。",
|
|
6
|
+
"Variable datasource": "变量数据源",
|
|
7
|
+
"Dynamic expression": "动态表达式",
|
|
8
|
+
"An expression for calculation in each rows": "每行数据计算规则不同时使用",
|
|
9
|
+
"Unconfigured": "未配置",
|
|
10
|
+
"Calculation result": "运算结果"
|
|
11
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { parse } from '@nocobase/utils';
|
|
2
|
+
import { FlowNodeModel, Instruction, JOB_STATUS, Processor } from '@nocobase/plugin-workflow';
|
|
3
|
+
import evaluators, { Evaluator } from '@nocobase/evaluators';
|
|
4
|
+
|
|
5
|
+
export class DynamicCalculation extends Instruction {
|
|
6
|
+
async run(node: FlowNodeModel, prevJob, processor: Processor) {
|
|
7
|
+
let { engine = 'math.js', expression = '' } = node.config;
|
|
8
|
+
let scope = processor.getScope(node.id);
|
|
9
|
+
const parsed = parse(expression)(scope) ?? {};
|
|
10
|
+
engine = parsed.engine;
|
|
11
|
+
expression = parsed.expression;
|
|
12
|
+
scope = parse(node.config.scope ?? '')(scope) ?? {};
|
|
13
|
+
|
|
14
|
+
const evaluator = <Evaluator | undefined>evaluators.get(engine);
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const result = evaluator && expression ? evaluator(expression, scope) : null;
|
|
18
|
+
return {
|
|
19
|
+
result,
|
|
20
|
+
status: JOB_STATUS.RESOLVED,
|
|
21
|
+
};
|
|
22
|
+
} catch (e) {
|
|
23
|
+
return {
|
|
24
|
+
result: e.toString(),
|
|
25
|
+
status: JOB_STATUS.ERROR,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Plugin } from '@nocobase/server';
|
|
2
|
+
import WorkflowPlugin from '@nocobase/plugin-workflow';
|
|
3
|
+
|
|
4
|
+
import { ExpressionField } from './expression-field';
|
|
5
|
+
import { DynamicCalculation } from './DynamicCalculation';
|
|
6
|
+
|
|
7
|
+
export default class extends Plugin {
|
|
8
|
+
workflow: WorkflowPlugin;
|
|
9
|
+
|
|
10
|
+
async load() {
|
|
11
|
+
this.db.registerFieldTypes({
|
|
12
|
+
expression: ExpressionField,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const workflowPlugin = this.app.getPlugin('workflow') as WorkflowPlugin;
|
|
16
|
+
this.workflow = workflowPlugin;
|
|
17
|
+
workflowPlugin.instructions.register('dynamic-calculation', new DynamicCalculation(workflowPlugin));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { extendCollection } from '@nocobase/database';
|
|
2
|
+
|
|
3
|
+
export default extendCollection({
|
|
4
|
+
name: 'categories',
|
|
5
|
+
fields: [
|
|
6
|
+
{
|
|
7
|
+
type: 'string',
|
|
8
|
+
name: 'engine',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
type: 'string',
|
|
12
|
+
name: 'collection',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
type: 'text',
|
|
16
|
+
name: 'expression',
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
import { MockServer } from '@nocobase/test';
|
|
4
|
+
import { MockDatabase } from '@nocobase/database';
|
|
5
|
+
import { getApp, sleep } from '@nocobase/plugin-workflow-test';
|
|
6
|
+
|
|
7
|
+
import Plugin from '..';
|
|
8
|
+
|
|
9
|
+
describe('workflow > instructions > calculation', () => {
|
|
10
|
+
let app: MockServer;
|
|
11
|
+
let db: MockDatabase;
|
|
12
|
+
let PostRepo;
|
|
13
|
+
let CategoryRepo;
|
|
14
|
+
let WorkflowModel;
|
|
15
|
+
let workflow;
|
|
16
|
+
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
app = await getApp({
|
|
19
|
+
plugins: [Plugin],
|
|
20
|
+
collectionsPath: path.join(__dirname, 'collections'),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
db = app.db;
|
|
24
|
+
WorkflowModel = db.getCollection('workflows').model;
|
|
25
|
+
PostRepo = db.getCollection('posts').repository;
|
|
26
|
+
CategoryRepo = db.getCollection('categories').repository;
|
|
27
|
+
|
|
28
|
+
workflow = await WorkflowModel.create({
|
|
29
|
+
title: 'test workflow',
|
|
30
|
+
enabled: true,
|
|
31
|
+
type: 'collection',
|
|
32
|
+
config: {
|
|
33
|
+
mode: 1,
|
|
34
|
+
collection: 'posts',
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => app.destroy());
|
|
40
|
+
|
|
41
|
+
describe('dynamic expression', () => {
|
|
42
|
+
it('dynamic expression field in current table', async () => {
|
|
43
|
+
const n1 = await workflow.createNode({
|
|
44
|
+
type: 'dynamic-calculation',
|
|
45
|
+
config: {
|
|
46
|
+
expression: '{{$context.data.category}}',
|
|
47
|
+
scope: '{{$context.data}}',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const post = await PostRepo.create({
|
|
52
|
+
values: {
|
|
53
|
+
title: 't1',
|
|
54
|
+
category: {
|
|
55
|
+
engine: 'math.js',
|
|
56
|
+
expression: '1 + {{read}}',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await sleep(500);
|
|
62
|
+
|
|
63
|
+
const [execution] = await workflow.getExecutions();
|
|
64
|
+
const [job] = await execution.getJobs();
|
|
65
|
+
expect(job.result).toBe(1);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('dynamic expression field in association table', async () => {
|
|
69
|
+
const n1 = await workflow.createNode({
|
|
70
|
+
type: 'query',
|
|
71
|
+
config: {
|
|
72
|
+
collection: 'categories',
|
|
73
|
+
params: {
|
|
74
|
+
filter: {
|
|
75
|
+
$and: [{ id: '{{$context.data.categoryId}}' }],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const n2 = await workflow.createNode({
|
|
82
|
+
type: 'dynamic-calculation',
|
|
83
|
+
config: {
|
|
84
|
+
expression: `{{$jobsMapByNodeKey.${n1.key}}}`,
|
|
85
|
+
scope: '{{$context.data}}',
|
|
86
|
+
},
|
|
87
|
+
upstreamId: n1.id,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await n1.setDownstream(n2);
|
|
91
|
+
|
|
92
|
+
const category = await CategoryRepo.create({
|
|
93
|
+
values: {
|
|
94
|
+
title: 'c1',
|
|
95
|
+
engine: 'math.js',
|
|
96
|
+
expression: '1 + {{read}}',
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const post = await PostRepo.create({
|
|
101
|
+
values: {
|
|
102
|
+
title: 't1',
|
|
103
|
+
categoryId: category.id,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await sleep(500);
|
|
108
|
+
|
|
109
|
+
const [execution] = await workflow.getExecutions();
|
|
110
|
+
const jobs = await execution.getJobs({ order: [['id', 'ASC']] });
|
|
111
|
+
expect(jobs.length).toBe(2);
|
|
112
|
+
expect(jobs[1].result).toBe(1);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseFieldOptions, DataTypes, Field } from '@nocobase/database';
|
|
2
|
+
|
|
3
|
+
export interface ExpressionFieldOptions extends BaseFieldOptions {
|
|
4
|
+
type: 'expression';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class ExpressionField extends Field {
|
|
8
|
+
get dataType() {
|
|
9
|
+
return DataTypes.TEXT;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Plugin';
|