refactor(plugin-workflow): refactor apis (#3267)

* refactor(plugin-workflow): refactor apis

* fix(plugin-workflow-parallel): fix import in test cases

* fix(plugin-workflow): fix some module import source

* fix(plugin-workflow): move manual table acl to manual plugin

* fix(plugin-workflow-manual): fix folder typo
This commit is contained in:
Junyi 2023-12-27 13:55:48 +08:00 committed by GitHub
parent 57c7dd3e95
commit 8ee8ab7d6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 222 additions and 261 deletions

View File

@ -13,7 +13,6 @@ export default class extends Plugin {
// You can get and modify the app instance here // You can get and modify the app instance here
async load() { async load() {
const workflow = this.app.pm.get('workflow') as WorkflowPlugin; const workflow = this.app.pm.get('workflow') as WorkflowPlugin;
const aggregateInstruction = new AggregateInstruction(); workflow.registerInstruction('aggregate', AggregateInstruction);
workflow.instructions.register(aggregateInstruction.type, aggregateInstruction);
} }
} }

View File

@ -28,7 +28,7 @@ export default class extends Instruction {
const result = await repo.aggregate({ const result = await repo.aggregate({
...options, ...options,
method: aggregators[aggregator], method: aggregators[aggregator],
transaction: processor.transaction, // transaction: processor.transaction,
}); });
return { return {

View File

@ -4,11 +4,8 @@ import WorkflowPlugin from '@nocobase/plugin-workflow';
import AggregateInstruction from './AggregateInstruction'; import AggregateInstruction from './AggregateInstruction';
export default class extends Plugin { export default class extends Plugin {
workflow: WorkflowPlugin;
async load() { async load() {
const workflowPlugin = this.app.getPlugin('workflow') as WorkflowPlugin; const workflowPlugin = this.app.getPlugin<WorkflowPlugin>(WorkflowPlugin);
this.workflow = workflowPlugin; workflowPlugin.registerInstruction('aggregate', AggregateInstruction);
workflowPlugin.instructions.register('aggregate', new AggregateInstruction(workflowPlugin));
} }
} }

View File

@ -13,7 +13,6 @@ export default class extends Plugin {
// You can get and modify the app instance here // You can get and modify the app instance here
async load() { async load() {
const workflow = this.app.pm.get('workflow') as WorkflowPlugin; const workflow = this.app.pm.get('workflow') as WorkflowPlugin;
const delayInstruction = new DelayInstruction(); workflow.registerInstruction('delay', DelayInstruction);
workflow.instructions.register(delayInstruction.type, delayInstruction);
} }
} }

View File

@ -16,15 +16,15 @@ interface DelayConfig {
export default class extends Instruction { export default class extends Instruction {
timers: Map<number, NodeJS.Timeout> = new Map(); timers: Map<number, NodeJS.Timeout> = new Map();
constructor(public plugin: WorkflowPlugin) { constructor(public workflow: WorkflowPlugin) {
super(plugin); super(workflow);
plugin.app.on('afterStart', this.load); workflow.app.on('afterStart', this.load);
plugin.app.on('beforeStop', this.unload); workflow.app.on('beforeStop', this.unload);
} }
load = async () => { load = async () => {
const { model } = this.plugin.db.getCollection('jobs'); const { model } = this.workflow.app.db.getCollection('jobs');
const jobs = (await model.findAll({ const jobs = (await model.findAll({
where: { where: {
status: JOB_STATUS.PENDING, status: JOB_STATUS.PENDING,
@ -79,7 +79,7 @@ export default class extends Instruction {
job.execution = await job.getExecution(); job.execution = await job.getExecution();
} }
if (job.execution.status === EXECUTION_STATUS.STARTED) { if (job.execution.status === EXECUTION_STATUS.STARTED) {
this.plugin.resume(job); this.workflow.resume(job);
} }
if (this.timers.get(job.id)) { if (this.timers.get(job.id)) {
this.timers.delete(job.id); this.timers.delete(job.id);

View File

@ -4,11 +4,8 @@ import WorkflowPlugin from '@nocobase/plugin-workflow';
import DelayInstruction from './DelayInstruction'; import DelayInstruction from './DelayInstruction';
export default class extends Plugin { export default class extends Plugin {
workflow: WorkflowPlugin;
async load() { async load() {
const workflowPlugin = this.app.getPlugin('workflow') as WorkflowPlugin; const workflowPlugin = this.app.getPlugin<WorkflowPlugin>(WorkflowPlugin);
this.workflow = workflowPlugin; workflowPlugin.registerInstruction('delay', DelayInstruction);
workflowPlugin.instructions.register('delay', new DelayInstruction(workflowPlugin));
} }
} }

View File

@ -5,15 +5,12 @@ import { ExpressionField } from './expression-field';
import { DynamicCalculation } from './DynamicCalculation'; import { DynamicCalculation } from './DynamicCalculation';
export default class extends Plugin { export default class extends Plugin {
workflow: WorkflowPlugin;
async load() { async load() {
this.db.registerFieldTypes({ this.db.registerFieldTypes({
expression: ExpressionField, expression: ExpressionField,
}); });
const workflowPlugin = this.app.getPlugin('workflow') as WorkflowPlugin; const workflowPlugin = this.app.getPlugin<WorkflowPlugin>(WorkflowPlugin);
this.workflow = workflowPlugin; workflowPlugin.registerInstruction('dynamic-calculation', DynamicCalculation);
workflowPlugin.instructions.register('dynamic-calculation', new DynamicCalculation(workflowPlugin));
} }
} }

View File

@ -6,7 +6,6 @@ import { NAMESPACE, useLang } from '../locale';
export default class extends Trigger { export default class extends Trigger {
title = `{{t("Form event", { ns: "${NAMESPACE}" })}}`; title = `{{t("Form event", { ns: "${NAMESPACE}" })}}`;
type = 'form';
description = `{{t("Event triggers when submitted a workflow bound form action.", { ns: "${NAMESPACE}" })}}`; description = `{{t("Event triggers when submitted a workflow bound form action.", { ns: "${NAMESPACE}" })}}`;
fieldset = { fieldset = {
collection: { collection: {

View File

@ -13,7 +13,6 @@ export default class extends Plugin {
// You can get and modify the app instance here // You can get and modify the app instance here
async load() { async load() {
const workflow = this.app.pm.get('workflow') as WorkflowPlugin; const workflow = this.app.pm.get('workflow') as WorkflowPlugin;
const formTrigger = new FormTrigger(); workflow.registerTrigger('form', FormTrigger);
workflow.triggers.register(formTrigger.type, formTrigger);
} }
} }

View File

@ -5,10 +5,10 @@ import { Model, modelAssociationByKey } from '@nocobase/database';
import WorkflowPlugin, { Trigger, WorkflowModel, toJSON } from '@nocobase/plugin-workflow'; import WorkflowPlugin, { Trigger, WorkflowModel, toJSON } from '@nocobase/plugin-workflow';
export default class extends Trigger { export default class extends Trigger {
constructor(plugin: WorkflowPlugin) { constructor(workflow: WorkflowPlugin) {
super(plugin); super(workflow);
plugin.app.resourcer.use(this.middleware); workflow.app.resourcer.use(this.middleware);
} }
async triggerAction(context, next) { async triggerAction(context, next) {
@ -58,7 +58,7 @@ export default class extends Trigger {
}; };
const triggers = triggerWorkflows.split(',').map((trigger) => trigger.split('!')); const triggers = triggerWorkflows.split(',').map((trigger) => trigger.split('!'));
const workflowRepo = this.plugin.db.getRepository('workflows'); const workflowRepo = this.workflow.db.getRepository('workflows');
const workflows = await workflowRepo.find({ const workflows = await workflowRepo.find({
filter: { filter: {
key: triggers.map((trigger) => trigger[0]), key: triggers.map((trigger) => trigger[0]),
@ -95,11 +95,11 @@ export default class extends Trigger {
appends, appends,
}); });
} }
this.plugin.trigger(workflow, { data: toJSON(payload), ...userInfo }); this.workflow.trigger(workflow, { data: toJSON(payload), ...userInfo });
}); });
} else { } else {
const data = trigger[1] ? get(values, trigger[1]) : values; const data = trigger[1] ? get(values, trigger[1]) : values;
this.plugin.trigger(workflow, { this.workflow.trigger(workflow, {
data, data,
...userInfo, ...userInfo,
}); });

View File

@ -4,11 +4,8 @@ import WorkflowPlugin from '@nocobase/plugin-workflow';
import FormTrigger from './FormTrigger'; import FormTrigger from './FormTrigger';
export default class extends Plugin { export default class extends Plugin {
workflow: WorkflowPlugin;
async load() { async load() {
const workflowPlugin = this.app.getPlugin('workflow') as WorkflowPlugin; const workflowPlugin = this.app.getPlugin<WorkflowPlugin>(WorkflowPlugin);
this.workflow = workflowPlugin;
workflowPlugin.triggers.register('form', new FormTrigger(workflowPlugin)); workflowPlugin.triggers.register('form', new FormTrigger(workflowPlugin));
} }
} }

View File

@ -13,7 +13,6 @@ export default class extends Plugin {
// You can get and modify the app instance here // You can get and modify the app instance here
async load() { async load() {
const workflow = this.app.pm.get('workflow') as WorkflowPlugin; const workflow = this.app.pm.get('workflow') as WorkflowPlugin;
const loopInstruction = new LoopInstruction(); workflow.registerInstruction('loop', LoopInstruction);
workflow.instructions.register(loopInstruction.type, loopInstruction);
} }
} }

View File

@ -4,11 +4,8 @@ import { default as WorkflowPlugin } from '@nocobase/plugin-workflow';
import LoopInstruction from './LoopInstruction'; import LoopInstruction from './LoopInstruction';
export default class extends Plugin { export default class extends Plugin {
workflow: WorkflowPlugin;
async load() { async load() {
const workflowPlugin = this.app.getPlugin('workflow') as WorkflowPlugin; const workflowPlugin = this.app.getPlugin<WorkflowPlugin>(WorkflowPlugin);
this.workflow = workflowPlugin; workflowPlugin.registerInstruction('loop', LoopInstruction);
workflowPlugin.instructions.register('loop', new LoopInstruction(workflowPlugin));
} }
} }

View File

@ -1,6 +1,6 @@
import Database from '@nocobase/database'; import Database from '@nocobase/database';
import { Application } from '@nocobase/server'; import { Application } from '@nocobase/server';
import { BRANCH_INDEX, EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow'; import { EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow';
import { getApp, sleep } from '@nocobase/plugin-workflow-test'; import { getApp, sleep } from '@nocobase/plugin-workflow-test';
import Plugin from '..'; import Plugin from '..';
@ -404,7 +404,7 @@ describe('workflow > instructions > loop', () => {
const n2 = await workflow.createNode({ const n2 = await workflow.createNode({
type: 'loop', type: 'loop',
branchIndex: BRANCH_INDEX.ON_TRUE, branchIndex: 1,
upstreamId: n1.id, upstreamId: n1.id,
config: { config: {
target: 0, target: 0,
@ -442,7 +442,7 @@ describe('workflow > instructions > loop', () => {
const n2 = await workflow.createNode({ const n2 = await workflow.createNode({
type: 'loop', type: 'loop',
branchIndex: BRANCH_INDEX.ON_TRUE, branchIndex: 1,
upstreamId: n1.id, upstreamId: n1.id,
config: { config: {
target: 2, target: 2,

View File

@ -85,8 +85,8 @@ function getMode(mode) {
export default class extends Instruction { export default class extends Instruction {
formTypes = new Registry<FormHandler>(); formTypes = new Registry<FormHandler>();
constructor(public plugin: WorkflowPlugin) { constructor(public workflow: WorkflowPlugin) {
super(plugin); super(workflow);
initFormTypes(this); initFormTypes(this);
} }
@ -103,7 +103,7 @@ export default class extends Instruction {
}); });
// NOTE: batch create users jobs // NOTE: batch create users jobs
const UserJobModel = processor.options.plugin.db.getModel('users_jobs'); const UserJobModel = this.workflow.app.db.getModel('users_jobs');
await UserJobModel.bulkCreate( await UserJobModel.bulkCreate(
assignees.map((userId) => ({ assignees.map((userId) => ({
userId, userId,
@ -114,7 +114,7 @@ export default class extends Instruction {
status: JOB_STATUS.PENDING, status: JOB_STATUS.PENDING,
})), })),
{ {
transaction: processor.transaction, // transaction: processor.transaction,
}, },
); );
@ -125,13 +125,13 @@ export default class extends Instruction {
// NOTE: check all users jobs related if all done then continue as parallel // NOTE: check all users jobs related if all done then continue as parallel
const { assignees = [], mode } = node.config as ManualConfig; const { assignees = [], mode } = node.config as ManualConfig;
const UserJobModel = processor.options.plugin.db.getModel('users_jobs'); const UserJobModel = this.workflow.app.db.getModel('users_jobs');
const distribution = await UserJobModel.count({ const distribution = await UserJobModel.count({
where: { where: {
jobId: job.id, jobId: job.id,
}, },
group: ['status'], group: ['status'],
transaction: processor.transaction, // transaction: processor.transaction,
}); });
const submitted = distribution.reduce( const submitted = distribution.reduce(

View File

@ -3,16 +3,14 @@ import actions from '@nocobase/actions';
import { HandlerType } from '@nocobase/resourcer'; import { HandlerType } from '@nocobase/resourcer';
import WorkflowPlugin, { JOB_STATUS } from '@nocobase/plugin-workflow'; import WorkflowPlugin, { JOB_STATUS } from '@nocobase/plugin-workflow';
import jobsCollection from './collecions/jobs'; import jobsCollection from './collections/jobs';
import usersCollection from './collecions/users'; import usersCollection from './collections/users';
import usersJobsCollection from './collecions/users_jobs'; import usersJobsCollection from './collections/users_jobs';
import { submit } from './actions'; import { submit } from './actions';
import ManualInstruction from './ManualInstruction'; import ManualInstruction from './ManualInstruction';
export default class extends Plugin { export default class extends Plugin {
workflow: WorkflowPlugin;
async load() { async load() {
this.app.db.collection(usersJobsCollection); this.app.db.collection(usersJobsCollection);
this.app.db.extendCollection(usersCollection); this.app.db.extendCollection(usersCollection);
@ -41,8 +39,9 @@ export default class extends Plugin {
}, },
}); });
const workflowPlugin = this.app.getPlugin('workflow') as WorkflowPlugin; this.app.acl.allow('users_jobs', ['list', 'get', 'submit'], 'loggedIn');
this.workflow = workflowPlugin;
workflowPlugin.instructions.register('manual', new ManualInstruction(workflowPlugin)); const workflowPlugin = this.app.getPlugin<WorkflowPlugin>(WorkflowPlugin);
workflowPlugin.registerInstruction('manual', ManualInstruction);
} }
} }

View File

@ -81,7 +81,7 @@ export async function submit(context: Context, next) {
await handler.call(instruction, userJob, forms[formKey], processor); await handler.call(instruction, userJob, forms[formKey], processor);
} }
await userJob.save({ transaction: processor.transaction }); await userJob.save();
await processor.exit(); await processor.exit();

View File

@ -2,7 +2,7 @@ import { Processor } from '@nocobase/plugin-workflow';
import ManualInstruction from '../ManualInstruction'; import ManualInstruction from '../ManualInstruction';
export default async function (this: ManualInstruction, instance, { collection }, processor: Processor) { export default async function (this: ManualInstruction, instance, { collection }, processor: Processor) {
const repo = this.plugin.db.getRepository(collection); const repo = this.workflow.db.getRepository(collection);
if (!repo) { if (!repo) {
throw new Error(`collection ${collection} for create data on manual node not found`); throw new Error(`collection ${collection} for create data on manual node not found`);
} }
@ -18,6 +18,6 @@ export default async function (this: ManualInstruction, instance, { collection }
context: { context: {
executionId: processor.execution.id, executionId: processor.execution.id,
}, },
transaction: processor.transaction, // transaction: processor.transaction,
}); });
} }

View File

@ -2,7 +2,7 @@ import { Processor } from '@nocobase/plugin-workflow';
import ManualInstruction from '../ManualInstruction'; import ManualInstruction from '../ManualInstruction';
export default async function (this: ManualInstruction, instance, { collection, filter = {} }, processor: Processor) { export default async function (this: ManualInstruction, instance, { collection, filter = {} }, processor: Processor) {
const repo = this.plugin.db.getRepository(collection); const repo = this.workflow.db.getRepository(collection);
if (!repo) { if (!repo) {
throw new Error(`collection ${collection} for update data on manual node not found`); throw new Error(`collection ${collection} for update data on manual node not found`);
} }
@ -18,6 +18,6 @@ export default async function (this: ManualInstruction, instance, { collection,
context: { context: {
executionId: processor.execution.id, executionId: processor.execution.id,
}, },
transaction: processor.transaction, // transaction: processor.transaction,
}); });
} }

View File

@ -13,7 +13,6 @@ export default class extends Plugin {
// You can get and modify the app instance here // You can get and modify the app instance here
async load() { async load() {
const workflow = this.app.pm.get(WorkflowPlugin); const workflow = this.app.pm.get(WorkflowPlugin);
const parallelInstruction = new ParallelInstruction(); workflow.registerInstruction('parallel', ParallelInstruction);
workflow.instructions.register(parallelInstruction.type, parallelInstruction);
} }
} }

View File

@ -110,7 +110,7 @@ export default class extends Instruction {
}); });
if (job.status === JOB_STATUS.PENDING) { if (job.status === JOB_STATUS.PENDING) {
await job.save({ transaction: processor.transaction }); await job.save();
return processor.exit(); return processor.exit();
} }

View File

@ -4,11 +4,8 @@ import WorkflowPlugin from '@nocobase/plugin-workflow';
import ParallelInstruction from './ParallelInstruction'; import ParallelInstruction from './ParallelInstruction';
export default class extends Plugin { export default class extends Plugin {
workflow: WorkflowPlugin;
async load() { async load() {
const workflowPlugin = this.app.getPlugin('workflow') as WorkflowPlugin; const workflowPlugin = this.app.getPlugin<WorkflowPlugin>(WorkflowPlugin);
this.workflow = workflowPlugin; workflowPlugin.registerInstruction('parallel', ParallelInstruction);
workflowPlugin.instructions.register('parallel', new ParallelInstruction(workflowPlugin));
} }
} }

View File

@ -1,6 +1,6 @@
import Database from '@nocobase/database'; import Database from '@nocobase/database';
import { Application } from '@nocobase/server'; import { Application } from '@nocobase/server';
import { BRANCH_INDEX, EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow'; import { EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow';
import { getApp, sleep } from '@nocobase/plugin-workflow-test'; import { getApp, sleep } from '@nocobase/plugin-workflow-test';
import Plugin from '..'; import Plugin from '..';
@ -453,7 +453,7 @@ describe('workflow > instructions > parallel', () => {
const n2 = await workflow.createNode({ const n2 = await workflow.createNode({
type: 'parallel', type: 'parallel',
branchIndex: BRANCH_INDEX.ON_TRUE, branchIndex: 1,
upstreamId: n1.id, upstreamId: n1.id,
}); });
@ -521,7 +521,7 @@ describe('workflow > instructions > parallel', () => {
const n4 = await workflow.createNode({ const n4 = await workflow.createNode({
type: 'echo', type: 'echo',
upstreamId: n3.id, upstreamId: n3.id,
branchIndex: BRANCH_INDEX.ON_TRUE, branchIndex: 1,
}); });
const n5 = await workflow.createNode({ const n5 = await workflow.createNode({

View File

@ -1,8 +1,11 @@
import { ArrayItems } from '@formily/antd-v5'; import { ArrayItems } from '@formily/antd-v5';
import { defaultFieldNames } from '@nocobase/client'; import {
Instruction,
import { Instruction, WorkflowVariableInput, WorkflowVariableJSON } from '@nocobase/plugin-workflow/client'; WorkflowVariableInput,
WorkflowVariableJSON,
defaultFieldNames,
} from '@nocobase/plugin-workflow/client';
import { NAMESPACE } from '../locale'; import { NAMESPACE } from '../locale';

View File

@ -13,7 +13,6 @@ export default class extends Plugin {
// You can get and modify the app instance here // You can get and modify the app instance here
async load() { async load() {
const workflow = this.app.pm.get('workflow') as WorkflowPlugin; const workflow = this.app.pm.get('workflow') as WorkflowPlugin;
const requestInstruction = new RequestInstruction(); workflow.registerInstruction('request', RequestInstruction);
workflow.instructions.register(requestInstruction.type, requestInstruction);
} }
} }

View File

@ -4,11 +4,8 @@ import WorkflowPlugin from '@nocobase/plugin-workflow';
import RequestInstruction from './RequestInstruction'; import RequestInstruction from './RequestInstruction';
export default class extends Plugin { export default class extends Plugin {
workflow: WorkflowPlugin;
async load() { async load() {
const workflowPlugin = this.app.getPlugin('workflow') as WorkflowPlugin; const workflowPlugin = this.app.getPlugin<WorkflowPlugin>(WorkflowPlugin);
this.workflow = workflowPlugin; workflowPlugin.registerInstruction('request', RequestInstruction);
workflowPlugin.instructions.register('request', new RequestInstruction(workflowPlugin));
} }
} }

View File

@ -65,7 +65,7 @@ export default class extends Instruction {
}) })
.finally(() => { .finally(() => {
processor.logger.info(`request (#${node.id}) response received, status: ${job.get('status')}`); processor.logger.info(`request (#${node.id}) response received, status: ${job.get('status')}`);
this.plugin.resume(job); this.workflow.resume(job);
}); });
processor.logger.info(`request (#${node.id}) sent to "${config.url}", waiting for response...`); processor.logger.info(`request (#${node.id}) sent to "${config.url}", waiting for response...`);

View File

@ -1,6 +1,6 @@
import { css, defaultFieldNames } from '@nocobase/client'; import { css } from '@nocobase/client';
import { Instruction, WorkflowVariableRawTextArea } from '@nocobase/plugin-workflow/client'; import { Instruction, WorkflowVariableRawTextArea, defaultFieldNames } from '@nocobase/plugin-workflow/client';
import { NAMESPACE } from '../locale'; import { NAMESPACE } from '../locale';

View File

@ -13,7 +13,6 @@ export default class extends Plugin {
// You can get and modify the app instance here // You can get and modify the app instance here
async load() { async load() {
const workflow = this.app.pm.get('workflow') as WorkflowPlugin; const workflow = this.app.pm.get('workflow') as WorkflowPlugin;
const sqlInstruction = new SQLInstruction(); workflow.registerInstruction('sql', SQLInstruction);
workflow.instructions.register(sqlInstruction.type, sqlInstruction);
} }
} }

View File

@ -4,11 +4,8 @@ import WorkflowPlugin from '@nocobase/plugin-workflow';
import SQLInstruction from './SQLInstruction'; import SQLInstruction from './SQLInstruction';
export default class extends Plugin { export default class extends Plugin {
workflow: WorkflowPlugin;
async load() { async load() {
const workflowPlugin = this.app.getPlugin('workflow') as WorkflowPlugin; const workflowPlugin = this.app.getPlugin<WorkflowPlugin>(WorkflowPlugin);
this.workflow = workflowPlugin; workflowPlugin.registerInstruction('sql', SQLInstruction);
workflowPlugin.instructions.register('sql', new SQLInstruction(workflowPlugin));
} }
} }

View File

@ -11,7 +11,7 @@ export default class extends Instruction {
} }
const result = await sequelize.query(sql, { const result = await sequelize.query(sql, {
transaction: processor.transaction, // transaction: processor.transaction,
// plain: true, // plain: true,
// model: db.getCollection(node.config.collection).model // model: db.getCollection(node.config.collection).model
}); });

View File

@ -45,6 +45,26 @@ export default class extends Plugin {
})); }));
}; };
registerTrigger(type: string, trigger: Trigger | { new (): Trigger }) {
if (typeof trigger === 'function') {
this.triggers.register(type, new trigger());
} else if (trigger) {
this.triggers.register(type, trigger);
} else {
throw new TypeError('invalid trigger type to register');
}
}
registerInstruction(type: string, instruction: Instruction | { new (): Instruction }) {
if (typeof instruction === 'function') {
this.instructions.register(type, new instruction());
} else if (instruction instanceof Instruction) {
this.instructions.register(type, instruction);
} else {
throw new TypeError('invalid instruction type to register');
}
}
async load() { async load() {
this.addRoutes(); this.addRoutes();
this.addScopes(); this.addScopes();
@ -57,15 +77,15 @@ export default class extends Plugin {
aclSnippet: 'pm.workflow.workflows', aclSnippet: 'pm.workflow.workflows',
}); });
this.triggers.register('collection', new CollectionTrigger()); this.registerTrigger('collection', CollectionTrigger);
this.triggers.register('schedule', new ScheduleTrigger()); this.registerTrigger('schedule', ScheduleTrigger);
this.instructions.register('calculation', new CalculationInstruction()); this.registerInstruction('calculation', CalculationInstruction);
this.instructions.register('condition', new ConditionInstruction()); this.registerInstruction('condition', ConditionInstruction);
this.instructions.register('query', new QueryInstruction()); this.registerInstruction('query', QueryInstruction);
this.instructions.register('create', new CreateInstruction()); this.registerInstruction('create', CreateInstruction);
this.instructions.register('update', new UpdateInstruction()); this.registerInstruction('update', UpdateInstruction);
this.instructions.register('destroy', new DestroyInstruction()); this.registerInstruction('destroy', DestroyInstruction);
} }
addScopes() { addScopes() {

View File

@ -1,11 +1,11 @@
import { SchemaInitializerItemType, defaultFieldNames } from '@nocobase/client'; import { SchemaInitializerItemType } from '@nocobase/client';
import { Evaluator, evaluators, getOptions } from '@nocobase/evaluators/client'; import { Evaluator, evaluators, getOptions } from '@nocobase/evaluators/client';
import { RadioWithTooltip } from '../components/RadioWithTooltip'; import { RadioWithTooltip } from '../components/RadioWithTooltip';
import { ValueBlock } from '../components/ValueBlock'; import { ValueBlock } from '../components/ValueBlock';
import { renderEngineReference } from '../components/renderEngineReference'; import { renderEngineReference } from '../components/renderEngineReference';
import { NAMESPACE, lang } from '../locale'; import { NAMESPACE, lang } from '../locale';
import { BaseTypeSets, WorkflowVariableTextArea } from '../variable'; import { BaseTypeSets, WorkflowVariableTextArea, defaultFieldNames } from '../variable';
import { Instruction } from '.'; import { Instruction } from '.';
export default class extends Instruction { export default class extends Instruction {

View File

@ -30,7 +30,13 @@ import { JobStatusOptionsMap } from '../constants';
import { useGetAriaLabelOfAddButton } from '../hooks/useGetAriaLabelOfAddButton'; import { useGetAriaLabelOfAddButton } from '../hooks/useGetAriaLabelOfAddButton';
import { lang } from '../locale'; import { lang } from '../locale';
import useStyles from '../style'; import useStyles from '../style';
import { VariableOption, VariableOptions } from '../variable'; import { UseVariableOptions, VariableOption } from '../variable';
export type NodeAvailableContext = {
workflow: object;
upstream: object;
branchIndex: number;
};
export abstract class Instruction { export abstract class Instruction {
title: string; title: string;
@ -43,10 +49,10 @@ export abstract class Instruction {
scope?: { [key: string]: any }; scope?: { [key: string]: any };
components?: { [key: string]: any }; components?: { [key: string]: any };
Component?(props): JSX.Element; Component?(props): JSX.Element;
useVariables?(node, options?): VariableOption; useVariables?(node, options?: UseVariableOptions): VariableOption;
useScopeVariables?(node, options?): VariableOptions; useScopeVariables?(node, options?): VariableOption[];
useInitializers?(node): SchemaInitializerItemType | null; useInitializers?(node): SchemaInitializerItemType | null;
isAvailable?(ctx: object): boolean; isAvailable?(ctx: NodeAvailableContext): boolean;
} }
function useUpdateAction() { function useUpdateAction() {
@ -362,7 +368,7 @@ export function NodeDefaultView(props) {
className: 'workflow-node-config-button', className: 'workflow-node-config-button',
}, },
}, },
[`${instruction.type}_${data.id}`]: { [data.id]: {
type: 'void', type: 'void',
title: ( title: (
<div <div

View File

@ -101,14 +101,6 @@ const workflowFieldset = {
type: 'object', type: 'object',
'x-component': 'fieldset', 'x-component': 'fieldset',
properties: { properties: {
// NOTE: not to expose this option for now, because hard to track errors
// useTransaction: {
// type: 'boolean',
// title: `{{ t("Use transaction", { ns: "${NAMESPACE}" }) }}`,
// description: `{{ t("Data operation nodes in workflow will run in a same transaction until any interruption. Any failure will cause data rollback, and will also rollback the history of the execution.", { ns: "${NAMESPACE}" }) }}`,
// 'x-decorator': 'FormItem',
// 'x-component': 'Checkbox',
// },
deleteExecutionOnStatus: { deleteExecutionOnStatus: {
type: 'array', type: 'array',
title: `{{ t("Auto delete history when execution is on end status", { ns: "${NAMESPACE}" }) }}`, title: `{{ t("Auto delete history when execution is on end status", { ns: "${NAMESPACE}" }) }}`,

View File

@ -22,7 +22,6 @@ const collectionModeOptions = [
export default class extends Trigger { export default class extends Trigger {
title = `{{t("Collection event", { ns: "${NAMESPACE}" })}}`; title = `{{t("Collection event", { ns: "${NAMESPACE}" })}}`;
type = 'collection';
description = `{{t("Event will be triggered on collection data row created, updated or deleted.", { ns: "${NAMESPACE}" })}}`; description = `{{t("Event will be triggered on collection data row created, updated or deleted.", { ns: "${NAMESPACE}" })}}`;
fieldset = { fieldset = {
collection: { collection: {

View File

@ -7,6 +7,7 @@ import { ISchema, useForm } from '@formily/react';
import { import {
ActionContextProvider, ActionContextProvider,
FieldNames,
FormProvider, FormProvider,
SchemaComponent, SchemaComponent,
SchemaInitializerItemType, SchemaInitializerItemType,
@ -24,7 +25,7 @@ import { useFlowContext } from '../FlowContext';
import { DrawerDescription } from '../components/DrawerDescription'; import { DrawerDescription } from '../components/DrawerDescription';
import { NAMESPACE, lang } from '../locale'; import { NAMESPACE, lang } from '../locale';
import useStyles from '../style'; import useStyles from '../style';
import { VariableOptions } from '../variable'; import { UseVariableOptions, VariableOption } from '../variable';
function useUpdateConfigAction() { function useUpdateConfigAction() {
const form = useForm(); const form = useForm();
@ -54,10 +55,9 @@ function useUpdateConfigAction() {
export abstract class Trigger { export abstract class Trigger {
title: string; title: string;
type: string;
description?: string; description?: string;
// group: string; // group: string;
useVariables?(config: any, options?): VariableOptions; useVariables?(config: Record<string, any>, options?: UseVariableOptions): VariableOption[];
fieldset: { [key: string]: ISchema }; fieldset: { [key: string]: ISchema };
view?: ISchema; view?: ISchema;
scope?: { [key: string]: any }; scope?: { [key: string]: any };

View File

@ -9,7 +9,6 @@ import { SCHEDULE_MODE } from './constants';
export default class extends Trigger { export default class extends Trigger {
title = `{{t("Schedule event", { ns: "${NAMESPACE}" })}}`; title = `{{t("Schedule event", { ns: "${NAMESPACE}" })}}`;
type = 'schedule';
description = `{{t("Event will be scheduled and triggered base on time conditions.", { ns: "${NAMESPACE}" })}}`; description = `{{t("Event will be scheduled and triggered base on time conditions.", { ns: "${NAMESPACE}" })}}`;
fieldset = { fieldset = {
config: { config: {

View File

@ -11,21 +11,26 @@ export type VariableOption = {
key?: string; key?: string;
value?: string; value?: string;
label?: string; label?: string;
children?: VariableOptions; children?: VariableOption[] | null;
[key: string]: any; [key: string]: any;
}; };
export type VariableOptions = VariableOption[] | null;
export type VariableDataType = export type VariableDataType =
| string | 'boolean'
| 'number'
| 'string'
| 'date'
| { | {
type: string; type: 'reference';
options?: { entity?: boolean; collection?: string }; options: {
collection: string;
multiple?: boolean;
entity?: boolean;
};
} }
| ((field: any, appends?: string[]) => boolean); | ((field: any) => boolean);
export type OptionsOfUseVariableOptions = { export type UseVariableOptions = {
types?: VariableDataType[]; types?: VariableDataType[];
fieldNames?: { fieldNames?: {
label?: string; label?: string;
@ -34,7 +39,6 @@ export type OptionsOfUseVariableOptions = {
}; };
appends?: string[] | null; appends?: string[] | null;
depth?: number; depth?: number;
current?: any;
}; };
export const defaultFieldNames = { label: 'label', value: 'value', children: 'children' } as const; export const defaultFieldNames = { label: 'label', value: 'value', children: 'children' } as const;
@ -42,7 +46,7 @@ export const defaultFieldNames = { label: 'label', value: 'value', children: 'ch
export const nodesOptions = { export const nodesOptions = {
label: `{{t("Node result", { ns: "${NAMESPACE}" })}}`, label: `{{t("Node result", { ns: "${NAMESPACE}" })}}`,
value: '$jobsMapByNodeKey', value: '$jobsMapByNodeKey',
useOptions(options: OptionsOfUseVariableOptions) { useOptions(options: UseVariableOptions) {
const { instructions } = usePlugin(WorkflowPlugin); const { instructions } = usePlugin(WorkflowPlugin);
const current = useNodeContext(); const current = useNodeContext();
const upstreams = useAvailableUpstreams(current); const upstreams = useAvailableUpstreams(current);
@ -61,7 +65,7 @@ export const nodesOptions = {
export const triggerOptions = { export const triggerOptions = {
label: `{{t("Trigger variables", { ns: "${NAMESPACE}" })}}`, label: `{{t("Trigger variables", { ns: "${NAMESPACE}" })}}`,
value: '$context', value: '$context',
useOptions(options: OptionsOfUseVariableOptions) { useOptions(options: UseVariableOptions) {
const { triggers } = usePlugin(WorkflowPlugin); const { triggers } = usePlugin(WorkflowPlugin);
const { workflow } = useFlowContext(); const { workflow } = useFlowContext();
const trigger = triggers.get(workflow.type); const trigger = triggers.get(workflow.type);
@ -72,7 +76,7 @@ export const triggerOptions = {
export const scopeOptions = { export const scopeOptions = {
label: `{{t("Scope variables", { ns: "${NAMESPACE}" })}}`, label: `{{t("Scope variables", { ns: "${NAMESPACE}" })}}`,
value: '$scopes', value: '$scopes',
useOptions(options: OptionsOfUseVariableOptions) { useOptions(options: UseVariableOptions & { current: any }) {
const { fieldNames = defaultFieldNames, current } = options; const { fieldNames = defaultFieldNames, current } = options;
const { instructions } = usePlugin(WorkflowPlugin); const { instructions } = usePlugin(WorkflowPlugin);
const source = useNodeContext(); const source = useNodeContext();
@ -98,7 +102,7 @@ export const scopeOptions = {
export const systemOptions = { export const systemOptions = {
label: `{{t("System variables", { ns: "${NAMESPACE}" })}}`, label: `{{t("System variables", { ns: "${NAMESPACE}" })}}`,
value: '$system', value: '$system',
useOptions({ types, fieldNames = defaultFieldNames }: OptionsOfUseVariableOptions) { useOptions({ types, fieldNames = defaultFieldNames }: UseVariableOptions) {
return [ return [
...(!types || types.includes('date') ...(!types || types.includes('date')
? [ ? [
@ -136,13 +140,12 @@ export const BaseTypeSets = {
// { type: 'reference', options: { collection: 'attachments', multiple: false } } // { type: 'reference', options: { collection: 'attachments', multiple: false } }
// { type: 'reference', options: { collection: 'myExpressions', entity: false } } // { type: 'reference', options: { collection: 'myExpressions', entity: false } }
function matchFieldType(field, type): boolean { function matchFieldType(field, type: VariableDataType): boolean {
const inputType = typeof type; if (typeof type === 'string') {
if (inputType === 'string') {
return BaseTypeSets[type]?.has(field.interface); return BaseTypeSets[type]?.has(field.interface);
} }
if (inputType === 'object' && type.type === 'reference') { if (typeof type === 'object' && type.type === 'reference') {
if (isAssociationField(field)) { if (isAssociationField(field)) {
return ( return (
type.options?.entity && (field.collectionName === type.options?.collection || type.options?.collection === '*') type.options?.entity && (field.collectionName === type.options?.collection || type.options?.collection === '*')
@ -157,7 +160,7 @@ function matchFieldType(field, type): boolean {
} }
} }
if (inputType === 'function') { if (typeof type === 'function') {
return type(field); return type(field);
} }
@ -232,7 +235,7 @@ function useOptions(scope, opts) {
}; };
} }
export function useWorkflowVariableOptions(options: OptionsOfUseVariableOptions = {}) { export function useWorkflowVariableOptions(options: UseVariableOptions = {}) {
const fieldNames = Object.assign({}, defaultFieldNames, options.fieldNames ?? {}); const fieldNames = Object.assign({}, defaultFieldNames, options.fieldNames ?? {});
const opts = Object.assign(options, { fieldNames }); const opts = Object.assign(options, { fieldNames });
const result = [ const result = [

View File

@ -12,10 +12,10 @@ import Processor from './Processor';
import initActions from './actions'; import initActions from './actions';
import { EXECUTION_STATUS } from './constants'; import { EXECUTION_STATUS } from './constants';
import initFunctions, { CustomFunction } from './functions'; import initFunctions, { CustomFunction } from './functions';
import type Trigger from './triggers'; import Trigger from './triggers';
import CollectionTrigger from './triggers/CollectionTrigger'; import CollectionTrigger from './triggers/CollectionTrigger';
import ScheduleTrigger from './triggers/ScheduleTrigger'; import ScheduleTrigger from './triggers/ScheduleTrigger';
import type Instruction from './instructions'; import Instruction from './instructions';
import CalculationInstruction from './instructions/CalculationInstruction'; import CalculationInstruction from './instructions/CalculationInstruction';
import ConditionInstruction from './instructions/ConditionInstruction'; import ConditionInstruction from './instructions/ConditionInstruction';
import CreateInstruction from './instructions/CreateInstruction'; import CreateInstruction from './instructions/CreateInstruction';
@ -112,29 +112,45 @@ export default class WorkflowPlugin extends Plugin {
} }
}; };
initTriggers<T extends Trigger>(more: { [key: string]: T | { new (p: Plugin): T } } = {}) { registerTrigger<T extends Trigger>(type: string, trigger: T | { new (p: Plugin): T }) {
const { triggers } = this; if (typeof trigger === 'function') {
this.triggers.register(type, new trigger(this));
} else if (trigger) {
this.triggers.register(type, trigger);
} else {
throw new Error('invalid trigger type to register');
}
}
triggers.register('collection', new CollectionTrigger(this)); registerInstruction<T extends Instruction>(type: string, instruction: T | { new (p: Plugin): T }) {
triggers.register('schedule', new ScheduleTrigger(this)); if (typeof instruction === 'function') {
this.instructions.register(type, new instruction(this));
} else if (instruction) {
this.instructions.register(type, instruction);
} else {
throw new Error('invalid instruction type to register');
}
}
private initTriggers<T extends Trigger>(more: { [key: string]: T | { new (p: Plugin): T } } = {}) {
this.registerTrigger('collection', CollectionTrigger);
this.registerTrigger('schedule', ScheduleTrigger);
for (const [name, trigger] of Object.entries(more)) { for (const [name, trigger] of Object.entries(more)) {
triggers.register(name, typeof trigger === 'function' ? new trigger(this) : trigger); this.registerTrigger(name, trigger);
} }
} }
initInstructions<T extends Instruction>(more: { [key: string]: T | { new (p: Plugin): T } } = {}) { private initInstructions<T extends Instruction>(more: { [key: string]: T | { new (p: Plugin): T } } = {}) {
const { instructions } = this; this.registerInstruction('calculation', CalculationInstruction);
this.registerInstruction('condition', ConditionInstruction);
instructions.register('calculation', new CalculationInstruction(this)); this.registerInstruction('create', CreateInstruction);
instructions.register('condition', new ConditionInstruction(this)); this.registerInstruction('destroy', DestroyInstruction);
instructions.register('create', new CreateInstruction(this)); this.registerInstruction('query', QueryInstruction);
instructions.register('destroy', new DestroyInstruction(this)); this.registerInstruction('update', UpdateInstruction);
instructions.register('query', new QueryInstruction(this));
instructions.register('update', new UpdateInstruction(this));
for (const [name, instruction] of Object.entries({ ...more })) { for (const [name, instruction] of Object.entries({ ...more })) {
instructions.register(name, typeof instruction === 'function' ? new instruction(this) : instruction); this.registerInstruction(name, instruction);
} }
} }
@ -171,7 +187,6 @@ export default class WorkflowPlugin extends Plugin {
actions: ['workflows:list'], actions: ['workflows:list'],
}); });
this.app.acl.allow('users_jobs', ['list', 'get', 'submit'], 'loggedIn');
this.app.acl.allow('workflows', ['trigger'], 'loggedIn'); this.app.acl.allow('workflows', ['trigger'], 'loggedIn');
await db.import({ await db.import({

View File

@ -25,8 +25,6 @@ export default class Processor {
logger: Logger; logger: Logger;
transaction?: Transaction;
nodes: FlowNodeModel[] = []; nodes: FlowNodeModel[] = [];
nodesMap = new Map<number, FlowNodeModel>(); nodesMap = new Map<number, FlowNodeModel>();
jobsMap = new Map<number, JobModel>(); jobsMap = new Map<number, JobModel>();
@ -67,35 +65,18 @@ export default class Processor {
}); });
} }
private async getTransaction() {
if (!this.execution.workflow.options?.useTransaction) {
return;
}
const { options } = this;
// @ts-ignore
return options.transaction && !options.transaction.finished
? options.transaction
: await options.plugin.db.sequelize.transaction();
}
public async prepare() { public async prepare() {
const { execution } = this; const { execution } = this;
if (!execution.workflow) { if (!execution.workflow) {
execution.workflow = await execution.getWorkflow(); execution.workflow = await execution.getWorkflow();
} }
const transaction = await this.getTransaction();
this.transaction = transaction;
const nodes = await execution.workflow.getNodes(); const nodes = await execution.workflow.getNodes();
this.makeNodes(nodes); this.makeNodes(nodes);
const jobs = await execution.getJobs({ const jobs = await execution.getJobs({
order: [['id', 'ASC']], order: [['id', 'ASC']],
transaction,
}); });
this.makeJobs(jobs); this.makeJobs(jobs);
@ -125,13 +106,6 @@ export default class Processor {
await this.recall(node, job); await this.recall(node, job);
} }
private async commit() {
// @ts-ignore
if (this.transaction && (!this.options.transaction || this.options.transaction.finished)) {
await this.transaction.commit();
}
}
private async exec(instruction: Runner, node: FlowNodeModel, prevJob) { private async exec(instruction: Runner, node: FlowNodeModel, prevJob) {
let job; let job;
try { try {
@ -224,10 +198,9 @@ export default class Processor {
async exit(s?: number) { async exit(s?: number) {
if (typeof s === 'number') { if (typeof s === 'number') {
const status = (<typeof Processor>this.constructor).StatusMap[s] ?? Math.sign(s); const status = (<typeof Processor>this.constructor).StatusMap[s] ?? Math.sign(s);
await this.execution.update({ status }, { transaction: this.transaction }); await this.execution.update({ status });
} }
this.logger.info(`execution (${this.execution.id}) exiting with status ${this.execution.status}`); this.logger.info(`execution (${this.execution.id}) exiting with status ${this.execution.status}`);
await this.commit();
return null; return null;
} }
@ -237,22 +210,15 @@ export default class Processor {
const { model } = database.getCollection('jobs'); const { model } = database.getCollection('jobs');
let job; let job;
if (payload instanceof model) { if (payload instanceof model) {
job = await payload.save({ transaction: this.transaction }); job = await payload.save();
} else if (payload.id) { } else if (payload.id) {
job = await model.findByPk(payload.id); job = await model.findByPk(payload.id);
await job.update(payload, { await job.update(payload);
transaction: this.transaction,
});
} else { } else {
job = await model.create( job = await model.create({
{
...payload, ...payload,
executionId: this.execution.id, executionId: this.execution.id,
}, });
{
transaction: this.transaction,
},
);
} }
this.jobsMap.set(job.id, job); this.jobsMap.set(job.id, job);

View File

@ -1,11 +1,11 @@
import Database from '@nocobase/database'; import { MockDatabase } from '@nocobase/database';
import { MockServer } from '@nocobase/test'; import { MockServer } from '@nocobase/test';
import { getApp, sleep } from '@nocobase/plugin-workflow-test'; import { getApp, sleep } from '@nocobase/plugin-workflow-test';
import { BRANCH_INDEX, EXECUTION_STATUS, JOB_STATUS } from '../constants'; import { EXECUTION_STATUS, JOB_STATUS } from '../constants';
describe('workflow > Processor', () => { describe('workflow > Processor', () => {
let app: MockServer; let app: MockServer;
let db: Database; let db: MockDatabase;
let PostRepo; let PostRepo;
let WorkflowModel; let WorkflowModel;
let workflow; let workflow;
@ -251,13 +251,13 @@ describe('workflow > Processor', () => {
const n2 = await workflow.createNode({ const n2 = await workflow.createNode({
type: 'echo', type: 'echo',
branchIndex: BRANCH_INDEX.ON_TRUE, branchIndex: 1,
upstreamId: n1.id, upstreamId: n1.id,
}); });
await workflow.createNode({ await workflow.createNode({
type: 'echo', type: 'echo',
branchIndex: BRANCH_INDEX.ON_FALSE, branchIndex: 0,
upstreamId: n1.id, upstreamId: n1.id,
}); });
@ -283,7 +283,7 @@ describe('workflow > Processor', () => {
const n2 = await workflow.createNode({ const n2 = await workflow.createNode({
type: 'prompt', type: 'prompt',
branchIndex: BRANCH_INDEX.ON_TRUE, branchIndex: 1,
upstreamId: n1.id, upstreamId: n1.id,
}); });
@ -323,7 +323,7 @@ describe('workflow > Processor', () => {
const n2 = await workflow.createNode({ const n2 = await workflow.createNode({
type: 'prompt->error', type: 'prompt->error',
branchIndex: BRANCH_INDEX.ON_TRUE, branchIndex: 1,
upstreamId: n1.id, upstreamId: n1.id,
}); });

View File

@ -1,7 +1,8 @@
import Database from '@nocobase/database'; import Database from '@nocobase/database';
import { Application } from '@nocobase/server'; import { Application } from '@nocobase/server';
import { getApp, sleep } from '@nocobase/plugin-workflow-test'; import { getApp, sleep } from '@nocobase/plugin-workflow-test';
import { BRANCH_INDEX, EXECUTION_STATUS, JOB_STATUS } from '../../constants'; import { EXECUTION_STATUS, JOB_STATUS } from '../../constants';
import { BRANCH_INDEX } from '../../instructions/ConditionInstruction';
describe('workflow > instructions > condition', () => { describe('workflow > instructions > condition', () => {
let app: Application; let app: Application;

View File

@ -54,12 +54,11 @@ export async function destroy(context: Context, next) {
} }
export async function revision(context: Context, next) { export async function revision(context: Context, next) {
const plugin = context.app.getPlugin('workflow') as Plugin; const plugin = context.app.getPlugin(Plugin);
const { db } = context;
const repository = utils.getRepositoryFromParams(context); const repository = utils.getRepositoryFromParams(context);
const { filterByTk, filter = {}, values = {} } = context.action.params; const { filterByTk, filter = {}, values = {} } = context.action.params;
context.body = await db.sequelize.transaction(async (transaction) => { context.body = await context.db.sequelize.transaction(async (transaction) => {
const origin = await repository.findOne({ const origin = await repository.findOne({
filterByTk, filterByTk,
filter, filter,
@ -140,7 +139,7 @@ export async function revision(context: Context, next) {
} }
export async function sync(context: Context, next) { export async function sync(context: Context, next) {
const plugin = context.app.getPlugin('workflow'); const plugin = context.app.getPlugin(Plugin);
const repository = utils.getRepositoryFromParams(context); const repository = utils.getRepositoryFromParams(context);
const { filterByTk, filter = {} } = context.action.params; const { filterByTk, filter = {} } = context.action.params;

View File

@ -35,11 +35,6 @@ export default function () {
required: true, required: true,
defaultValue: {}, defaultValue: {},
}, },
{
type: 'boolean',
name: 'useTransaction',
// defaultValue: true,
},
{ {
type: 'hasMany', type: 'hasMany',
name: 'nodes', name: 'nodes',

View File

@ -8,7 +8,7 @@ export const EXECUTION_STATUS = {
CANCELED: -4, CANCELED: -4,
REJECTED: -5, REJECTED: -5,
RETRY_NEEDED: -6, RETRY_NEEDED: -6,
}; } as const;
export const JOB_STATUS = { export const JOB_STATUS = {
PENDING: 0, PENDING: 0,
@ -19,10 +19,4 @@ export const JOB_STATUS = {
CANCELED: -4, CANCELED: -4,
REJECTED: -5, REJECTED: -5,
RETRY_NEEDED: -6, RETRY_NEEDED: -6,
}; } as const;
export const BRANCH_INDEX = {
DEFAULT: null,
ON_TRUE: 1,
ON_FALSE: 0,
};

View File

@ -7,6 +7,12 @@ import type { FlowNodeModel, JobModel } from '../types';
type Comparer = (a: any, b: any) => boolean; type Comparer = (a: any, b: any) => boolean;
export const BRANCH_INDEX = {
DEFAULT: null,
ON_TRUE: 1,
ON_FALSE: 0,
} as const;
export const calculators = new Registry<Comparer>(); export const calculators = new Registry<Comparer>();
// built-in functions // built-in functions

View File

@ -15,7 +15,7 @@ export class CreateInstruction extends Instruction {
context: { context: {
executionId: processor.execution.id, executionId: processor.execution.id,
}, },
transaction: processor.transaction, // transaction: processor.transaction,
}); });
let result = created; let result = created;
@ -28,7 +28,7 @@ export class CreateInstruction extends Instruction {
result = await repository.findOne({ result = await repository.findOne({
filterByTk: created[model.primaryKeyAttribute], filterByTk: created[model.primaryKeyAttribute],
appends: Array.from(includeFields), appends: Array.from(includeFields),
transaction: processor.transaction, // transaction: processor.transaction,
}); });
} }

View File

@ -14,7 +14,7 @@ export class DestroyInstruction extends Instruction {
context: { context: {
executionId: processor.execution.id, executionId: processor.execution.id,
}, },
transaction: processor.transaction, // transaction: processor.transaction,
}); });
return { return {

View File

@ -33,7 +33,7 @@ export class QueryInstruction extends Instruction {
.filter((item) => item.field) .filter((item) => item.field)
.map((item) => `${item.direction?.toLowerCase() === 'desc' ? '-' : ''}${item.field}`), .map((item) => `${item.direction?.toLowerCase() === 'desc' ? '-' : ''}${item.field}`),
appends, appends,
transaction: processor.transaction, // transaction: processor.transaction,
}); });
if (failOnEmpty && (multiple ? !result.length : !result)) { if (failOnEmpty && (multiple ? !result.length : !result)) {

View File

@ -14,7 +14,7 @@ export class UpdateInstruction extends Instruction {
context: { context: {
executionId: processor.execution.id, executionId: processor.execution.id,
}, },
transaction: processor.transaction, // transaction: processor.transaction,
}); });
return { return {

View File

@ -18,7 +18,7 @@ export type Runner = (node: FlowNodeModel, input: any, processor: Processor) =>
// what should a instruction do? // what should a instruction do?
// - base on input and context, do any calculations or system call (io), and produce a result or pending. // - base on input and context, do any calculations or system call (io), and produce a result or pending.
export abstract class Instruction { export abstract class Instruction {
constructor(public plugin: Plugin) {} constructor(public workflow: Plugin) {}
abstract run(node: FlowNodeModel, input: any, processor: Processor): InstructionResult; abstract run(node: FlowNodeModel, input: any, processor: Processor): InstructionResult;

View File

@ -85,7 +85,7 @@ async function handler(this: CollectionTrigger, workflow: WorkflowModel, data: M
// TODO: `result.toJSON()` throws error // TODO: `result.toJSON()` throws error
const json = toJSON(result); const json = toJSON(result);
this.plugin.trigger( this.workflow.trigger(
workflow, workflow,
{ data: json }, { data: json },
{ {
@ -98,7 +98,7 @@ export default class CollectionTrigger extends Trigger {
events = new Map(); events = new Map();
on(workflow: WorkflowModel) { on(workflow: WorkflowModel) {
const { db } = this.plugin.app; const { db } = this.workflow.app;
const { collection, mode } = workflow.config; const { collection, mode } = workflow.config;
const Collection = db.getCollection(collection); const Collection = db.getCollection(collection);
if (!Collection) { if (!Collection) {
@ -125,7 +125,7 @@ export default class CollectionTrigger extends Trigger {
} }
off(workflow: WorkflowModel) { off(workflow: WorkflowModel) {
const { db } = this.plugin.app; const { db } = this.workflow.app;
const { collection, mode } = workflow.config; const { collection, mode } = workflow.config;
const Collection = db.getCollection(collection); const Collection = db.getCollection(collection);
if (!Collection) { if (!Collection) {

View File

@ -103,7 +103,7 @@ ScheduleModes.set(SCHEDULE_MODE.CONSTANT, {
} }
} }
this.plugin.trigger(workflow, { date: now }); this.workflow.trigger(workflow, { date: now });
return 1; return 1;
}, },
@ -201,7 +201,7 @@ ScheduleModes.set(SCHEDULE_MODE.COLLECTION_FIELD, {
this.setCache(workflow); this.setCache(workflow);
}; };
this.events.set(name, listener); this.events.set(name, listener);
this.plugin.app.db.on(event, listener); this.workflow.app.db.on(event, listener);
}, },
off(workflow) { off(workflow) {
@ -211,12 +211,12 @@ ScheduleModes.set(SCHEDULE_MODE.COLLECTION_FIELD, {
if (this.events.has(name)) { if (this.events.has(name)) {
const listener = this.events.get(name); const listener = this.events.get(name);
this.events.delete(name); this.events.delete(name);
this.plugin.app.db.off(event, listener); this.workflow.app.db.off(event, listener);
} }
}, },
async shouldCache(workflow, now) { async shouldCache(workflow, now) {
const { db } = this.plugin.app; const { db } = this.workflow.app;
const { startsOn, endsOn, repeat, collection } = workflow.config; const { startsOn, endsOn, repeat, collection } = workflow.config;
const timestamp = now.getTime(); const timestamp = now.getTime();
@ -305,7 +305,7 @@ ScheduleModes.set(SCHEDULE_MODE.COLLECTION_FIELD, {
}, },
}); });
const tsFn = DialectTimestampFnMap[this.plugin.app.db.options.dialect]; const tsFn = DialectTimestampFnMap[this.workflow.app.db.options.dialect];
if (typeof repeat === 'number' && tsFn) { if (typeof repeat === 'number' && tsFn) {
const modExp = fn( const modExp = fn(
'MOD', 'MOD',
@ -343,7 +343,7 @@ ScheduleModes.set(SCHEDULE_MODE.COLLECTION_FIELD, {
}); });
} }
const repo = this.plugin.app.db.getRepository(collection); const repo = this.workflow.app.db.getRepository(collection);
const instances = await repo.find({ const instances = await repo.find({
where: { where: {
[Op.and]: conditions, [Op.and]: conditions,
@ -357,7 +357,7 @@ ScheduleModes.set(SCHEDULE_MODE.COLLECTION_FIELD, {
}); });
instances.forEach((item) => { instances.forEach((item) => {
this.plugin.trigger(workflow, { this.workflow.trigger(workflow, {
date: now, date: now,
data: item.toJSON(), data: item.toJSON(),
}); });
@ -423,10 +423,10 @@ export default class ScheduleTrigger extends Trigger {
// caching workflows in range, default to 1min // caching workflows in range, default to 1min
cacheCycle = 60_000; cacheCycle = 60_000;
constructor(plugin: Plugin) { constructor(workflow: Plugin) {
super(plugin); super(workflow);
plugin.app.on('beforeStop', () => { workflow.app.on('beforeStop', () => {
if (this.timer) { if (this.timer) {
clearInterval(this.timer); clearInterval(this.timer);
} }
@ -434,7 +434,7 @@ export default class ScheduleTrigger extends Trigger {
} }
init() { init() {
if (this.plugin.app.getPlugin('multi-app-share-collection')?.enabled && this.plugin.app.name !== 'main') { if (this.workflow.app.getPlugin('multi-app-share-collection')?.enabled && this.workflow.app.name !== 'main') {
return; return;
} }
@ -474,7 +474,7 @@ export default class ScheduleTrigger extends Trigger {
async onTick(now) { async onTick(now) {
// NOTE: trigger workflows in sequence when sqlite due to only one transaction // NOTE: trigger workflows in sequence when sqlite due to only one transaction
const isSqlite = this.plugin.app.db.options.dialect === 'sqlite'; const isSqlite = this.workflow.app.db.options.dialect === 'sqlite';
return Array.from(this.cache.values()).reduce( return Array.from(this.cache.values()).reduce(
(prev, workflow) => { (prev, workflow) => {
if (!this.shouldTrigger(workflow, now)) { if (!this.shouldTrigger(workflow, now)) {
@ -491,7 +491,7 @@ export default class ScheduleTrigger extends Trigger {
} }
async reload() { async reload() {
const WorkflowRepo = this.plugin.app.db.getRepository('workflows'); const WorkflowRepo = this.workflow.app.db.getRepository('workflows');
const workflows = await WorkflowRepo.find({ const workflows = await WorkflowRepo.find({
filter: { enabled: true, type: 'schedule' }, filter: { enabled: true, type: 'schedule' },
}); });
@ -510,7 +510,7 @@ export default class ScheduleTrigger extends Trigger {
const should = await this.shouldCache(workflow, now); const should = await this.shouldCache(workflow, now);
if (should) { if (should) {
this.plugin.getLogger(workflow.id).info('caching scheduled workflow will run in next minute'); this.workflow.getLogger(workflow.id).info('caching scheduled workflow will run in next minute');
} }
this.setCache(workflow, !should); this.setCache(workflow, !should);

View File

@ -3,7 +3,7 @@ import type Plugin from '../Plugin';
import type { WorkflowModel } from '../types'; import type { WorkflowModel } from '../types';
export abstract class Trigger { export abstract class Trigger {
constructor(public readonly plugin: Plugin) {} constructor(public readonly workflow: Plugin) {}
abstract on(workflow: WorkflowModel): void; abstract on(workflow: WorkflowModel): void;
abstract off(workflow: WorkflowModel): void; abstract off(workflow: WorkflowModel): void;
duplicateConfig?(workflow: WorkflowModel, options: Transactionable): object | Promise<object>; duplicateConfig?(workflow: WorkflowModel, options: Transactionable): object | Promise<object>;

View File

@ -9,9 +9,6 @@ export default class ExecutionModel extends Model {
declare title: string; declare title: string;
declare context: any; declare context: any;
declare status: number; declare status: number;
// NOTE: this duplicated column is for transaction in preparing cycle from workflow
declare useTransaction: boolean;
declare transaction: string;
declare createdAt: Date; declare createdAt: Date;
declare updatedAt: Date; declare updatedAt: Date;

View File

@ -19,7 +19,6 @@ export default class WorkflowModel extends Model {
declare description?: string; declare description?: string;
declare type: string; declare type: string;
declare config: any; declare config: any;
declare useTransaction: boolean;
declare executed: number; declare executed: number;
declare createdAt: Date; declare createdAt: Date;