@nocobase/flow-engine 2.1.0-alpha.4 → 2.1.0-alpha.45
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/LICENSE +201 -661
- package/README.md +79 -10
- package/lib/FlowContextProvider.d.ts +5 -1
- package/lib/FlowContextProvider.js +9 -2
- package/lib/JSRunner.d.ts +10 -1
- package/lib/JSRunner.js +50 -5
- package/lib/ViewScopedFlowEngine.js +5 -1
- package/lib/components/FieldModelRenderer.js +2 -2
- package/lib/components/FlowModelRenderer.d.ts +3 -1
- package/lib/components/FlowModelRenderer.js +12 -6
- package/lib/components/FormItem.d.ts +6 -0
- package/lib/components/FormItem.js +11 -3
- package/lib/components/MobilePopup.js +6 -5
- package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
- package/lib/components/dnd/gridDragPlanner.js +613 -21
- package/lib/components/dnd/index.d.ts +31 -2
- package/lib/components/dnd/index.js +244 -23
- package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
- package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -11
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +16 -2
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
- package/lib/components/subModel/AddSubModelButton.js +27 -1
- package/lib/components/subModel/LazyDropdown.js +293 -52
- package/lib/components/subModel/index.d.ts +1 -0
- package/lib/components/subModel/index.js +19 -0
- package/lib/components/subModel/utils.d.ts +1 -1
- package/lib/components/subModel/utils.js +9 -3
- package/lib/components/variables/VariableHybridInput.d.ts +27 -0
- package/lib/components/variables/VariableHybridInput.js +499 -0
- package/lib/components/variables/index.d.ts +2 -0
- package/lib/components/variables/index.js +3 -0
- package/lib/data-source/index.d.ts +84 -0
- package/lib/data-source/index.js +259 -5
- package/lib/executor/FlowExecutor.js +32 -9
- package/lib/flow-registry/DetachedFlowRegistry.d.ts +21 -0
- package/lib/flow-registry/DetachedFlowRegistry.js +80 -0
- package/lib/flow-registry/index.d.ts +1 -0
- package/lib/flow-registry/index.js +3 -1
- package/lib/flowContext.d.ts +3 -0
- package/lib/flowContext.js +46 -1
- package/lib/flowEngine.d.ts +151 -1
- package/lib/flowEngine.js +392 -18
- package/lib/flowI18n.js +2 -1
- package/lib/flowSettings.d.ts +14 -6
- package/lib/flowSettings.js +34 -6
- package/lib/index.d.ts +2 -0
- package/lib/index.js +7 -0
- package/lib/lazy-helper.d.ts +14 -0
- package/lib/lazy-helper.js +71 -0
- package/lib/locale/en-US.json +1 -0
- package/lib/locale/index.d.ts +2 -0
- package/lib/locale/zh-CN.json +1 -0
- package/lib/models/DisplayItemModel.d.ts +1 -1
- package/lib/models/EditableItemModel.d.ts +1 -1
- package/lib/models/FilterableItemModel.d.ts +1 -1
- package/lib/models/flowModel.d.ts +13 -10
- package/lib/models/flowModel.js +81 -21
- package/lib/provider.js +38 -23
- package/lib/reactive/observer.js +46 -16
- package/lib/runjs-context/registry.d.ts +1 -1
- package/lib/runjs-context/setup.js +20 -12
- package/lib/runjs-context/snippets/index.js +13 -2
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +5 -1
- package/lib/scheduler/ModelOperationScheduler.js +3 -2
- package/lib/types.d.ts +50 -2
- package/lib/types.js +1 -0
- package/lib/utils/createCollectionContextMeta.js +6 -2
- package/lib/utils/index.d.ts +3 -2
- package/lib/utils/index.js +7 -0
- package/lib/utils/parsePathnameToViewParams.d.ts +5 -1
- package/lib/utils/parsePathnameToViewParams.js +29 -5
- package/lib/utils/randomId.d.ts +39 -0
- package/lib/utils/randomId.js +45 -0
- package/lib/utils/runjsTemplateCompat.js +1 -1
- package/lib/utils/runjsValue.js +41 -11
- package/lib/utils/schema-utils.d.ts +7 -1
- package/lib/utils/schema-utils.js +19 -0
- package/lib/views/FlowView.d.ts +7 -1
- package/lib/views/FlowView.js +11 -1
- package/lib/views/PageComponent.js +8 -6
- package/lib/views/ViewNavigation.d.ts +12 -2
- package/lib/views/ViewNavigation.js +28 -9
- package/lib/views/createViewMeta.js +114 -50
- package/lib/views/inheritLayoutContext.d.ts +10 -0
- package/lib/views/inheritLayoutContext.js +50 -0
- package/lib/views/runViewBeforeClose.d.ts +10 -0
- package/lib/views/runViewBeforeClose.js +45 -0
- package/lib/views/useDialog.d.ts +2 -1
- package/lib/views/useDialog.js +22 -3
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +22 -3
- package/lib/views/usePage.d.ts +5 -11
- package/lib/views/usePage.js +304 -144
- package/package.json +6 -5
- package/src/FlowContextProvider.tsx +9 -1
- package/src/JSRunner.ts +68 -4
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +27 -1
- package/src/__tests__/createViewMeta.popup.test.ts +115 -1
- package/src/__tests__/flow-engine.test.ts +166 -0
- package/src/__tests__/flowContext.test.ts +82 -1
- package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
- package/src/__tests__/flowEngine.removeModel.test.ts +47 -3
- package/src/__tests__/flowSettings.test.ts +94 -15
- package/src/__tests__/objectVariable.test.ts +24 -0
- package/src/__tests__/provider.test.tsx +24 -2
- package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
- package/src/__tests__/runjsContext.test.ts +16 -0
- package/src/__tests__/runjsContextRuntime.test.ts +2 -0
- package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
- package/src/__tests__/runjsSnippets.test.ts +21 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
- package/src/components/FieldModelRenderer.tsx +2 -1
- package/src/components/FlowModelRenderer.tsx +18 -6
- package/src/components/FormItem.tsx +7 -1
- package/src/components/MobilePopup.tsx +4 -2
- package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
- package/src/components/__tests__/FormItem.test.tsx +25 -0
- package/src/components/__tests__/dnd.test.ts +44 -0
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
- package/src/components/__tests__/gridDragPlanner.test.ts +558 -3
- package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
- package/src/components/dnd/gridDragPlanner.ts +758 -19
- package/src/components/dnd/index.tsx +305 -28
- package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +99 -11
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +18 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +194 -5
- package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
- package/src/components/subModel/AddSubModelButton.tsx +32 -2
- package/src/components/subModel/LazyDropdown.tsx +332 -56
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +522 -37
- package/src/components/subModel/__tests__/utils.test.ts +24 -0
- package/src/components/subModel/index.ts +1 -0
- package/src/components/subModel/utils.ts +7 -1
- package/src/components/variables/VariableHybridInput.tsx +531 -0
- package/src/components/variables/index.ts +2 -0
- package/src/data-source/__tests__/collection.test.ts +41 -2
- package/src/data-source/__tests__/index.test.ts +68 -1
- package/src/data-source/index.ts +322 -6
- package/src/executor/FlowExecutor.ts +35 -10
- package/src/executor/__tests__/flowExecutor.test.ts +85 -0
- package/src/flow-registry/DetachedFlowRegistry.ts +46 -0
- package/src/flow-registry/__tests__/detachedFlowRegistry.test.ts +47 -0
- package/src/flow-registry/index.ts +1 -0
- package/src/flowContext.ts +50 -3
- package/src/flowEngine.ts +449 -14
- package/src/flowI18n.ts +2 -1
- package/src/flowSettings.ts +40 -6
- package/src/index.ts +2 -0
- package/src/lazy-helper.tsx +57 -0
- package/src/locale/en-US.json +1 -0
- package/src/locale/zh-CN.json +1 -0
- package/src/models/DisplayItemModel.tsx +1 -1
- package/src/models/EditableItemModel.tsx +1 -1
- package/src/models/FilterableItemModel.tsx +1 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +214 -0
- package/src/models/__tests__/flowEngine.resolveUse.test.ts +0 -15
- package/src/models/__tests__/flowModel.test.ts +80 -37
- package/src/models/flowModel.tsx +122 -36
- package/src/provider.tsx +41 -25
- package/src/reactive/__tests__/observer.test.tsx +82 -0
- package/src/reactive/observer.tsx +87 -25
- package/src/runjs-context/registry.ts +1 -1
- package/src/runjs-context/setup.ts +22 -12
- package/src/runjs-context/snippets/index.ts +12 -1
- package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
- package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
- package/src/scheduler/ModelOperationScheduler.ts +14 -3
- package/src/types.ts +62 -0
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +28 -0
- package/src/utils/__tests__/runjsValue.test.ts +11 -0
- package/src/utils/__tests__/utils.test.ts +62 -0
- package/src/utils/createCollectionContextMeta.ts +6 -2
- package/src/utils/index.ts +5 -1
- package/src/utils/parsePathnameToViewParams.ts +47 -7
- package/src/utils/randomId.ts +48 -0
- package/src/utils/runjsTemplateCompat.ts +1 -1
- package/src/utils/runjsValue.ts +50 -11
- package/src/utils/schema-utils.ts +30 -1
- package/src/views/FlowView.tsx +22 -2
- package/src/views/PageComponent.tsx +7 -4
- package/src/views/ViewNavigation.ts +46 -9
- package/src/views/__tests__/FlowView.usePage.test.tsx +243 -3
- package/src/views/__tests__/ViewNavigation.test.ts +52 -0
- package/src/views/__tests__/inheritLayoutContext.test.ts +53 -0
- package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +13 -12
- package/src/views/createViewMeta.ts +106 -34
- package/src/views/inheritLayoutContext.ts +26 -0
- package/src/views/runViewBeforeClose.ts +19 -0
- package/src/views/useDialog.tsx +27 -3
- package/src/views/useDrawer.tsx +27 -3
- package/src/views/usePage.tsx +367 -179
|
@@ -81,6 +81,34 @@ describe('FlowExecutor', () => {
|
|
|
81
81
|
expect(result.step2).toBe('step2-ok');
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
+
it('runFlow warns and skips steps without use or handler', async () => {
|
|
85
|
+
const flows = {
|
|
86
|
+
referenceSettings: {
|
|
87
|
+
steps: {
|
|
88
|
+
target: {},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
} satisfies Record<string, Omit<FlowDefinitionOptions, 'key'>>;
|
|
92
|
+
const model = createModelWithFlows('m-empty-step', flows);
|
|
93
|
+
const loggerChildSpy = vi.spyOn(engine.logger, 'child').mockReturnValue(engine.logger);
|
|
94
|
+
const loggerWarnSpy = vi.spyOn(engine.logger, 'warn').mockImplementation(() => {});
|
|
95
|
+
const loggerErrorSpy = vi.spyOn(engine.logger, 'error').mockImplementation(() => {});
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const result = await engine.executor.runFlow(model, 'referenceSettings');
|
|
99
|
+
|
|
100
|
+
expect(result).toEqual({});
|
|
101
|
+
expect(loggerWarnSpy).toHaveBeenCalledWith(
|
|
102
|
+
"BaseModel.applyFlow: Step 'target' in flow 'referenceSettings' has neither 'use' nor 'handler'. Skipping.",
|
|
103
|
+
);
|
|
104
|
+
expect(loggerErrorSpy).not.toHaveBeenCalled();
|
|
105
|
+
} finally {
|
|
106
|
+
loggerChildSpy.mockRestore();
|
|
107
|
+
loggerWarnSpy.mockRestore();
|
|
108
|
+
loggerErrorSpy.mockRestore();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
84
112
|
it("dispatchEvent('beforeRender') executes flows in sort order and caches result (when options specify)", async () => {
|
|
85
113
|
const calls: string[] = [];
|
|
86
114
|
const mkFlow = (key: string, sort: number) => ({
|
|
@@ -232,6 +260,37 @@ describe('FlowExecutor', () => {
|
|
|
232
260
|
expect(calls.sort()).toEqual(['a', 'b']);
|
|
233
261
|
});
|
|
234
262
|
|
|
263
|
+
it('dispatchEvent sequential exposes abortedByExitAll metadata on result array', async () => {
|
|
264
|
+
const flows = {
|
|
265
|
+
stopClose: {
|
|
266
|
+
on: { eventName: 'close' },
|
|
267
|
+
steps: {
|
|
268
|
+
only: {
|
|
269
|
+
handler: vi.fn().mockImplementation((ctx) => {
|
|
270
|
+
ctx.exit();
|
|
271
|
+
}),
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
afterClose: {
|
|
276
|
+
on: { eventName: 'close', phase: 'afterAllFlows' },
|
|
277
|
+
steps: {
|
|
278
|
+
only: {
|
|
279
|
+
handler: vi.fn(),
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
} satisfies Record<string, Omit<FlowDefinitionOptions, 'key'>>;
|
|
284
|
+
|
|
285
|
+
const model = createModelWithFlows('m-close-meta', flows);
|
|
286
|
+
|
|
287
|
+
const result = await engine.executor.dispatchEvent(model, 'close', {}, { sequential: true });
|
|
288
|
+
|
|
289
|
+
expect(Array.isArray(result)).toBe(true);
|
|
290
|
+
expect((result as any).__abortedByExitAll).toBe(true);
|
|
291
|
+
expect(flows.afterClose.steps.only.handler).not.toHaveBeenCalled();
|
|
292
|
+
});
|
|
293
|
+
|
|
235
294
|
it('dispatchEvent sequential respects sort order and stops on errors', async () => {
|
|
236
295
|
const calls: string[] = [];
|
|
237
296
|
const mkFlow = (key: string, sort: number, opts?: { throw?: boolean }) => ({
|
|
@@ -288,6 +347,32 @@ describe('FlowExecutor', () => {
|
|
|
288
347
|
expect(handler).toHaveBeenCalledTimes(2); // 每个 flow 各 1 次,共 2 次
|
|
289
348
|
});
|
|
290
349
|
|
|
350
|
+
it("dispatchEvent('beforeRender') keeps aborted flag on end event when cache hits", async () => {
|
|
351
|
+
const handler = vi.fn().mockImplementation((ctx) => {
|
|
352
|
+
ctx.exitAll();
|
|
353
|
+
});
|
|
354
|
+
const flows = {
|
|
355
|
+
abortFlow: { steps: { s: { handler } } },
|
|
356
|
+
} satisfies Record<string, Omit<FlowDefinitionOptions, 'key'>>;
|
|
357
|
+
const model = createModelWithFlows('m-br-cache-aborted', flows);
|
|
358
|
+
|
|
359
|
+
const endEvents: any[] = [];
|
|
360
|
+
const onEnd = (payload: any) => {
|
|
361
|
+
endEvents.push(payload);
|
|
362
|
+
};
|
|
363
|
+
engine.emitter.on('model:event:beforeRender:end', onEnd);
|
|
364
|
+
|
|
365
|
+
await engine.executor.dispatchEvent(model, 'beforeRender', undefined, { sequential: true, useCache: true });
|
|
366
|
+
await engine.executor.dispatchEvent(model, 'beforeRender', undefined, { sequential: true, useCache: true });
|
|
367
|
+
|
|
368
|
+
engine.emitter.off('model:event:beforeRender:end', onEnd);
|
|
369
|
+
|
|
370
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
371
|
+
expect(endEvents).toHaveLength(2);
|
|
372
|
+
expect(endEvents[0]?.aborted).toBe(true);
|
|
373
|
+
expect(endEvents[1]?.aborted).toBe(true);
|
|
374
|
+
});
|
|
375
|
+
|
|
291
376
|
it('dispatchEvent supports sequential execution order and exitAll break', async () => {
|
|
292
377
|
const calls: string[] = [];
|
|
293
378
|
const mkFlow = (key: string, sort: number, opts?: { exitAll?: boolean }) => ({
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import _ from 'lodash';
|
|
11
|
+
import { FlowDefinition } from '../FlowDefinition';
|
|
12
|
+
import { FlowDefinitionOptions } from '../types';
|
|
13
|
+
import { BaseFlowRegistry, IFlowRepository } from './BaseFlowRegistry';
|
|
14
|
+
|
|
15
|
+
export type FlowRegistryData = Record<string, Omit<FlowDefinitionOptions, 'key'> & { key?: string }>;
|
|
16
|
+
|
|
17
|
+
export class DetachedFlowRegistry extends BaseFlowRegistry {
|
|
18
|
+
constructor(flows: FlowRegistryData = {}) {
|
|
19
|
+
super();
|
|
20
|
+
this.addFlows(_.cloneDeep(flows));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
saveFlow(_flow: FlowDefinition): void {}
|
|
24
|
+
|
|
25
|
+
destroyFlow(flowKey: string): void {
|
|
26
|
+
this.removeFlow(flowKey);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function serializeFlowRegistry(registry: Pick<IFlowRepository, 'getFlows'>): FlowRegistryData {
|
|
31
|
+
const flows: FlowRegistryData = {};
|
|
32
|
+
for (const [key, flow] of registry.getFlows()) {
|
|
33
|
+
flows[key] = _.cloneDeep(flow.toData());
|
|
34
|
+
}
|
|
35
|
+
return flows;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function replaceFlowRegistry(
|
|
39
|
+
registry: Pick<IFlowRepository, 'getFlows' | 'removeFlow' | 'addFlows'>,
|
|
40
|
+
flows: FlowRegistryData,
|
|
41
|
+
) {
|
|
42
|
+
for (const key of Array.from(registry.getFlows().keys())) {
|
|
43
|
+
registry.removeFlow(key);
|
|
44
|
+
}
|
|
45
|
+
registry.addFlows(_.cloneDeep(flows));
|
|
46
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, expect, test } from 'vitest';
|
|
11
|
+
import { DetachedFlowRegistry, replaceFlowRegistry, serializeFlowRegistry } from '../DetachedFlowRegistry';
|
|
12
|
+
|
|
13
|
+
describe('DetachedFlowRegistry', () => {
|
|
14
|
+
test('keeps flow edits detached and can replace another registry', () => {
|
|
15
|
+
const source = {
|
|
16
|
+
flow1: {
|
|
17
|
+
title: 'Flow 1',
|
|
18
|
+
steps: {
|
|
19
|
+
step1: { title: 'Step 1' } as any,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
const registry = new DetachedFlowRegistry(source);
|
|
24
|
+
|
|
25
|
+
source.flow1.title = 'Changed outside';
|
|
26
|
+
expect(registry.getFlow('flow1')?.title).toBe('Flow 1');
|
|
27
|
+
|
|
28
|
+
const flow = registry.getFlow('flow1');
|
|
29
|
+
expect(flow).toBeDefined();
|
|
30
|
+
if (!flow) {
|
|
31
|
+
throw new Error('flow1 should exist');
|
|
32
|
+
}
|
|
33
|
+
flow.title = 'Draft title';
|
|
34
|
+
const serialized = serializeFlowRegistry(registry);
|
|
35
|
+
serialized.flow1.title = 'Changed serialized';
|
|
36
|
+
expect(registry.getFlow('flow1')?.title).toBe('Draft title');
|
|
37
|
+
|
|
38
|
+
const target = new DetachedFlowRegistry({ stale: { title: 'Stale', steps: {} } });
|
|
39
|
+
replaceFlowRegistry(target, serializeFlowRegistry(registry));
|
|
40
|
+
|
|
41
|
+
expect(target.hasFlow('stale')).toBe(false);
|
|
42
|
+
expect(target.getFlow('flow1')?.title).toBe('Draft title');
|
|
43
|
+
|
|
44
|
+
target.destroyFlow('flow1');
|
|
45
|
+
expect(target.hasFlow('flow1')).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
});
|
package/src/flowContext.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { ISchema } from '@formily/json-schema';
|
|
|
11
11
|
import { observable } from '@formily/reactive';
|
|
12
12
|
import { APIClient, RequestOptions } from '@nocobase/sdk';
|
|
13
13
|
import type { Router } from '@remix-run/router';
|
|
14
|
+
import axios from 'axios';
|
|
14
15
|
import { MessageInstance } from 'antd/es/message/interface';
|
|
15
16
|
import * as antd from 'antd';
|
|
16
17
|
import type { HookAPI } from 'antd/es/modal/useModal';
|
|
@@ -27,7 +28,7 @@ import { ContextPathProxy } from './ContextPathProxy';
|
|
|
27
28
|
import { DataSource, DataSourceManager } from './data-source';
|
|
28
29
|
import { FlowEngine } from './flowEngine';
|
|
29
30
|
import { FlowI18n } from './flowI18n';
|
|
30
|
-
import { JSRunner, JSRunnerOptions } from './JSRunner';
|
|
31
|
+
import { JSRunner, JSRunnerOptions, shouldPreprocessRunJSTemplates } from './JSRunner';
|
|
31
32
|
import type { FlowModel } from './models/flowModel';
|
|
32
33
|
import type { ForkFlowModel } from './models/forkFlowModel';
|
|
33
34
|
import { FlowResource, FlowSQLRepository } from './resources';
|
|
@@ -58,6 +59,31 @@ import dayjs from 'dayjs';
|
|
|
58
59
|
import { externalReactRender, setupRunJSLibs } from './runjsLibs';
|
|
59
60
|
import { runjsImportAsync, runjsImportModule, runjsRequireAsync } from './utils/runjsModuleLoader';
|
|
60
61
|
|
|
62
|
+
function normalizePathname(pathname: string) {
|
|
63
|
+
return pathname.endsWith('/') ? pathname : `${pathname}/`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function shouldBypassApiClient(url: string, app?: { getApiUrl?: (pathname?: string) => string }) {
|
|
67
|
+
try {
|
|
68
|
+
const requestUrl = new URL(url);
|
|
69
|
+
if (!['http:', 'https:'].includes(requestUrl.protocol)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!app?.getApiUrl) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const apiUrl = new URL(app.getApiUrl());
|
|
78
|
+
const apiPath = normalizePathname(apiUrl.pathname);
|
|
79
|
+
const requestPath = normalizePathname(requestUrl.pathname);
|
|
80
|
+
|
|
81
|
+
return requestUrl.origin !== apiUrl.origin || !requestPath.startsWith(apiPath);
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
61
87
|
// Helper: detect a RecordRef-like object
|
|
62
88
|
function isRecordRefLike(val: any): boolean {
|
|
63
89
|
return !!(val && typeof val === 'object' && 'collection' in val && 'filterByTk' in val);
|
|
@@ -2980,8 +3006,10 @@ export class FlowContext {
|
|
|
2980
3006
|
}
|
|
2981
3007
|
|
|
2982
3008
|
class BaseFlowEngineContext extends FlowContext {
|
|
3009
|
+
declare t: (key: any, options?: any) => string;
|
|
2983
3010
|
declare router: Router;
|
|
2984
3011
|
declare dataSourceManager: DataSourceManager;
|
|
3012
|
+
declare isDarkTheme: boolean;
|
|
2985
3013
|
declare requireAsync: (url: string) => Promise<any>;
|
|
2986
3014
|
declare importAsync: (url: string) => Promise<any>;
|
|
2987
3015
|
declare createJSRunner: (options?: JSRunnerOptions) => Promise<JSRunner>;
|
|
@@ -3008,6 +3036,7 @@ class BaseFlowEngineContext extends FlowContext {
|
|
|
3008
3036
|
declare runAction: (actionName: string, params?: Record<string, any>) => Promise<any> | any;
|
|
3009
3037
|
declare engine: FlowEngine;
|
|
3010
3038
|
declare api: APIClient;
|
|
3039
|
+
declare locale: string;
|
|
3011
3040
|
declare viewer: FlowViewer;
|
|
3012
3041
|
declare view: FlowView;
|
|
3013
3042
|
declare modal: HookAPI;
|
|
@@ -3024,6 +3053,10 @@ class BaseFlowEngineContext extends FlowContext {
|
|
|
3024
3053
|
return this.engine.getModel(modelName, searchInPreviousEngines);
|
|
3025
3054
|
});
|
|
3026
3055
|
this.defineMethod('request', (options: RequestOptions) => {
|
|
3056
|
+
const app = this.app as { getApiUrl?: (pathname?: string) => string } | undefined;
|
|
3057
|
+
if (typeof options?.url === 'string' && shouldBypassApiClient(options.url, app)) {
|
|
3058
|
+
return axios.request(options);
|
|
3059
|
+
}
|
|
3027
3060
|
return this.api.request(options);
|
|
3028
3061
|
});
|
|
3029
3062
|
this.defineMethod(
|
|
@@ -3035,8 +3068,10 @@ class BaseFlowEngineContext extends FlowContext {
|
|
|
3035
3068
|
...(runnerOptions || {}),
|
|
3036
3069
|
globals: mergedGlobals,
|
|
3037
3070
|
});
|
|
3038
|
-
|
|
3039
|
-
|
|
3071
|
+
const shouldPreprocessTemplates = shouldPreprocessRunJSTemplates({
|
|
3072
|
+
version: runnerOptions?.version,
|
|
3073
|
+
preprocessTemplates,
|
|
3074
|
+
});
|
|
3040
3075
|
const jsCode = await prepareRunJsCode(String(code ?? ''), { preprocessTemplates: shouldPreprocessTemplates });
|
|
3041
3076
|
return runner.run(jsCode);
|
|
3042
3077
|
},
|
|
@@ -3112,6 +3147,15 @@ export class FlowEngineContext extends BaseFlowEngineContext {
|
|
|
3112
3147
|
this.defineMethod('t', (keyOrTemplate: string, options?: any) => {
|
|
3113
3148
|
return i18n.translate(keyOrTemplate, options);
|
|
3114
3149
|
});
|
|
3150
|
+
this.defineProperty('locale', {
|
|
3151
|
+
get: () => this.api?.auth?.locale || this.i18n?.language,
|
|
3152
|
+
cache: false,
|
|
3153
|
+
meta: Object.assign(() => ({ type: 'string', title: this.t('Current language'), sort: 970 }), {
|
|
3154
|
+
title: escapeT('Current language'),
|
|
3155
|
+
sort: 970,
|
|
3156
|
+
hasChildren: false,
|
|
3157
|
+
}),
|
|
3158
|
+
});
|
|
3115
3159
|
this.defineMethod('renderJson', function (template: any) {
|
|
3116
3160
|
return this.resolveJsonTemplate(template);
|
|
3117
3161
|
});
|
|
@@ -3537,6 +3581,9 @@ export class FlowEngineContext extends BaseFlowEngineContext {
|
|
|
3537
3581
|
},
|
|
3538
3582
|
});
|
|
3539
3583
|
this.defineMethod('aclCheck', function (params) {
|
|
3584
|
+
if (this.skipAclCheck) {
|
|
3585
|
+
return true;
|
|
3586
|
+
}
|
|
3540
3587
|
return this.acl.aclCheck(params);
|
|
3541
3588
|
});
|
|
3542
3589
|
this.defineMethod('createResource', function (this: BaseFlowEngineContext, resourceType) {
|