fix(plugin-workflow): fix loop scope variable parsing (#2502)
* fix(plugin-workflow): fix loop scope variable parsing * fix(plugin-workflow): fix api parameter * fix(plugin-workflow): fix test cases
This commit is contained in:
parent
a625fc538d
commit
0fe5bdc859
@ -318,7 +318,8 @@ export default class Processor {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getScope(node?) {
|
||||
public getScope(sourceNodeId: number) {
|
||||
const node = this.nodesMap.get(sourceNodeId);
|
||||
const systemFns = {};
|
||||
const scope = {
|
||||
execution: this.execution,
|
||||
@ -329,12 +330,10 @@ export default class Processor {
|
||||
}
|
||||
|
||||
const $scopes = {};
|
||||
if (node) {
|
||||
for (let n = this.findBranchParentNode(node); n; n = this.findBranchParentNode(n)) {
|
||||
const instruction = this.options.plugin.instructions.get(n.type);
|
||||
if (typeof instruction.getScope === 'function') {
|
||||
$scopes[n.id] = instruction.getScope(n, this.jobsMapByNodeId[n.id], this);
|
||||
}
|
||||
for (let n = this.findBranchParentNode(node); n; n = this.findBranchParentNode(n)) {
|
||||
const instruction = this.options.plugin.instructions.get(n.type);
|
||||
if (typeof instruction.getScope === 'function') {
|
||||
$scopes[n.id] = instruction.getScope(n, this.jobsMapByNodeId[n.id], this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -346,9 +345,9 @@ export default class Processor {
|
||||
};
|
||||
}
|
||||
|
||||
public getParsedValue(value, node?, additionalScope?: object) {
|
||||
public getParsedValue(value, sourceNodeId: number, additionalScope?: object) {
|
||||
const template = parse(value);
|
||||
const scope = Object.assign(this.getScope(node), additionalScope);
|
||||
const scope = Object.assign(this.getScope(sourceNodeId), additionalScope);
|
||||
template.parameters.forEach(({ key }) => {
|
||||
appendArrayColumn(scope, key);
|
||||
});
|
||||
|
@ -29,7 +29,7 @@ describe('workflow > Processor', () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => db.close());
|
||||
afterEach(() => app.destroy());
|
||||
|
||||
describe('base', () => {
|
||||
it('empty workflow without any nodes', async () => {
|
||||
|
@ -31,7 +31,7 @@ describe('workflow > instructions > aggregate', () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => db.close());
|
||||
afterEach(() => app.destroy());
|
||||
|
||||
describe('based on collection', () => {
|
||||
it('count', async () => {
|
||||
|
@ -30,7 +30,7 @@ describe('workflow > instructions > calculation', () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => db.close());
|
||||
afterEach(() => app.destroy());
|
||||
|
||||
describe('math.js', () => {
|
||||
it('syntax error', async () => {
|
||||
|
@ -28,7 +28,7 @@ describe('workflow > instructions > condition', () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => db.close());
|
||||
afterEach(() => app.destroy());
|
||||
|
||||
describe('config.rejectOnFalse', () => {});
|
||||
|
||||
|
@ -29,7 +29,7 @@ describe('workflow > instructions > create', () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => db.close());
|
||||
afterEach(() => app.destroy());
|
||||
|
||||
describe('create one', () => {
|
||||
it('params: from context', async () => {
|
||||
|
@ -27,7 +27,7 @@ describe('workflow > instructions > destroy', () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => db.close());
|
||||
afterEach(() => app.destroy());
|
||||
|
||||
describe('destroy one', () => {
|
||||
it('params: from context', async () => {
|
||||
|
@ -49,7 +49,7 @@ describe('workflow > instructions > manual', () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => db.close());
|
||||
afterEach(() => app.destroy());
|
||||
|
||||
describe('actions configuration', () => {
|
||||
it('no action configured', async () => {
|
||||
@ -58,8 +58,7 @@ describe('workflow > instructions > manual', () => {
|
||||
config: {
|
||||
assignees: [users[0].id],
|
||||
forms: {
|
||||
f1: {
|
||||
},
|
||||
f1: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -96,9 +95,7 @@ describe('workflow > instructions > manual', () => {
|
||||
assignees: [users[0].id],
|
||||
forms: {
|
||||
f1: {
|
||||
actions: [
|
||||
{ status: JOB_STATUS.RESOLVED, key: 'resolve' },
|
||||
],
|
||||
actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -346,9 +343,7 @@ describe('workflow > instructions > manual', () => {
|
||||
assignees: [users[0].id],
|
||||
forms: {
|
||||
f1: {
|
||||
actions: [
|
||||
{ status: JOB_STATUS.RESOLVED, key: 'resolve' },
|
||||
],
|
||||
actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -418,9 +413,7 @@ describe('workflow > instructions > manual', () => {
|
||||
assignees: [users[0].id, users[1].id],
|
||||
forms: {
|
||||
f1: {
|
||||
actions: [
|
||||
{ status: JOB_STATUS.RESOLVED, key: 'resolve' },
|
||||
]
|
||||
actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -467,9 +460,7 @@ describe('workflow > instructions > manual', () => {
|
||||
assignees: [users[0].id],
|
||||
forms: {
|
||||
f1: {
|
||||
actions: [
|
||||
{ status: JOB_STATUS.RESOLVED, key: 'resolve' },
|
||||
],
|
||||
actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -512,9 +503,7 @@ describe('workflow > instructions > manual', () => {
|
||||
mode: 1,
|
||||
forms: {
|
||||
f1: {
|
||||
actions: [
|
||||
{ status: JOB_STATUS.RESOLVED, key: 'resolve' },
|
||||
],
|
||||
actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -575,9 +564,7 @@ describe('workflow > instructions > manual', () => {
|
||||
mode: 1,
|
||||
forms: {
|
||||
f1: {
|
||||
actions: [
|
||||
{ status: JOB_STATUS.REJECTED, key: 'reject' },
|
||||
],
|
||||
actions: [{ status: JOB_STATUS.REJECTED, key: 'reject' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -808,9 +795,7 @@ describe('workflow > instructions > manual', () => {
|
||||
mode: -1,
|
||||
forms: {
|
||||
f1: {
|
||||
actions: [
|
||||
{ status: JOB_STATUS.REJECTED, key: 'reject' },
|
||||
],
|
||||
actions: [{ status: JOB_STATUS.REJECTED, key: 'reject' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -872,9 +857,7 @@ describe('workflow > instructions > manual', () => {
|
||||
assignees: [users[0].id, users[1].id],
|
||||
forms: {
|
||||
f1: {
|
||||
actions: [
|
||||
{ status: JOB_STATUS.RESOLVED, key: 'resolve' },
|
||||
],
|
||||
actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1013,9 +996,7 @@ describe('workflow > instructions > manual', () => {
|
||||
forms: {
|
||||
f1: {
|
||||
type: 'create',
|
||||
actions: [
|
||||
{ status: JOB_STATUS.RESOLVED, key: 'resolve' },
|
||||
],
|
||||
actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }],
|
||||
collection: 'comments',
|
||||
},
|
||||
},
|
||||
@ -1129,9 +1110,7 @@ describe('workflow > instructions > manual', () => {
|
||||
forms: {
|
||||
f1: {
|
||||
type: 'update',
|
||||
actions: [
|
||||
{ status: JOB_STATUS.RESOLVED, key: 'resolve' },
|
||||
],
|
||||
actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }],
|
||||
collection: 'posts',
|
||||
},
|
||||
},
|
||||
|
@ -34,7 +34,7 @@ describe('workflow > instructions > query', () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => db.close());
|
||||
afterEach(() => app.destroy());
|
||||
|
||||
describe('query one', () => {
|
||||
it('params: empty', async () => {
|
||||
|
@ -27,7 +27,7 @@ describe('workflow > instructions > update', () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => db.close());
|
||||
afterEach(() => app.destroy());
|
||||
|
||||
describe('update one', () => {
|
||||
it('params: from context', async () => {
|
||||
|
@ -23,7 +23,7 @@ describe('workflow > triggers > collection', () => {
|
||||
TagRepo = db.getCollection('tags').repository;
|
||||
});
|
||||
|
||||
afterEach(() => db.close());
|
||||
afterEach(() => app.destroy());
|
||||
|
||||
describe('toggle', () => {
|
||||
it('when collection change', async () => {
|
||||
|
@ -15,12 +15,12 @@ const aggregators = {
|
||||
export default {
|
||||
async run(node: FlowNodeModel, input, processor: Processor) {
|
||||
const { aggregator, associated, collection, association = {}, params = {} } = node.config;
|
||||
const options = processor.getParsedValue(params);
|
||||
const options = processor.getParsedValue(params, node.id);
|
||||
const { database } = <typeof FlowNodeModel>node.constructor;
|
||||
const repo = associated
|
||||
? database.getRepository<HasManyRepository | BelongsToManyRepository>(
|
||||
`${association?.associatedCollection}.${association.name}`,
|
||||
processor.getParsedValue(association?.associatedKey),
|
||||
processor.getParsedValue(association?.associatedKey, node.id),
|
||||
)
|
||||
: database.getRepository(collection);
|
||||
|
||||
|
@ -15,7 +15,7 @@ export default {
|
||||
async run(node: FlowNodeModel, prevJob, processor: Processor) {
|
||||
const { dynamic = false } = <CalculationConfig>node.config || {};
|
||||
let { engine = 'math.js', expression = '' } = node.config;
|
||||
let scope = processor.getScope(node);
|
||||
let scope = processor.getScope(node.id);
|
||||
if (dynamic) {
|
||||
const parsed = parse(dynamic)(scope) ?? {};
|
||||
engine = parsed.engine;
|
||||
|
@ -125,8 +125,8 @@ export default {
|
||||
|
||||
try {
|
||||
result = evaluator
|
||||
? evaluator(expression, processor.getScope())
|
||||
: logicCalculate(processor.getParsedValue(calculation, node));
|
||||
? evaluator(expression, processor.getScope(node.id))
|
||||
: logicCalculate(processor.getParsedValue(calculation, node.id));
|
||||
} catch (e) {
|
||||
return {
|
||||
result: e.toString(),
|
||||
|
@ -7,7 +7,7 @@ export default {
|
||||
const { collection, params: { appends = [], ...params } = {} } = node.config;
|
||||
|
||||
const { repository, model } = (<typeof FlowNodeModel>node.constructor).database.getCollection(collection);
|
||||
const options = processor.getParsedValue(params, node);
|
||||
const options = processor.getParsedValue(params, node.id);
|
||||
const created = await repository.create({
|
||||
...options,
|
||||
context: {
|
||||
|
@ -6,7 +6,7 @@ export default {
|
||||
const { collection, params = {} } = node.config;
|
||||
|
||||
const repo = (<typeof FlowNodeModel>node.constructor).database.getRepository(collection);
|
||||
const options = processor.getParsedValue(params, node);
|
||||
const options = processor.getParsedValue(params, node.id);
|
||||
const result = await repo.destroy({
|
||||
...options,
|
||||
context: {
|
||||
|
@ -19,7 +19,7 @@ function getTargetLength(target) {
|
||||
export default {
|
||||
async run(node: FlowNodeModel, prevJob: JobModel, processor: Processor) {
|
||||
const [branch] = processor.getBranches(node);
|
||||
const target = processor.getParsedValue(node.config.target, node);
|
||||
const target = processor.getParsedValue(node.config.target, node.id);
|
||||
const length = getTargetLength(target);
|
||||
|
||||
if (!branch || !length) {
|
||||
@ -61,7 +61,7 @@ export default {
|
||||
|
||||
const nextIndex = result + 1;
|
||||
|
||||
const target = processor.getParsedValue(loop.config.target, node);
|
||||
const target = processor.getParsedValue(loop.config.target, node.id);
|
||||
// branchJob.status === JOB_STATUS.RESOLVED means branchJob is done, try next loop or exit as resolved
|
||||
if (branchJob.status > JOB_STATUS.PENDING) {
|
||||
job.set({ result: nextIndex });
|
||||
@ -83,7 +83,7 @@ export default {
|
||||
},
|
||||
|
||||
getScope(node, index, processor) {
|
||||
const target = processor.getParsedValue(node.config.target, node);
|
||||
const target = processor.getParsedValue(node.config.target, node.id);
|
||||
const targets = (Array.isArray(target) ? target : [target]).filter((t) => t != null);
|
||||
const length = getTargetLength(target);
|
||||
const item = typeof target === 'number' ? index : targets[index];
|
||||
|
@ -30,7 +30,7 @@ export async function submit(context: Context, next) {
|
||||
}
|
||||
|
||||
const { forms = {} } = userJob.node.config;
|
||||
const [formKey] = Object.keys(values.result ?? {}).filter(key => key !== '_');
|
||||
const [formKey] = Object.keys(values.result ?? {}).filter((key) => key !== '_');
|
||||
const actionKey = values.result?._;
|
||||
|
||||
const actionItem = forms[formKey]?.actions?.find((item) => item.key === actionKey);
|
||||
@ -40,7 +40,8 @@ export async function submit(context: Context, next) {
|
||||
userJob.job.status !== JOB_STATUS.PENDING ||
|
||||
userJob.execution.status !== EXECUTION_STATUS.STARTED ||
|
||||
!userJob.workflow.enabled ||
|
||||
!actionKey || actionItem?.status == null
|
||||
!actionKey ||
|
||||
actionItem?.status == null
|
||||
) {
|
||||
return context.throw(400);
|
||||
}
|
||||
@ -50,11 +51,11 @@ export async function submit(context: Context, next) {
|
||||
await processor.prepare();
|
||||
|
||||
// NOTE: validate assignee
|
||||
const assignees = processor.getParsedValue(userJob.node.config.assignees ?? []);
|
||||
const assignees = processor.getParsedValue(userJob.node.config.assignees ?? [], userJob.nodeId);
|
||||
if (!assignees.includes(currentUser.id) || userJob.userId !== currentUser.id) {
|
||||
return context.throw(403);
|
||||
}
|
||||
const presetValues = processor.getParsedValue(actionItem.values ?? {}, null, {
|
||||
const presetValues = processor.getParsedValue(actionItem.values ?? {}, userJob.nodeId, {
|
||||
currentUser: currentUser.toJSON(),
|
||||
currentRecord: values.result[formKey],
|
||||
currentTime: new Date(),
|
||||
@ -62,9 +63,10 @@ export async function submit(context: Context, next) {
|
||||
|
||||
userJob.set({
|
||||
status: actionItem.status,
|
||||
result: actionItem.status > JOB_STATUS.PENDING
|
||||
? { [formKey]: Object.assign(values.result[formKey], presetValues), _: actionKey }
|
||||
: Object.assign(userJob.result ?? {}, values.result),
|
||||
result:
|
||||
actionItem.status > JOB_STATUS.PENDING
|
||||
? { [formKey]: Object.assign(values.result[formKey], presetValues), _: actionKey }
|
||||
: Object.assign(userJob.result ?? {}, values.result),
|
||||
});
|
||||
|
||||
const handler = instruction.formTypes.get(forms[formKey].type);
|
||||
|
@ -10,7 +10,7 @@ export default async function (this: ManualInstruction, instance, { collection,
|
||||
const { _, ...form } = instance.result;
|
||||
const [values] = Object.values(form);
|
||||
await repo.update({
|
||||
filter: processor.getParsedValue(filter),
|
||||
filter: processor.getParsedValue(filter, instance.nodeId),
|
||||
values: {
|
||||
...((values as { [key: string]: any }) ?? {}),
|
||||
updatedBy: instance.userId,
|
||||
|
@ -2,7 +2,7 @@ import actions from '@nocobase/actions';
|
||||
import { HandlerType } from '@nocobase/resourcer';
|
||||
import { Registry } from '@nocobase/utils';
|
||||
|
||||
import Plugin from '../..';
|
||||
import Plugin, { Processor } from '../..';
|
||||
import { JOB_STATUS } from '../../constants';
|
||||
import { Instruction } from '..';
|
||||
import jobsCollection from './collecions/jobs';
|
||||
@ -124,9 +124,9 @@ export default class implements Instruction {
|
||||
initFormTypes(this);
|
||||
}
|
||||
|
||||
async run(node, prevJob, processor) {
|
||||
async run(node, prevJob, processor: Processor) {
|
||||
const { mode, ...config } = node.config as ManualConfig;
|
||||
const assignees = [...new Set(processor.getParsedValue(config.assignees) || [])];
|
||||
const assignees = [...new Set(processor.getParsedValue(config.assignees, node.id) || [])];
|
||||
|
||||
const job = await processor.saveJob({
|
||||
status: JOB_STATUS.PENDING,
|
||||
@ -154,7 +154,7 @@ export default class implements Instruction {
|
||||
return job;
|
||||
}
|
||||
|
||||
async resume(node, job, processor) {
|
||||
async resume(node, job, processor: Processor) {
|
||||
// NOTE: check all users jobs related if all done then continue as parallel
|
||||
const { assignees = [], mode } = node.config as ManualConfig;
|
||||
|
||||
|
@ -15,7 +15,7 @@ export default {
|
||||
pageSize = DEFAULT_PER_PAGE,
|
||||
sort = [],
|
||||
...options
|
||||
} = processor.getParsedValue(params, node);
|
||||
} = processor.getParsedValue(params, node.id);
|
||||
const appends = options.appends
|
||||
? Array.from(
|
||||
options.appends.reduce((set, field) => {
|
||||
|
@ -52,7 +52,7 @@ export default class implements Instruction {
|
||||
upstreamId: prevJob?.id ?? null,
|
||||
});
|
||||
|
||||
const config = processor.getParsedValue(node.config, node) as RequestConfig;
|
||||
const config = processor.getParsedValue(node.config, node.id) as RequestConfig;
|
||||
|
||||
// eslint-disable-next-line promise/catch-or-return
|
||||
request(config)
|
||||
|
@ -4,11 +4,11 @@ import type { FlowNodeModel } from '../types';
|
||||
export default {
|
||||
async run(node: FlowNodeModel, input, processor: Processor) {
|
||||
const { sequelize } = (<typeof FlowNodeModel>node.constructor).database;
|
||||
const sql = processor.getParsedValue(node.config.sql ?? '', node).trim();
|
||||
const sql = processor.getParsedValue(node.config.sql ?? '', node.id).trim();
|
||||
if (!sql) {
|
||||
return {
|
||||
status: JOB_STATUS.RESOLVED,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const result = await sequelize.query(sql, {
|
||||
|
@ -7,7 +7,7 @@ export default {
|
||||
const { collection, params = {} } = node.config;
|
||||
|
||||
const repo = (<typeof FlowNodeModel>node.constructor).database.getRepository(collection);
|
||||
const options = processor.getParsedValue(params, node);
|
||||
const options = processor.getParsedValue(params, node.id);
|
||||
const result = await repo.update({
|
||||
...options,
|
||||
context: {
|
||||
|
Loading…
Reference in New Issue
Block a user