@nocobase/plugin-workflow 0.10.1-alpha.1 → 0.11.0-alpha.1
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/client.d.ts +2 -3
- package/client.js +1 -30
- package/lib/client/AddButton.js +1 -8
- package/lib/client/Branch.js +8 -8
- package/lib/client/CanvasContent.js +8 -8
- package/lib/client/ExecutionCanvas.js +20 -27
- package/lib/client/ExecutionPage.js +1 -8
- package/lib/client/WorkflowCanvas.js +3 -10
- package/lib/client/WorkflowPage.js +1 -8
- package/lib/client/WorkflowProvider.js +3 -42
- package/lib/client/components/CollectionFieldset.d.ts +1 -1
- package/lib/client/components/CollectionFieldset.js +8 -15
- package/lib/client/components/Duration.js +5 -5
- package/lib/client/components/DynamicExpression.d.ts +3 -3
- package/lib/client/components/FieldsSelect.d.ts +1 -1
- package/lib/client/components/FieldsSelect.js +6 -6
- package/lib/client/components/NodeDescription.js +11 -11
- package/lib/client/components/RadioWithTooltip.js +13 -20
- package/lib/client/components/ValueBlock.js +14 -21
- package/lib/client/components/renderEngineReference.js +1 -8
- package/lib/client/index.d.ts +12 -4
- package/lib/client/index.js +78 -15
- package/lib/client/nodes/aggregate.d.ts +2 -2
- package/lib/client/nodes/aggregate.js +1 -1
- package/lib/client/nodes/calculation.d.ts +1 -1
- package/lib/client/nodes/calculation.js +16 -23
- package/lib/client/nodes/condition.d.ts +1 -3
- package/lib/client/nodes/condition.js +13 -20
- package/lib/client/nodes/create.d.ts +4 -3
- package/lib/client/nodes/destroy.d.ts +1 -1
- package/lib/client/nodes/index.d.ts +2 -2
- package/lib/client/nodes/index.js +79 -86
- package/lib/client/nodes/loop.d.ts +1 -1
- package/lib/client/nodes/loop.js +35 -34
- package/lib/client/nodes/manual/ModeConfig.js +23 -30
- package/lib/client/nodes/manual/SchemaConfig.d.ts +3 -3
- package/lib/client/nodes/manual/SchemaConfig.js +18 -17
- package/lib/client/nodes/manual/WorkflowTodo.js +61 -69
- package/lib/client/nodes/manual/WorkflowTodoBlockInitializer.d.ts +2 -5
- package/lib/client/nodes/manual/WorkflowTodoBlockInitializer.js +6 -5
- package/lib/client/nodes/manual/forms/custom.js +11 -18
- package/lib/client/nodes/parallel.js +20 -20
- package/lib/client/nodes/query.d.ts +3 -2
- package/lib/client/nodes/request.d.ts +2 -2
- package/lib/client/nodes/request.js +7 -7
- package/lib/client/nodes/update.d.ts +2 -2
- package/lib/client/nodes/update.js +1 -1
- package/lib/client/schemas/collection.d.ts +1 -1
- package/lib/client/schemas/collection.js +3 -10
- package/lib/client/style.js +18 -18
- package/lib/client/triggers/collection.d.ts +4 -3
- package/lib/client/triggers/collection.js +1 -1
- package/lib/client/triggers/index.d.ts +2 -2
- package/lib/client/triggers/index.js +45 -52
- package/lib/client/triggers/schedule/DateFieldsSelect.js +1 -1
- package/lib/client/triggers/schedule/EndsByField.js +11 -11
- package/lib/client/triggers/schedule/OnField.js +11 -11
- package/lib/client/triggers/schedule/RepeatField.js +4 -4
- package/lib/client/triggers/schedule/ScheduleConfig.js +17 -24
- package/lib/client/triggers/schedule/index.d.ts +2 -1
- package/lib/client/triggers/schedule/index.js +2 -2
- package/lib/client/variable.d.ts +2 -2
- package/lib/client/variable.js +5 -5
- package/lib/server/Plugin.d.ts +2 -3
- package/lib/server/Plugin.js +8 -9
- package/lib/server/Processor.d.ts +2 -4
- package/lib/server/actions/nodes.js +7 -7
- package/lib/server/fields/expression-field.d.ts +1 -2
- package/lib/server/fields/expression-field.js +1 -8
- package/lib/server/functions/index.d.ts +2 -3
- package/lib/server/index.d.ts +1 -0
- package/lib/server/index.js +12 -0
- package/lib/server/instructions/aggregate.d.ts +1 -1
- package/lib/server/instructions/aggregate.js +5 -5
- package/lib/server/instructions/condition.d.ts +2 -1
- package/lib/server/instructions/create.d.ts +1 -1
- package/lib/server/instructions/delay.d.ts +3 -3
- package/lib/server/instructions/delay.js +66 -64
- package/lib/server/instructions/destroy.d.ts +1 -1
- package/lib/server/instructions/index.d.ts +5 -5
- package/lib/server/instructions/loop.d.ts +1 -2
- package/lib/server/instructions/manual/actions.js +1 -1
- package/lib/server/instructions/manual/forms/index.d.ts +1 -1
- package/lib/server/instructions/manual/index.d.ts +1 -1
- package/lib/server/instructions/parallel.d.ts +1 -2
- package/lib/server/instructions/query.d.ts +1 -1
- package/lib/server/instructions/request.d.ts +2 -2
- package/lib/server/instructions/request.js +1 -0
- package/lib/server/instructions/update.d.ts +1 -1
- package/lib/server/migrations/20230221071831-calculation-expression.js +1 -1
- package/lib/server/migrations/20230221121203-condition-calculation.js +1 -1
- package/lib/server/migrations/20230221162902-jsonb-to-json.js +7 -7
- package/lib/server/migrations/20230411034722-manual-multi-form.js +1 -8
- package/lib/server/triggers/collection.d.ts +1 -1
- package/lib/server/triggers/collection.js +2 -2
- package/lib/server/triggers/index.d.ts +1 -1
- package/lib/server/triggers/schedule.d.ts +1 -1
- package/lib/server/triggers/schedule.js +18 -18
- package/lib/server/{models → types}/Execution.d.ts +2 -3
- package/lib/server/{models → types}/FlowNode.d.ts +1 -2
- package/lib/server/{models → types}/Job.d.ts +1 -2
- package/lib/server/{models → types}/Workflow.d.ts +1 -2
- package/lib/server/types/index.d.ts +4 -0
- package/lib/server/types/index.js +5 -0
- package/package.json +40 -20
- package/server.d.ts +2 -3
- package/server.js +1 -30
- package/src/client/AddButton.tsx +99 -0
- package/src/client/Branch.tsx +35 -0
- package/src/client/CanvasContent.tsx +23 -0
- package/src/client/ExecutionCanvas.tsx +168 -0
- package/src/client/ExecutionLink.tsx +16 -0
- package/src/client/ExecutionPage.tsx +44 -0
- package/src/client/ExecutionResourceProvider.tsx +21 -0
- package/src/client/FlowContext.ts +7 -0
- package/src/client/WorkflowCanvas.tsx +220 -0
- package/src/client/WorkflowLink.tsx +16 -0
- package/src/client/WorkflowPage.tsx +51 -0
- package/src/client/WorkflowProvider.tsx +84 -0
- package/src/client/components/CollectionBlockInitializer.tsx +71 -0
- package/src/client/components/CollectionFieldset.tsx +158 -0
- package/src/client/components/Duration.tsx +45 -0
- package/src/client/components/DynamicExpression.tsx +53 -0
- package/src/client/components/FieldsSelect.tsx +28 -0
- package/src/client/components/FilterDynamicComponent.tsx +15 -0
- package/src/client/components/NodeDescription.tsx +44 -0
- package/src/client/components/NullRender.tsx +3 -0
- package/src/client/components/OpenDrawer.tsx +24 -0
- package/src/client/components/RadioWithTooltip.tsx +38 -0
- package/src/client/components/ValueBlock.tsx +67 -0
- package/src/client/components/renderEngineReference.tsx +30 -0
- package/src/client/constants.tsx +91 -0
- package/src/client/index.tsx +51 -0
- package/src/client/interfaces/expression.tsx +25 -0
- package/src/client/locale/en-US.ts +136 -0
- package/src/client/locale/es-ES.ts +129 -0
- package/src/client/locale/index.ts +18 -0
- package/src/client/locale/ja-JP.ts +90 -0
- package/src/client/locale/pt-BR.ts +136 -0
- package/src/client/locale/ru-RU.ts +90 -0
- package/src/client/locale/tr-TR.ts +90 -0
- package/src/client/locale/zh-CN.ts +242 -0
- package/src/client/nodes/aggregate.tsx +327 -0
- package/src/client/nodes/calculation.tsx +217 -0
- package/src/client/nodes/condition.tsx +490 -0
- package/src/client/nodes/create.tsx +86 -0
- package/src/client/nodes/delay.tsx +37 -0
- package/src/client/nodes/destroy.tsx +34 -0
- package/src/client/nodes/index.tsx +489 -0
- package/src/client/nodes/loop.tsx +159 -0
- package/src/client/nodes/manual/AssigneesSelect.tsx +33 -0
- package/src/client/nodes/manual/DetailsBlockProvider.tsx +80 -0
- package/src/client/nodes/manual/FormBlockInitializer.tsx +68 -0
- package/src/client/nodes/manual/FormBlockProvider.tsx +75 -0
- package/src/client/nodes/manual/ModeConfig.tsx +84 -0
- package/src/client/nodes/manual/SchemaConfig.tsx +365 -0
- package/src/client/nodes/manual/WorkflowTodo.tsx +611 -0
- package/src/client/nodes/manual/WorkflowTodoBlockInitializer.tsx +28 -0
- package/src/client/nodes/manual/forms/create.tsx +88 -0
- package/src/client/nodes/manual/forms/custom.tsx +388 -0
- package/src/client/nodes/manual/forms/update.tsx +130 -0
- package/src/client/nodes/manual/index.tsx +162 -0
- package/src/client/nodes/manual/utils.ts +28 -0
- package/src/client/nodes/parallel.tsx +137 -0
- package/src/client/nodes/query.tsx +89 -0
- package/src/client/nodes/request.tsx +185 -0
- package/src/client/nodes/update.tsx +99 -0
- package/src/client/schemas/collection.ts +75 -0
- package/src/client/schemas/executions.tsx +169 -0
- package/src/client/schemas/workflows.ts +364 -0
- package/src/client/style.tsx +312 -0
- package/src/client/triggers/collection.tsx +186 -0
- package/src/client/triggers/index.tsx +307 -0
- package/src/client/triggers/schedule/DateFieldsSelect.tsx +28 -0
- package/src/client/triggers/schedule/EndsByField.tsx +40 -0
- package/src/client/triggers/schedule/OnField.tsx +50 -0
- package/src/client/triggers/schedule/RepeatField.tsx +116 -0
- package/src/client/triggers/schedule/ScheduleConfig.tsx +225 -0
- package/src/client/triggers/schedule/constants.ts +4 -0
- package/src/client/triggers/schedule/index.tsx +72 -0
- package/src/client/triggers/schedule/locale/Cron.zh-CN.ts +79 -0
- package/src/client/utils.ts +36 -0
- package/src/client/variable.tsx +296 -0
- package/src/index.ts +1 -0
- package/src/server/Plugin.ts +351 -0
- package/src/server/Processor.ts +354 -0
- package/src/server/__tests__/Plugin.test.ts +398 -0
- package/src/server/__tests__/Processor.test.ts +474 -0
- package/src/server/__tests__/actions/workflows.test.ts +419 -0
- package/src/server/__tests__/collections/categories.ts +23 -0
- package/src/server/__tests__/collections/comments.ts +24 -0
- package/src/server/__tests__/collections/posts.ts +42 -0
- package/src/server/__tests__/collections/replies.ts +9 -0
- package/src/server/__tests__/collections/tags.ts +15 -0
- package/src/server/__tests__/index.ts +89 -0
- package/src/server/__tests__/instructions/aggregate.test.ts +294 -0
- package/src/server/__tests__/instructions/calculation.test.ts +265 -0
- package/src/server/__tests__/instructions/condition.test.ts +335 -0
- package/src/server/__tests__/instructions/create.test.ts +129 -0
- package/src/server/__tests__/instructions/delay.test.ts +182 -0
- package/src/server/__tests__/instructions/destroy.test.ts +58 -0
- package/src/server/__tests__/instructions/loop.test.ts +331 -0
- package/src/server/__tests__/instructions/manual.test.ts +854 -0
- package/src/server/__tests__/instructions/parallel.test.ts +445 -0
- package/src/server/__tests__/instructions/query.test.ts +359 -0
- package/src/server/__tests__/instructions/request.test.ts +217 -0
- package/src/server/__tests__/instructions/update.test.ts +189 -0
- package/src/server/__tests__/triggers/collection.test.ts +298 -0
- package/src/server/__tests__/triggers/schedule.test.ts +369 -0
- package/src/server/actions/index.ts +25 -0
- package/src/server/actions/nodes.ts +214 -0
- package/src/server/actions/workflows.ts +178 -0
- package/src/server/collections/executions.ts +35 -0
- package/src/server/collections/flow_nodes.ts +54 -0
- package/src/server/collections/jobs.ts +31 -0
- package/src/server/collections/workflows.ts +88 -0
- package/src/server/constants.ts +26 -0
- package/src/server/fields/expression-field.ts +11 -0
- package/src/server/fields/index.ts +7 -0
- package/src/server/functions/index.ts +16 -0
- package/src/server/index.ts +6 -0
- package/src/server/instructions/aggregate.ts +42 -0
- package/src/server/instructions/calculation.ts +41 -0
- package/src/server/instructions/condition.ts +172 -0
- package/src/server/instructions/create.ts +37 -0
- package/src/server/instructions/delay.ts +105 -0
- package/src/server/instructions/destroy.ts +23 -0
- package/src/server/instructions/index.ts +63 -0
- package/src/server/instructions/loop.ts +99 -0
- package/src/server/instructions/manual/actions.ts +79 -0
- package/src/server/instructions/manual/collecions/jobs.ts +17 -0
- package/src/server/instructions/manual/collecions/users.ts +15 -0
- package/src/server/instructions/manual/collecions/users_jobs.ts +50 -0
- package/src/server/instructions/manual/forms/create.ts +22 -0
- package/src/server/instructions/manual/forms/index.ts +12 -0
- package/src/server/instructions/manual/forms/update.ts +22 -0
- package/src/server/instructions/manual/index.ts +184 -0
- package/src/server/instructions/parallel.ts +121 -0
- package/src/server/instructions/query.ts +31 -0
- package/src/server/instructions/request.ts +87 -0
- package/src/server/instructions/update.ts +24 -0
- package/src/server/migrations/20221129153547-calculation-variables.ts +64 -0
- package/src/server/migrations/20230221032941-change-request-body-type.ts +76 -0
- package/src/server/migrations/20230221071831-calculation-expression.ts +102 -0
- package/src/server/migrations/20230221121203-condition-calculation.ts +82 -0
- package/src/server/migrations/20230221162902-jsonb-to-json.ts +51 -0
- package/src/server/migrations/20230411034722-manual-multi-form.ts +282 -0
- package/src/server/migrations/20230612021134-manual-collection-block.ts +138 -0
- package/src/server/triggers/collection.ts +142 -0
- package/src/server/triggers/index.ts +22 -0
- package/src/server/triggers/schedule.ts +567 -0
- package/src/server/types/Execution.ts +26 -0
- package/src/server/types/FlowNode.ts +21 -0
- package/src/server/types/Job.ts +18 -0
- package/src/server/types/Workflow.ts +36 -0
- package/src/server/types/index.ts +4 -0
- /package/lib/server/{models → types}/Execution.js +0 -0
- /package/lib/server/{models → types}/FlowNode.js +0 -0
- /package/lib/server/{models → types}/Job.js +0 -0
- /package/lib/server/{models → types}/Workflow.js +0 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './server';
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
import winston from 'winston';
|
|
4
|
+
import LRUCache from 'lru-cache';
|
|
5
|
+
|
|
6
|
+
import { Op } from '@nocobase/database';
|
|
7
|
+
import { Plugin } from '@nocobase/server';
|
|
8
|
+
import { Registry } from '@nocobase/utils';
|
|
9
|
+
|
|
10
|
+
import initFields from './fields';
|
|
11
|
+
import initActions from './actions';
|
|
12
|
+
import { EXECUTION_STATUS } from './constants';
|
|
13
|
+
import initInstructions, { Instruction } from './instructions';
|
|
14
|
+
import Processor from './Processor';
|
|
15
|
+
import initTriggers, { Trigger } from './triggers';
|
|
16
|
+
import initFunctions, { CustomFunction } from './functions';
|
|
17
|
+
import { createLogger, Logger, LoggerOptions, getLoggerLevel, getLoggerFilePath } from '@nocobase/logger';
|
|
18
|
+
|
|
19
|
+
import type { WorkflowModel, ExecutionModel, JobModel } from './types';
|
|
20
|
+
|
|
21
|
+
type Pending = [ExecutionModel, JobModel?];
|
|
22
|
+
|
|
23
|
+
type ID = number | string;
|
|
24
|
+
export default class WorkflowPlugin extends Plugin {
|
|
25
|
+
instructions: Registry<Instruction> = new Registry();
|
|
26
|
+
triggers: Registry<Trigger> = new Registry();
|
|
27
|
+
functions: Registry<CustomFunction> = new Registry();
|
|
28
|
+
private executing = false;
|
|
29
|
+
private pending: Pending[] = [];
|
|
30
|
+
private events: [WorkflowModel, any, { context?: any }][] = [];
|
|
31
|
+
|
|
32
|
+
private loggerCache: LRUCache<string, Logger>;
|
|
33
|
+
|
|
34
|
+
getLogger(workflowId: ID): Logger {
|
|
35
|
+
const now = new Date();
|
|
36
|
+
const date = `${now.getFullYear()}-${`0${now.getMonth() + 1}`.slice(-2)}-${`0${now.getDate()}`.slice(-2)}`;
|
|
37
|
+
const key = `${date}-${workflowId}}`;
|
|
38
|
+
if (this.loggerCache.has(key)) {
|
|
39
|
+
return this.loggerCache.get(key);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const logger = createLogger({
|
|
43
|
+
transports: [
|
|
44
|
+
'console',
|
|
45
|
+
new winston.transports.File({
|
|
46
|
+
filename: getLoggerFilePath('workflows', date, `${workflowId}.log`),
|
|
47
|
+
level: getLoggerLevel(),
|
|
48
|
+
}),
|
|
49
|
+
],
|
|
50
|
+
} as LoggerOptions);
|
|
51
|
+
|
|
52
|
+
this.loggerCache.set(key, logger);
|
|
53
|
+
|
|
54
|
+
return logger;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onBeforeSave = async (instance: WorkflowModel, options) => {
|
|
58
|
+
const Model = <typeof WorkflowModel>instance.constructor;
|
|
59
|
+
|
|
60
|
+
if (instance.enabled) {
|
|
61
|
+
instance.set('current', true);
|
|
62
|
+
} else if (!instance.current) {
|
|
63
|
+
const count = await Model.count({
|
|
64
|
+
where: {
|
|
65
|
+
key: instance.key,
|
|
66
|
+
},
|
|
67
|
+
transaction: options.transaction,
|
|
68
|
+
});
|
|
69
|
+
if (!count) {
|
|
70
|
+
instance.set('current', true);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!instance.changed('enabled') || !instance.enabled) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const previous = await Model.findOne({
|
|
79
|
+
where: {
|
|
80
|
+
key: instance.key,
|
|
81
|
+
current: true,
|
|
82
|
+
id: {
|
|
83
|
+
[Op.ne]: instance.id,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
transaction: options.transaction,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (previous) {
|
|
90
|
+
// NOTE: set to `null` but not `false` will not violate the unique index
|
|
91
|
+
await previous.update(
|
|
92
|
+
{ enabled: false, current: null },
|
|
93
|
+
{
|
|
94
|
+
transaction: options.transaction,
|
|
95
|
+
hooks: false,
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
this.toggle(previous, false);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
async load() {
|
|
104
|
+
const { db, options } = this;
|
|
105
|
+
|
|
106
|
+
initFields(this);
|
|
107
|
+
initActions(this);
|
|
108
|
+
initTriggers(this, options.triggers);
|
|
109
|
+
initInstructions(this, options.instructions);
|
|
110
|
+
initFunctions(this, options.functions);
|
|
111
|
+
|
|
112
|
+
this.loggerCache = new LRUCache({
|
|
113
|
+
max: 20,
|
|
114
|
+
updateAgeOnGet: true,
|
|
115
|
+
dispose(logger) {
|
|
116
|
+
(<Logger>logger).end();
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
this.app.acl.registerSnippet({
|
|
121
|
+
name: `pm.${this.name}.workflows`,
|
|
122
|
+
actions: [
|
|
123
|
+
'workflows:*',
|
|
124
|
+
'workflows.nodes:*',
|
|
125
|
+
'executions:list',
|
|
126
|
+
'executions:get',
|
|
127
|
+
'flow_nodes:update',
|
|
128
|
+
'flow_nodes:destroy',
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
this.app.acl.allow('users_jobs', ['list', 'get', 'submit'], 'loggedIn');
|
|
133
|
+
|
|
134
|
+
await db.import({
|
|
135
|
+
directory: path.resolve(__dirname, 'collections'),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
this.db.addMigrations({
|
|
139
|
+
namespace: 'workflow',
|
|
140
|
+
directory: path.resolve(__dirname, 'migrations'),
|
|
141
|
+
context: {
|
|
142
|
+
plugin: this,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
db.on('workflows.beforeSave', this.onBeforeSave);
|
|
147
|
+
db.on('workflows.afterSave', (model: WorkflowModel) => this.toggle(model));
|
|
148
|
+
db.on('workflows.afterDestroy', (model: WorkflowModel) => this.toggle(model, false));
|
|
149
|
+
|
|
150
|
+
// [Life Cycle]:
|
|
151
|
+
// * load all workflows in db
|
|
152
|
+
// * add all hooks for enabled workflows
|
|
153
|
+
// * add hooks for create/update[enabled]/delete workflow to add/remove specific hooks
|
|
154
|
+
this.app.on('beforeStart', async () => {
|
|
155
|
+
const collection = db.getCollection('workflows');
|
|
156
|
+
const workflows = await collection.repository.find({
|
|
157
|
+
filter: { enabled: true },
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
workflows.forEach((workflow: WorkflowModel) => {
|
|
161
|
+
this.toggle(workflow);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
this.app.on('afterStart', () => {
|
|
166
|
+
// check for not started executions
|
|
167
|
+
this.dispatch();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
this.app.on('beforeStop', async () => {
|
|
171
|
+
const collection = db.getCollection('workflows');
|
|
172
|
+
const workflows = await collection.repository.find({
|
|
173
|
+
filter: { enabled: true },
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
workflows.forEach((workflow: WorkflowModel) => {
|
|
177
|
+
this.toggle(workflow, false);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
toggle(workflow: WorkflowModel, enable?: boolean) {
|
|
183
|
+
const type = workflow.get('type');
|
|
184
|
+
const trigger = this.triggers.get(type);
|
|
185
|
+
if (typeof enable !== 'undefined' ? enable : workflow.get('enabled')) {
|
|
186
|
+
// NOTE: remove previous listener if config updated
|
|
187
|
+
const prev = workflow.previous();
|
|
188
|
+
if (prev.config) {
|
|
189
|
+
trigger.off({ ...workflow.get(), ...prev });
|
|
190
|
+
}
|
|
191
|
+
trigger.on(workflow);
|
|
192
|
+
} else {
|
|
193
|
+
trigger.off(workflow);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public trigger(workflow: WorkflowModel, context: { [key: string]: any }, options: { context?: any } = {}): void {
|
|
198
|
+
// `null` means not to trigger
|
|
199
|
+
if (context == null) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.events.push([workflow, context, options]);
|
|
204
|
+
|
|
205
|
+
this.getLogger(workflow.id).info(`new event triggered, now events: ${this.events.length}`);
|
|
206
|
+
this.getLogger(workflow.id).debug(`event data:`, {
|
|
207
|
+
data: context,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (this.events.length > 1) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// NOTE: no await for quick return
|
|
215
|
+
setTimeout(this.prepare);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private prepare = async () => {
|
|
219
|
+
const event = this.events.shift();
|
|
220
|
+
if (!event) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const [workflow, context, options] = event;
|
|
224
|
+
|
|
225
|
+
let valid = true;
|
|
226
|
+
if (options.context?.executionId) {
|
|
227
|
+
// NOTE: no transaction here for read-uncommitted execution
|
|
228
|
+
const existed = await workflow.countExecutions({
|
|
229
|
+
where: {
|
|
230
|
+
id: options.context.executionId,
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (existed) {
|
|
235
|
+
this.getLogger(workflow.id).warn(
|
|
236
|
+
`workflow ${workflow.id} has already been triggered in same execution (${options.context.executionId}), and newly triggering will be skipped.`,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
valid = false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (valid) {
|
|
244
|
+
const execution = await this.db.sequelize.transaction(async (transaction) => {
|
|
245
|
+
const execution = await workflow.createExecution(
|
|
246
|
+
{
|
|
247
|
+
context,
|
|
248
|
+
key: workflow.key,
|
|
249
|
+
status: EXECUTION_STATUS.QUEUEING,
|
|
250
|
+
useTransaction: workflow.useTransaction,
|
|
251
|
+
},
|
|
252
|
+
{ transaction },
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
await workflow.increment('executed', { transaction });
|
|
256
|
+
|
|
257
|
+
await (<typeof WorkflowModel>workflow.constructor).increment('allExecuted', {
|
|
258
|
+
where: {
|
|
259
|
+
key: workflow.key,
|
|
260
|
+
},
|
|
261
|
+
transaction,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
execution.workflow = workflow;
|
|
265
|
+
|
|
266
|
+
return execution;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
this.getLogger(workflow.id).debug(`execution of workflow ${workflow.id} created as ${execution.id}`, {
|
|
270
|
+
data: execution.context,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// NOTE: cache first execution for most cases
|
|
274
|
+
if (!this.executing && !this.pending.length) {
|
|
275
|
+
this.pending.push([execution]);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (this.events.length) {
|
|
280
|
+
await this.prepare();
|
|
281
|
+
} else {
|
|
282
|
+
this.dispatch();
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
public async resume(job) {
|
|
287
|
+
if (!job.execution) {
|
|
288
|
+
job.execution = await job.getExecution();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
this.pending.push([job.execution, job]);
|
|
292
|
+
this.dispatch();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private async dispatch() {
|
|
296
|
+
if (this.executing) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this.executing = true;
|
|
301
|
+
|
|
302
|
+
let next: Pending | null = null;
|
|
303
|
+
// resuming has high priority
|
|
304
|
+
if (this.pending.length) {
|
|
305
|
+
next = this.pending.shift() as Pending;
|
|
306
|
+
} else {
|
|
307
|
+
const execution = (await this.db.getRepository('executions').findOne({
|
|
308
|
+
filter: {
|
|
309
|
+
status: EXECUTION_STATUS.QUEUEING,
|
|
310
|
+
},
|
|
311
|
+
appends: ['workflow'],
|
|
312
|
+
sort: 'createdAt',
|
|
313
|
+
})) as ExecutionModel;
|
|
314
|
+
if (execution && execution.workflow.enabled) {
|
|
315
|
+
next = [execution];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (next) {
|
|
319
|
+
this.process(...next);
|
|
320
|
+
} else {
|
|
321
|
+
this.executing = false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private async process(execution: ExecutionModel, job?: JobModel) {
|
|
326
|
+
if (execution.status === EXECUTION_STATUS.QUEUEING) {
|
|
327
|
+
await execution.update({ status: EXECUTION_STATUS.STARTED });
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const processor = this.createProcessor(execution);
|
|
331
|
+
|
|
332
|
+
this.getLogger(execution.workflowId).info(`execution (${execution.id}) ${job ? 'resuming' : 'starting'}...`);
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
await (job ? processor.resume(job) : processor.start());
|
|
336
|
+
this.getLogger(execution.workflowId).info(
|
|
337
|
+
`execution (${execution.id}) finished with status: ${execution.status}`,
|
|
338
|
+
);
|
|
339
|
+
} catch (err) {
|
|
340
|
+
this.getLogger(execution.workflowId).error(`execution (${execution.id}) error: ${err.message}`, err);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
this.executing = false;
|
|
344
|
+
|
|
345
|
+
this.dispatch();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
public createProcessor(execution: ExecutionModel, options = {}): Processor {
|
|
349
|
+
return new Processor(execution, { ...options, plugin: this });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { Model, Transaction, Transactionable } from '@nocobase/database';
|
|
2
|
+
import { appendArrayColumn } from '@nocobase/evaluators';
|
|
3
|
+
import { Logger } from '@nocobase/logger';
|
|
4
|
+
import { parse } from '@nocobase/utils';
|
|
5
|
+
import Plugin from '.';
|
|
6
|
+
import { EXECUTION_STATUS, JOB_STATUS } from './constants';
|
|
7
|
+
import { Runner } from './instructions';
|
|
8
|
+
import type { ExecutionModel, FlowNodeModel, JobModel } from './types';
|
|
9
|
+
|
|
10
|
+
export interface ProcessorOptions extends Transactionable {
|
|
11
|
+
plugin: Plugin;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default class Processor {
|
|
15
|
+
static StatusMap = {
|
|
16
|
+
[JOB_STATUS.PENDING]: EXECUTION_STATUS.STARTED,
|
|
17
|
+
[JOB_STATUS.RESOLVED]: EXECUTION_STATUS.RESOLVED,
|
|
18
|
+
[JOB_STATUS.FAILED]: EXECUTION_STATUS.FAILED,
|
|
19
|
+
[JOB_STATUS.ERROR]: EXECUTION_STATUS.ERROR,
|
|
20
|
+
[JOB_STATUS.ABORTED]: EXECUTION_STATUS.ABORTED,
|
|
21
|
+
[JOB_STATUS.CANCELED]: EXECUTION_STATUS.CANCELED,
|
|
22
|
+
[JOB_STATUS.REJECTED]: EXECUTION_STATUS.REJECTED,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
logger: Logger;
|
|
26
|
+
|
|
27
|
+
transaction?: Transaction;
|
|
28
|
+
|
|
29
|
+
nodes: FlowNodeModel[] = [];
|
|
30
|
+
nodesMap = new Map<number, FlowNodeModel>();
|
|
31
|
+
jobsMap = new Map<number, JobModel>();
|
|
32
|
+
jobsMapByNodeId: { [key: number]: any } = {};
|
|
33
|
+
|
|
34
|
+
constructor(public execution: ExecutionModel, public options: ProcessorOptions) {
|
|
35
|
+
this.logger = options.plugin.getLogger(execution.workflowId);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// make dual linked nodes list then cache
|
|
39
|
+
private makeNodes(nodes: FlowNodeModel[] = []) {
|
|
40
|
+
this.nodes = nodes;
|
|
41
|
+
|
|
42
|
+
nodes.forEach((node) => {
|
|
43
|
+
this.nodesMap.set(node.id, node);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
nodes.forEach((node) => {
|
|
47
|
+
if (node.upstreamId) {
|
|
48
|
+
node.upstream = this.nodesMap.get(node.upstreamId) as FlowNodeModel;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (node.downstreamId) {
|
|
52
|
+
node.downstream = this.nodesMap.get(node.downstreamId) as FlowNodeModel;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private makeJobs(jobs: Array<JobModel>) {
|
|
58
|
+
jobs.forEach((job) => {
|
|
59
|
+
this.jobsMap.set(job.id, job);
|
|
60
|
+
// TODO: should consider cycle, and from previous job
|
|
61
|
+
this.jobsMapByNodeId[job.nodeId] = job.result;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async getTransaction() {
|
|
66
|
+
if (!this.execution.useTransaction) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { options } = this;
|
|
71
|
+
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
return options.transaction && !options.transaction.finished
|
|
74
|
+
? options.transaction
|
|
75
|
+
: await options.plugin.db.sequelize.transaction();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public async prepare() {
|
|
79
|
+
const transaction = await this.getTransaction();
|
|
80
|
+
this.transaction = transaction;
|
|
81
|
+
|
|
82
|
+
const { execution } = this;
|
|
83
|
+
if (!execution.workflow) {
|
|
84
|
+
execution.workflow = await execution.getWorkflow({ transaction });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const nodes = await execution.workflow.getNodes({ transaction });
|
|
88
|
+
|
|
89
|
+
this.makeNodes(nodes);
|
|
90
|
+
|
|
91
|
+
const jobs = await execution.getJobs({
|
|
92
|
+
order: [['id', 'ASC']],
|
|
93
|
+
transaction,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
this.makeJobs(jobs);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public async start() {
|
|
100
|
+
const { execution } = this;
|
|
101
|
+
if (execution.status !== EXECUTION_STATUS.STARTED) {
|
|
102
|
+
throw new Error(`execution was ended with status ${execution.status}`);
|
|
103
|
+
}
|
|
104
|
+
await this.prepare();
|
|
105
|
+
if (this.nodes.length) {
|
|
106
|
+
const head = this.nodes.find((item) => !item.upstream);
|
|
107
|
+
await this.run(head, { result: execution.context });
|
|
108
|
+
} else {
|
|
109
|
+
await this.exit(JOB_STATUS.RESOLVED);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public async resume(job: JobModel) {
|
|
114
|
+
const { execution } = this;
|
|
115
|
+
if (execution.status !== EXECUTION_STATUS.STARTED) {
|
|
116
|
+
throw new Error(`execution was ended with status ${execution.status}`);
|
|
117
|
+
}
|
|
118
|
+
await this.prepare();
|
|
119
|
+
const node = this.nodesMap.get(job.nodeId);
|
|
120
|
+
await this.recall(node, job);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private async commit() {
|
|
124
|
+
// @ts-ignore
|
|
125
|
+
if (this.transaction && (!this.options.transaction || this.options.transaction.finished)) {
|
|
126
|
+
await this.transaction.commit();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private async exec(instruction: Runner, node: FlowNodeModel, prevJob) {
|
|
131
|
+
let job;
|
|
132
|
+
try {
|
|
133
|
+
// call instruction to get result and status
|
|
134
|
+
this.logger.info(`execution (${this.execution.id}) run instruction [${node.type}] for node (${node.id})`);
|
|
135
|
+
this.logger.debug(`config of node`, { data: node.config });
|
|
136
|
+
job = await instruction(node, prevJob, this);
|
|
137
|
+
if (!job) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
} catch (err) {
|
|
141
|
+
// for uncaught error, set to error
|
|
142
|
+
this.logger.error(
|
|
143
|
+
`execution (${this.execution.id}) run instruction [${node.type}] for node (${node.id}) failed: `,
|
|
144
|
+
{ error: err },
|
|
145
|
+
);
|
|
146
|
+
job = {
|
|
147
|
+
result:
|
|
148
|
+
err instanceof Error
|
|
149
|
+
? { message: err.message, stack: process.env.NODE_ENV === 'production' ? [] : err.stack }
|
|
150
|
+
: err,
|
|
151
|
+
status: JOB_STATUS.ERROR,
|
|
152
|
+
};
|
|
153
|
+
// if previous job is from resuming
|
|
154
|
+
if (prevJob && prevJob.nodeId === node.id) {
|
|
155
|
+
prevJob.set(job);
|
|
156
|
+
job = prevJob;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!(job instanceof Model)) {
|
|
161
|
+
job.upstreamId = prevJob instanceof Model ? prevJob.get('id') : null;
|
|
162
|
+
job.nodeId = node.id;
|
|
163
|
+
}
|
|
164
|
+
const savedJob = await this.saveJob(job);
|
|
165
|
+
|
|
166
|
+
this.logger.info(
|
|
167
|
+
`execution (${this.execution.id}) run instruction [${node.type}] for node (${node.id}) finished as status: ${savedJob.status}`,
|
|
168
|
+
);
|
|
169
|
+
this.logger.debug(`result of node`, { data: savedJob.result });
|
|
170
|
+
|
|
171
|
+
if (savedJob.status === JOB_STATUS.RESOLVED && node.downstream) {
|
|
172
|
+
// run next node
|
|
173
|
+
this.logger.debug(`run next node (${node.downstreamId})`);
|
|
174
|
+
return this.run(node.downstream, savedJob);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// all nodes in scope have been executed
|
|
178
|
+
return this.end(node, savedJob);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public async run(node, input?) {
|
|
182
|
+
const { instructions } = this.options.plugin;
|
|
183
|
+
const instruction = instructions.get(node.type);
|
|
184
|
+
if (typeof instruction.run !== 'function') {
|
|
185
|
+
return Promise.reject(new Error('`run` should be implemented for customized execution of the node'));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return this.exec(instruction.run.bind(instruction), node, input);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// parent node should take over the control
|
|
192
|
+
public async end(node, job: JobModel) {
|
|
193
|
+
this.logger.debug(`branch ended at node (${node.id})`);
|
|
194
|
+
const parentNode = this.findBranchParentNode(node);
|
|
195
|
+
// no parent, means on main flow
|
|
196
|
+
if (parentNode) {
|
|
197
|
+
this.logger.debug(`not on main, recall to parent entry node (${node.id})})`);
|
|
198
|
+
await this.recall(parentNode, job);
|
|
199
|
+
return job;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// really done for all nodes
|
|
203
|
+
// * should mark execution as done with last job status
|
|
204
|
+
return this.exit(job.status);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async recall(node, job) {
|
|
208
|
+
const { instructions } = this.options.plugin;
|
|
209
|
+
const instruction = instructions.get(node.type);
|
|
210
|
+
if (typeof instruction.resume !== 'function') {
|
|
211
|
+
return Promise.reject(new Error('`resume` should be implemented'));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return this.exec(instruction.resume.bind(instruction), node, job);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async exit(s?: number) {
|
|
218
|
+
if (typeof s === 'number') {
|
|
219
|
+
const status = (<typeof Processor>this.constructor).StatusMap[s] ?? Math.sign(s);
|
|
220
|
+
await this.execution.update({ status }, { transaction: this.transaction });
|
|
221
|
+
}
|
|
222
|
+
this.logger.info(`execution (${this.execution.id}) exiting with status ${this.execution.status}`);
|
|
223
|
+
await this.commit();
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// TODO(optimize)
|
|
228
|
+
async saveJob(payload) {
|
|
229
|
+
const { database } = <typeof ExecutionModel>this.execution.constructor;
|
|
230
|
+
const { model } = database.getCollection('jobs');
|
|
231
|
+
let job;
|
|
232
|
+
if (payload instanceof model) {
|
|
233
|
+
job = await payload.save({ transaction: this.transaction });
|
|
234
|
+
} else if (payload.id) {
|
|
235
|
+
job = await model.findByPk(payload.id);
|
|
236
|
+
await job.update(payload, {
|
|
237
|
+
transaction: this.transaction,
|
|
238
|
+
});
|
|
239
|
+
} else {
|
|
240
|
+
job = await model.create(
|
|
241
|
+
{
|
|
242
|
+
...payload,
|
|
243
|
+
executionId: this.execution.id,
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
transaction: this.transaction,
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
this.jobsMap.set(job.id, job);
|
|
251
|
+
this.jobsMapByNodeId[job.nodeId] = job.result;
|
|
252
|
+
|
|
253
|
+
return job;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
getBranches(node: FlowNodeModel): FlowNodeModel[] {
|
|
257
|
+
return this.nodes
|
|
258
|
+
.filter((item) => item.upstream === node && item.branchIndex !== null)
|
|
259
|
+
.sort((a, b) => Number(a.branchIndex) - Number(b.branchIndex));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// find the first node in current branch
|
|
263
|
+
findBranchStartNode(node: FlowNodeModel, parent?: FlowNodeModel): FlowNodeModel | null {
|
|
264
|
+
for (let n = node; n; n = n.upstream) {
|
|
265
|
+
if (!parent) {
|
|
266
|
+
if (n.branchIndex !== null) {
|
|
267
|
+
return n;
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
if (n.upstream === parent) {
|
|
271
|
+
return n;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// find the node start current branch
|
|
279
|
+
findBranchParentNode(node: FlowNodeModel): FlowNodeModel | null {
|
|
280
|
+
for (let n = node; n; n = n.upstream) {
|
|
281
|
+
if (n.branchIndex !== null) {
|
|
282
|
+
return n.upstream;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
findBranchEndNode(node: FlowNodeModel): FlowNodeModel | null {
|
|
289
|
+
for (let n = node; n; n = n.downstream) {
|
|
290
|
+
if (!n.downstream) {
|
|
291
|
+
return n;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
findBranchParentJob(job: JobModel, node: FlowNodeModel): JobModel | null {
|
|
298
|
+
for (let j: JobModel | undefined = job; j; j = this.jobsMap.get(j.upstreamId)) {
|
|
299
|
+
if (j.nodeId === node.id) {
|
|
300
|
+
return j;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
findBranchLastJob(node: FlowNodeModel): JobModel | null {
|
|
307
|
+
for (let n = this.findBranchEndNode(node); n && n !== node.upstream; n = n.upstream) {
|
|
308
|
+
const jobs = Array.from(this.jobsMap.values())
|
|
309
|
+
.filter((item) => item.nodeId === n.id)
|
|
310
|
+
.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
311
|
+
if (jobs.length) {
|
|
312
|
+
return jobs[jobs.length - 1];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
public getScope(node?) {
|
|
319
|
+
const systemFns = {};
|
|
320
|
+
const scope = {
|
|
321
|
+
execution: this.execution,
|
|
322
|
+
node,
|
|
323
|
+
};
|
|
324
|
+
for (const [name, fn] of this.options.plugin.functions.getEntities()) {
|
|
325
|
+
systemFns[name] = fn.bind(scope);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const $scopes = {};
|
|
329
|
+
if (node) {
|
|
330
|
+
for (let n = this.findBranchParentNode(node); n; n = this.findBranchParentNode(n)) {
|
|
331
|
+
const instruction = this.options.plugin.instructions.get(n.type);
|
|
332
|
+
if (typeof instruction.getScope === 'function') {
|
|
333
|
+
$scopes[n.id] = instruction.getScope(n, this.jobsMapByNodeId[n.id], this);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
$context: this.execution.context,
|
|
340
|
+
$jobsMapByNodeId: this.jobsMapByNodeId,
|
|
341
|
+
$system: systemFns,
|
|
342
|
+
$scopes,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
public getParsedValue(value, node?) {
|
|
347
|
+
const template = parse(value);
|
|
348
|
+
const scope = this.getScope(node);
|
|
349
|
+
template.parameters.forEach(({ key }) => {
|
|
350
|
+
appendArrayColumn(scope, key);
|
|
351
|
+
});
|
|
352
|
+
return template(scope);
|
|
353
|
+
}
|
|
354
|
+
}
|