refactor(plugin-workflow-action-trigger): support to use end node to determine status (#6399)

This commit is contained in:
Junyi 2025-03-10 16:01:35 +08:00 committed by GitHub
parent e3123a4abb
commit 58b5396c30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 124 additions and 4 deletions

View File

@ -12,12 +12,28 @@ import { BelongsTo, HasOne } from 'sequelize';
import { Model, modelAssociationByKey } from '@nocobase/database';
import Application, { DefaultContext } from '@nocobase/server';
import { Context as ActionContext, Next } from '@nocobase/actions';
import PluginErrorHandler from '@nocobase/plugin-error-handler';
import WorkflowPlugin, { EventOptions, Trigger, WorkflowModel, toJSON } from '@nocobase/plugin-workflow';
import WorkflowPlugin, {
EXECUTION_STATUS,
EventOptions,
Trigger,
WorkflowModel,
toJSON,
} from '@nocobase/plugin-workflow';
import { joinCollectionName, parseCollectionName } from '@nocobase/data-source-manager';
interface Context extends ActionContext, DefaultContext {}
class RequestOnActionTriggerError extends Error {
status = 400;
messages: any[] = [];
constructor(message) {
super(message);
this.name = 'RequestOnActionTriggerError';
}
}
export default class extends Trigger {
static TYPE = 'action';
@ -43,6 +59,16 @@ export default class extends Trigger {
}
workflow.app.dataSourceManager.use(triggerWorkflowActionMiddleware);
workflow.app.pm.get(PluginErrorHandler).errorHandler.register(
(err) => err instanceof RequestOnActionTriggerError || err.name === 'RequestOnActionTriggerError',
async (err, ctx) => {
ctx.body = {
errors: err.messages,
};
ctx.status = err.status;
},
);
}
/**
@ -177,7 +203,35 @@ export default class extends Trigger {
}
for (const event of syncGroup) {
await this.workflow.trigger(event[0], event[1]);
const processor = await this.workflow.trigger(event[0], event[1], { httpContext: context });
// NOTE: workflow trigger failed
if (!processor) {
return context.throw(500);
}
const { lastSavedJob, nodesMap } = processor;
const lastNode = nodesMap.get(lastSavedJob?.nodeId);
// NOTE: passthrough
if (processor.execution.status === EXECUTION_STATUS.RESOLVED) {
if (lastNode?.type === 'end') {
return;
}
continue;
}
// NOTE: intercept
if (processor.execution.status < EXECUTION_STATUS.STARTED) {
if (lastNode?.type !== 'end') {
return context.throw(500, 'Workflow on your action failed, please contact the administrator');
}
const err = new RequestOnActionTriggerError('Request failed');
err.status = 400;
err.messages = context.state.messages;
return context.throw(err.status, err);
}
// NOTE: should not be pending
return context.throw(500, 'Workflow on your action hangs, please contact the administrator');
}
for (const event of asyncGroup) {

View File

@ -9,7 +9,7 @@
import { omit } from 'lodash';
import Database from '@nocobase/database';
import { EXECUTION_STATUS } from '@nocobase/plugin-workflow';
import { EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow';
import { getApp, sleep } from '@nocobase/plugin-workflow-test';
import { MockServer } from '@nocobase/test';
@ -30,7 +30,7 @@ describe('workflow > action-trigger', () => {
beforeEach(async () => {
app = await getApp({
plugins: ['users', 'auth', 'acl', 'data-source-manager', 'system-settings', Plugin],
plugins: ['users', 'auth', 'acl', 'data-source-manager', 'system-settings', 'error-handler', Plugin],
});
await app.pm.get('auth').install();
agent = app.agent();
@ -661,6 +661,72 @@ describe('workflow > action-trigger', () => {
expect(e3s.length).toBe(1);
expect(e3s[0].status).toBe(EXECUTION_STATUS.RESOLVED);
});
it('execution failed on node error', async () => {
const workflow = await WorkflowModel.create({
enabled: true,
type: 'action',
sync: true,
config: {
collection: 'posts',
},
});
const n1 = await workflow.createNode({
type: 'error',
});
const res1 = await userAgents[0].resource('posts').create({
values: { title: 't1' },
triggerWorkflows: `${workflow.key}`,
});
expect(res1.status).toBe(500);
});
it('execution failed on end node success', async () => {
const workflow = await WorkflowModel.create({
enabled: true,
type: 'action',
sync: true,
config: {
collection: 'posts',
},
});
const n1 = await workflow.createNode({
type: 'end',
});
const res1 = await userAgents[0].resource('posts').create({
values: { title: 't1' },
triggerWorkflows: `${workflow.key}`,
});
expect(res1.status).toBe(200);
});
it('execution failed on end node success', async () => {
const workflow = await WorkflowModel.create({
enabled: true,
type: 'action',
sync: true,
config: {
collection: 'posts',
},
});
const n1 = await workflow.createNode({
type: 'end',
config: {
endStatus: JOB_STATUS.FAILED,
},
});
const res1 = await userAgents[0].resource('posts').create({
values: { title: 't1' },
triggerWorkflows: `${workflow.key}`,
});
expect(res1.status).toBe(400);
});
});
describe('global workflow', () => {