mirror of
https://github.com/nocobase/nocobase.git
synced 2026-10-29 18:16:18 +08:00
Merge branch 'next' into develop
This commit is contained in:
@@ -421,6 +421,9 @@ export class FlowExecutor {
|
||||
model.uid,
|
||||
async (m) => {
|
||||
const res = await this.runFlow(m, it.flow.key, inputArgs, runId, eventName);
|
||||
if (res instanceof FlowExitAllException) {
|
||||
throw res;
|
||||
}
|
||||
results.push(res);
|
||||
},
|
||||
{ when: whenKey as any },
|
||||
@@ -442,6 +445,10 @@ export class FlowExecutor {
|
||||
}
|
||||
results.push(result);
|
||||
} catch (error) {
|
||||
if (error instanceof FlowExitAllException) {
|
||||
logger.debug(`[FlowEngine.dispatchEvent] ${error.message}`);
|
||||
break; // 终止后续
|
||||
}
|
||||
logger.error(
|
||||
{ err: error },
|
||||
`BaseModel.dispatchEvent: Error executing event-triggered flow '${flow.key}' for event '${eventName}' (sequential):`,
|
||||
|
||||
@@ -39,6 +39,45 @@ describe('dispatchEvent dynamic event flow phase (scheduleModelOperation integra
|
||||
expect(calls).toEqual(['dynamic', 'static-a']);
|
||||
});
|
||||
|
||||
test('default (phase undefined): ctx.exitAll() stops static flows (beforeAllFlows regression)', async () => {
|
||||
const engine = new FlowEngine();
|
||||
class M extends FlowModel {}
|
||||
engine.registerModels({ M });
|
||||
|
||||
const calls: string[] = [];
|
||||
|
||||
M.registerFlow({
|
||||
key: 'S',
|
||||
on: { eventName: 'go' },
|
||||
steps: {
|
||||
a: { handler: async () => void calls.push('static-a') } as any,
|
||||
},
|
||||
});
|
||||
M.registerFlow({
|
||||
key: 'T',
|
||||
on: { eventName: 'go' },
|
||||
steps: {
|
||||
t: { handler: async () => void calls.push('static-t') } as any,
|
||||
},
|
||||
});
|
||||
|
||||
const model = engine.createModel({ use: 'M' });
|
||||
model.registerFlow('D', {
|
||||
on: { eventName: 'go' },
|
||||
steps: {
|
||||
d: {
|
||||
handler: async (ctx: any) => {
|
||||
calls.push('dynamic');
|
||||
ctx.exitAll();
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
});
|
||||
|
||||
await model.dispatchEvent('go');
|
||||
expect(calls).toEqual(['dynamic']);
|
||||
});
|
||||
|
||||
test("phase='afterAllFlows': instance flow runs after static flows", async () => {
|
||||
const engine = new FlowEngine();
|
||||
class M extends FlowModel {}
|
||||
@@ -178,6 +217,165 @@ describe('dispatchEvent dynamic event flow phase (scheduleModelOperation integra
|
||||
expect(calls).toEqual(['static-a', 'dynamic', 'static-b']);
|
||||
});
|
||||
|
||||
test("phase='beforeFlow': ctx.exitAll() stops anchor flow and subsequent flows", async () => {
|
||||
const engine = new FlowEngine();
|
||||
class M extends FlowModel {}
|
||||
engine.registerModels({ M });
|
||||
|
||||
const calls: string[] = [];
|
||||
|
||||
M.registerFlow({
|
||||
key: 'S',
|
||||
on: { eventName: 'go' },
|
||||
steps: {
|
||||
a: { handler: async () => void calls.push('static-a') } as any,
|
||||
},
|
||||
});
|
||||
M.registerFlow({
|
||||
key: 'T',
|
||||
on: { eventName: 'go' },
|
||||
steps: {
|
||||
t: { handler: async () => void calls.push('static-t') } as any,
|
||||
},
|
||||
});
|
||||
|
||||
const model = engine.createModel({ use: 'M' });
|
||||
model.registerFlow('D', {
|
||||
on: { eventName: 'go', phase: 'beforeFlow', flowKey: 'S' },
|
||||
steps: {
|
||||
d: {
|
||||
handler: async (ctx: any) => {
|
||||
calls.push('dynamic');
|
||||
ctx.exitAll();
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
});
|
||||
|
||||
await model.dispatchEvent('go');
|
||||
expect(calls).toEqual(['dynamic']);
|
||||
});
|
||||
|
||||
test("phase='beforeStep': ctx.exitAll() stops anchor step and subsequent flows", async () => {
|
||||
const engine = new FlowEngine();
|
||||
class M extends FlowModel {}
|
||||
engine.registerModels({ M });
|
||||
|
||||
const calls: string[] = [];
|
||||
|
||||
M.registerFlow({
|
||||
key: 'S',
|
||||
on: { eventName: 'go' },
|
||||
steps: {
|
||||
a: { handler: async () => void calls.push('static-a') } as any,
|
||||
b: { handler: async () => void calls.push('static-b') } as any,
|
||||
},
|
||||
});
|
||||
M.registerFlow({
|
||||
key: 'T',
|
||||
on: { eventName: 'go' },
|
||||
steps: {
|
||||
t: { handler: async () => void calls.push('static-t') } as any,
|
||||
},
|
||||
});
|
||||
|
||||
const model = engine.createModel({ use: 'M' });
|
||||
model.registerFlow('D', {
|
||||
on: { eventName: 'go', phase: 'beforeStep', flowKey: 'S', stepKey: 'a' },
|
||||
steps: {
|
||||
d: {
|
||||
handler: async (ctx: any) => {
|
||||
calls.push('dynamic');
|
||||
ctx.exitAll();
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
});
|
||||
|
||||
await model.dispatchEvent('go');
|
||||
expect(calls).toEqual(['dynamic']);
|
||||
});
|
||||
|
||||
test("phase='afterStep': ctx.exitAll() stops subsequent steps and subsequent flows", async () => {
|
||||
const engine = new FlowEngine();
|
||||
class M extends FlowModel {}
|
||||
engine.registerModels({ M });
|
||||
|
||||
const calls: string[] = [];
|
||||
|
||||
M.registerFlow({
|
||||
key: 'S',
|
||||
on: { eventName: 'go' },
|
||||
steps: {
|
||||
a: { handler: async () => void calls.push('static-a') } as any,
|
||||
b: { handler: async () => void calls.push('static-b') } as any,
|
||||
},
|
||||
});
|
||||
M.registerFlow({
|
||||
key: 'T',
|
||||
on: { eventName: 'go' },
|
||||
steps: {
|
||||
t: { handler: async () => void calls.push('static-t') } as any,
|
||||
},
|
||||
});
|
||||
|
||||
const model = engine.createModel({ use: 'M' });
|
||||
model.registerFlow('D', {
|
||||
on: { eventName: 'go', phase: 'afterStep', flowKey: 'S', stepKey: 'a' },
|
||||
steps: {
|
||||
d: {
|
||||
handler: async (ctx: any) => {
|
||||
calls.push('dynamic');
|
||||
ctx.exitAll();
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
});
|
||||
|
||||
await model.dispatchEvent('go');
|
||||
expect(calls).toEqual(['static-a', 'dynamic']);
|
||||
});
|
||||
|
||||
test("phase='afterFlow': ctx.exitAll() stops subsequent flows", async () => {
|
||||
const engine = new FlowEngine();
|
||||
class M extends FlowModel {}
|
||||
engine.registerModels({ M });
|
||||
|
||||
const calls: string[] = [];
|
||||
|
||||
M.registerFlow({
|
||||
key: 'S',
|
||||
on: { eventName: 'go' },
|
||||
steps: {
|
||||
a: { handler: async () => void calls.push('static-a') } as any,
|
||||
b: { handler: async () => void calls.push('static-b') } as any,
|
||||
},
|
||||
});
|
||||
M.registerFlow({
|
||||
key: 'T',
|
||||
on: { eventName: 'go' },
|
||||
steps: {
|
||||
t: { handler: async () => void calls.push('static-t') } as any,
|
||||
},
|
||||
});
|
||||
|
||||
const model = engine.createModel({ use: 'M' });
|
||||
model.registerFlow('D', {
|
||||
on: { eventName: 'go', phase: 'afterFlow', flowKey: 'S' },
|
||||
steps: {
|
||||
d: {
|
||||
handler: async (ctx: any) => {
|
||||
calls.push('dynamic');
|
||||
ctx.exitAll();
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
});
|
||||
|
||||
await model.dispatchEvent('go');
|
||||
expect(calls).toEqual(['static-a', 'static-b', 'dynamic']);
|
||||
});
|
||||
|
||||
test("phase='beforeFlow' missing flow: falls back to afterAllFlows", async () => {
|
||||
const engine = new FlowEngine();
|
||||
class M extends FlowModel {}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { uid as genUid } from 'uid/secure';
|
||||
import type { FlowEngine } from '../flowEngine';
|
||||
import type { FlowModel } from '../models/flowModel';
|
||||
import { FlowExitAllException } from '../utils/exceptions';
|
||||
|
||||
type LifecycleType =
|
||||
| 'created'
|
||||
@@ -273,6 +274,9 @@ export class ModelOperationScheduler {
|
||||
if (!model) return;
|
||||
await Promise.resolve(item.fn(model));
|
||||
} catch (err) {
|
||||
if (err instanceof FlowExitAllException) {
|
||||
throw err;
|
||||
}
|
||||
this.engine.logger?.error?.(
|
||||
{ err, id, fromUid: item.fromUid, toUid: item.toUid, when: item.options.when },
|
||||
'ModelOperationScheduler: operation execution failed',
|
||||
|
||||
Reference in New Issue
Block a user