@nocobase/client-v2 2.2.0-beta.6 → 2.2.0-beta.8
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/es/PluginSettingsManager.d.ts +33 -0
- package/es/flow/actions/afterSuccess.d.ts +8 -1
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +1 -0
- package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +9 -0
- package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
- package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
- package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
- package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
- package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
- package/es/index.mjs +105 -89
- package/lib/index.js +106 -90
- package/package.json +7 -7
- package/src/PluginSettingsManager.ts +53 -0
- package/src/__tests__/PluginSettingsManager.test.ts +13 -0
- package/src/__tests__/plugin-manager.test.tsx +44 -2
- package/src/__tests__/settings-center.test.tsx +7 -1
- package/src/collection-manager/field-configure.ts +1 -1
- package/src/collection-manager/interfaces/id.ts +1 -1
- package/src/collection-manager/interfaces/m2m.tsx +2 -2
- package/src/collection-manager/interfaces/m2o.tsx +2 -2
- package/src/collection-manager/interfaces/nanoid.ts +1 -1
- package/src/collection-manager/interfaces/o2m.tsx +2 -2
- package/src/collection-manager/interfaces/obo.tsx +2 -2
- package/src/collection-manager/interfaces/oho.tsx +2 -2
- package/src/collection-manager/interfaces/properties/index.ts +2 -2
- package/src/collection-manager/interfaces/uuid.ts +1 -1
- package/src/flow/__tests__/getKey.test.ts +7 -0
- package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
- package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +58 -0
- package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +104 -0
- package/src/flow/actions/afterSuccess.tsx +142 -3
- package/src/flow/actions/dataScopeFilter.ts +10 -0
- package/src/flow/actions/index.ts +1 -1
- package/src/flow/actions/openView.tsx +38 -4
- package/src/flow/admin-shell/BaseLayoutModel.tsx +25 -3
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +5 -2
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +13 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +1 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +5 -4
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +82 -0
- package/src/flow/components/FieldAssignRulesEditor.tsx +66 -9
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +427 -0
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
- package/src/flow/components/fieldAssignOptions.ts +155 -27
- package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
- package/src/flow/models/base/ActionModelCore.tsx +6 -4
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
- package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
- package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
- package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
- package/src/flow/models/blocks/form/submitHandler.ts +17 -3
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +87 -0
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +91 -0
- package/src/flow/models/blocks/js-block/JSBlock.tsx +223 -2
- package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +35 -12
- package/src/flow/models/blocks/table/TableBlockModel.tsx +25 -14
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +57 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
- package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
- package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
- package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
- package/src/flow/resolveViewParamsToViewList.tsx +11 -3
- package/src/nocobase-buildin-plugin/index.tsx +0 -2
- package/src/settings-center/SystemSettingsPage.tsx +1 -2
- package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
- package/src/settings-center/utils.tsx +0 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/client-v2",
|
|
3
|
-
"version": "2.2.0-beta.
|
|
3
|
+
"version": "2.2.0-beta.8",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.mjs",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"@formily/antd-v5": "1.2.3",
|
|
28
28
|
"@formily/react": "^2.2.27",
|
|
29
29
|
"@formily/shared": "^2.2.27",
|
|
30
|
-
"@nocobase/evaluators": "2.2.0-beta.
|
|
31
|
-
"@nocobase/flow-engine": "2.2.0-beta.
|
|
32
|
-
"@nocobase/sdk": "2.2.0-beta.
|
|
33
|
-
"@nocobase/shared": "2.2.0-beta.
|
|
34
|
-
"@nocobase/utils": "2.2.0-beta.
|
|
30
|
+
"@nocobase/evaluators": "2.2.0-beta.8",
|
|
31
|
+
"@nocobase/flow-engine": "2.2.0-beta.8",
|
|
32
|
+
"@nocobase/sdk": "2.2.0-beta.8",
|
|
33
|
+
"@nocobase/shared": "2.2.0-beta.8",
|
|
34
|
+
"@nocobase/utils": "2.2.0-beta.8",
|
|
35
35
|
"ahooks": "^3.7.2",
|
|
36
36
|
"antd": "5.24.2",
|
|
37
37
|
"antd-style": "3.7.1",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"react-i18next": "^11.15.1",
|
|
47
47
|
"react-router-dom": "^6.30.1"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "fa2502c1e9faf6d74b3f51b42dbc6546638d46af"
|
|
50
50
|
}
|
|
@@ -97,6 +97,7 @@ interface InternalPageItemRecord extends PluginSettingsPageItemOptions {
|
|
|
97
97
|
export class PluginSettingsManager<TApp extends BaseApplication<any> = BaseApplication<any>> {
|
|
98
98
|
protected menus: Record<string, InternalMenuItemRecord> = {};
|
|
99
99
|
protected pages: Record<string, InternalPageItemRecord> = {};
|
|
100
|
+
protected pluginSettingsLinks: Record<string, string> = {};
|
|
100
101
|
protected aclSnippets: string[] = [];
|
|
101
102
|
public app: TApp;
|
|
102
103
|
|
|
@@ -294,6 +295,58 @@ export class PluginSettingsManager<TApp extends BaseApplication<any> = BaseAppli
|
|
|
294
295
|
return !!(this.menus[name] || this.pages[name]);
|
|
295
296
|
}
|
|
296
297
|
|
|
298
|
+
/**
|
|
299
|
+
* 绑定插件管理列表中的 plugin name 到实际 settings 入口。
|
|
300
|
+
*
|
|
301
|
+
* 插件管理列表使用 applicationPlugins.name 判断是否显示 Settings 入口,
|
|
302
|
+
* 但部分插件会把设置页挂到共享菜单或历史路径下。
|
|
303
|
+
*
|
|
304
|
+
* @param pluginName 插件管理列表中的插件名
|
|
305
|
+
* @param settingsName menu 或 page 名称
|
|
306
|
+
* @returns {void}
|
|
307
|
+
*/
|
|
308
|
+
setPluginSettingsLink(pluginName: string, settingsName: string) {
|
|
309
|
+
if (!pluginName) {
|
|
310
|
+
throw new Error('Plugin settings link pluginName is required');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (!settingsName) {
|
|
314
|
+
throw new Error('Plugin settings link settingsName is required');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
this.pluginSettingsLinks[pluginName] = settingsName;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* 获取插件管理列表中的 plugin name 对应的 settings 名称。
|
|
322
|
+
*
|
|
323
|
+
* @param pluginName 插件管理列表中的插件名
|
|
324
|
+
* @returns {string} settings 名称
|
|
325
|
+
*/
|
|
326
|
+
getPluginSettingsName(pluginName: string) {
|
|
327
|
+
return this.pluginSettingsLinks[pluginName] || pluginName;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* 判断插件管理列表中的插件是否有可跳转的 settings 入口。
|
|
332
|
+
*
|
|
333
|
+
* @param pluginName 插件管理列表中的插件名
|
|
334
|
+
* @returns {boolean} 是否已注册 settings 入口
|
|
335
|
+
*/
|
|
336
|
+
hasPluginSettings(pluginName: string) {
|
|
337
|
+
return this.has(this.getPluginSettingsName(pluginName));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* 获取插件管理列表中的插件对应的 settings 路径。
|
|
342
|
+
*
|
|
343
|
+
* @param pluginName 插件管理列表中的插件名
|
|
344
|
+
* @returns {string} settings 绝对路径
|
|
345
|
+
*/
|
|
346
|
+
getPluginSettingsRoutePath(pluginName: string) {
|
|
347
|
+
return this.getRoutePath(this.getPluginSettingsName(pluginName));
|
|
348
|
+
}
|
|
349
|
+
|
|
297
350
|
/**
|
|
298
351
|
* 获取单个 menu 或 page 的只读快照。
|
|
299
352
|
*
|
|
@@ -78,6 +78,19 @@ describe('PluginSettingsManager v2', () => {
|
|
|
78
78
|
expect(app.router.get('admin.settings.demo.advanced')).toMatchObject({ path: 'advanced' });
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
it('should resolve plugin settings links for plugin manager entries', () => {
|
|
82
|
+
const app = createMockClient();
|
|
83
|
+
|
|
84
|
+
app.pluginSettingsManager.addMenuItem({ key: 'shared', title: 'Shared' });
|
|
85
|
+
app.pluginSettingsManager.addPageTabItem({ menuKey: 'shared', key: 'target', title: 'Target' });
|
|
86
|
+
app.pluginSettingsManager.setPluginSettingsLink('demo-plugin', 'shared.target');
|
|
87
|
+
|
|
88
|
+
expect(app.pluginSettingsManager.has('demo-plugin')).toBe(false);
|
|
89
|
+
expect(app.pluginSettingsManager.hasPluginSettings('demo-plugin')).toBe(true);
|
|
90
|
+
expect(app.pluginSettingsManager.getPluginSettingsName('demo-plugin')).toBe('shared.target');
|
|
91
|
+
expect(app.pluginSettingsManager.getPluginSettingsRoutePath('demo-plugin')).toBe('/admin/settings/shared/target');
|
|
92
|
+
});
|
|
93
|
+
|
|
81
94
|
it('should render string icon on both menu and page tab via renderIcon', () => {
|
|
82
95
|
// Previously `renderPage` spread the raw `icon` string straight to the antd
|
|
83
96
|
// Menu item, which displayed "LockOutlinedTitle" as text. Both `renderMenuItem`
|
|
@@ -18,6 +18,19 @@ class TestAclPlugin extends Plugin {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
class TestSettingsLinkPlugin extends Plugin {
|
|
22
|
+
async load() {
|
|
23
|
+
this.app.pluginSettingsManager.addMenuItem({ key: 'shared-settings', title: 'Shared settings' });
|
|
24
|
+
this.app.pluginSettingsManager.addPageTabItem({
|
|
25
|
+
menuKey: 'shared-settings',
|
|
26
|
+
key: 'target',
|
|
27
|
+
title: 'Target settings',
|
|
28
|
+
Component: () => <div>Target settings page</div>,
|
|
29
|
+
});
|
|
30
|
+
this.app.pluginSettingsManager.setPluginSettingsLink('demo-plugin', 'shared-settings.target');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
21
34
|
type MockClientApplication = ReturnType<typeof createMockClient>;
|
|
22
35
|
|
|
23
36
|
const renderApp = (app: MockClientApplication) => {
|
|
@@ -35,9 +48,9 @@ const waitForGetRequests = async (app: MockClientApplication, urls: string[]) =>
|
|
|
35
48
|
);
|
|
36
49
|
};
|
|
37
50
|
|
|
38
|
-
const setupApp = (pmList: any[]) => {
|
|
51
|
+
const setupApp = (pmList: any[], plugins: Array<typeof Plugin> = []) => {
|
|
39
52
|
const app = createMockClient({
|
|
40
|
-
plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
|
|
53
|
+
plugins: [NocoBaseBuildInPlugin, TestAclPlugin, ...plugins],
|
|
41
54
|
router: { type: 'memory', initialEntries: ['/admin/settings/plugin-manager'] },
|
|
42
55
|
});
|
|
43
56
|
|
|
@@ -174,4 +187,33 @@ describe('plugin-manager page', () => {
|
|
|
174
187
|
expect(removeCall?.params).toMatchObject({ filterByTk: 'demo-plugin' });
|
|
175
188
|
});
|
|
176
189
|
});
|
|
190
|
+
|
|
191
|
+
it('shows Settings for a plugin linked to a different settings page', async () => {
|
|
192
|
+
const app = setupApp(
|
|
193
|
+
[
|
|
194
|
+
{
|
|
195
|
+
name: 'demo-plugin',
|
|
196
|
+
packageName: '@nocobase/demo-plugin',
|
|
197
|
+
displayName: 'Demo plugin',
|
|
198
|
+
description: 'A demo',
|
|
199
|
+
enabled: true,
|
|
200
|
+
builtIn: false,
|
|
201
|
+
removable: false,
|
|
202
|
+
version: '0.1.0',
|
|
203
|
+
isCompatible: true,
|
|
204
|
+
keywords: [],
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
[TestSettingsLinkPlugin],
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
renderApp(app);
|
|
211
|
+
await waitForGetRequests(app, ['/auth:check', 'roles:check', 'pm:list']);
|
|
212
|
+
|
|
213
|
+
const card = await screen.findByRole('button', { name: 'Demo plugin' });
|
|
214
|
+
const settingsLink = within(card.closest('.ant-card') as HTMLElement).getByText('Settings');
|
|
215
|
+
fireEvent.click(settingsLink);
|
|
216
|
+
|
|
217
|
+
expect(await screen.findByText('Target settings page')).toBeInTheDocument();
|
|
218
|
+
});
|
|
177
219
|
});
|
|
@@ -14,7 +14,7 @@ import { message } from 'antd';
|
|
|
14
14
|
import { AdminSettingsLayoutModel as ClientV2AdminSettingsLayoutModel } from '../settings-center';
|
|
15
15
|
import { AdminSettingsLayoutModel as ClientV1AdminSettingsLayoutModel } from '../../../client/src/pm/AdminSettingsLayoutModel';
|
|
16
16
|
import { NocoBaseBuildInPlugin } from '../nocobase-buildin-plugin';
|
|
17
|
-
import { matchSettingsRoute } from '../settings-center/utils';
|
|
17
|
+
import { matchSettingsRoute, sortTopLevelSettings } from '../settings-center/utils';
|
|
18
18
|
|
|
19
19
|
class TestAclPlugin extends Plugin {
|
|
20
20
|
async load() {
|
|
@@ -168,6 +168,12 @@ describe('settings center', () => {
|
|
|
168
168
|
});
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
+
it('should sort system-settings with other top-level settings by normal ordering', () => {
|
|
172
|
+
const settings = [{ name: 'system-settings' }, { name: 'api-keys' }, { name: 'backups' }] as any;
|
|
173
|
+
|
|
174
|
+
expect(sortTopLevelSettings(settings).map((item) => item.name)).toEqual(['api-keys', 'backups', 'system-settings']);
|
|
175
|
+
});
|
|
176
|
+
|
|
171
177
|
it('should redirect /admin/settings to system-settings by default', async () => {
|
|
172
178
|
const app = createMockClient({
|
|
173
179
|
plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
|
|
@@ -530,7 +530,7 @@ export function reverseFieldConfigureItems(): FieldConfigureItem[] {
|
|
|
530
530
|
component: 'Input',
|
|
531
531
|
required: true,
|
|
532
532
|
description:
|
|
533
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
533
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
534
534
|
hidden: ({ context, values }) => !context.showReverseFieldConfig && !get(values, 'autoCreateReverseField'),
|
|
535
535
|
disabled: ({ context }) => !context.showReverseFieldConfig,
|
|
536
536
|
},
|
|
@@ -48,7 +48,7 @@ export class IdFieldInterface extends CollectionFieldInterface {
|
|
|
48
48
|
'x-decorator': 'FormItem',
|
|
49
49
|
'x-component': 'Input',
|
|
50
50
|
description:
|
|
51
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
51
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
52
52
|
},
|
|
53
53
|
};
|
|
54
54
|
filterable = {
|
|
@@ -158,7 +158,7 @@ export class M2MFieldInterface extends CollectionFieldInterface {
|
|
|
158
158
|
required: true,
|
|
159
159
|
default: '{{ useNewId("f_") }}',
|
|
160
160
|
description:
|
|
161
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
161
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
162
162
|
'x-decorator': 'FormItem',
|
|
163
163
|
'x-component': 'ForeignKey',
|
|
164
164
|
'x-validator': 'uid',
|
|
@@ -192,7 +192,7 @@ export class M2MFieldInterface extends CollectionFieldInterface {
|
|
|
192
192
|
required: true,
|
|
193
193
|
default: '{{ useNewId("f_") }}',
|
|
194
194
|
description:
|
|
195
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
195
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
196
196
|
'x-decorator': 'FormItem',
|
|
197
197
|
'x-component': 'ForeignKey',
|
|
198
198
|
'x-validator': 'uid',
|
|
@@ -69,7 +69,7 @@ export class M2OFieldInterface extends CollectionFieldInterface {
|
|
|
69
69
|
'x-decorator': 'FormItem',
|
|
70
70
|
'x-component': 'Input',
|
|
71
71
|
description:
|
|
72
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
72
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
73
73
|
},
|
|
74
74
|
type: relationshipType,
|
|
75
75
|
grid: {
|
|
@@ -123,7 +123,7 @@ export class M2OFieldInterface extends CollectionFieldInterface {
|
|
|
123
123
|
required: true,
|
|
124
124
|
default: '{{ useNewId("f_") }}',
|
|
125
125
|
description:
|
|
126
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
126
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
127
127
|
'x-decorator': 'FormItem',
|
|
128
128
|
'x-component': 'ForeignKey',
|
|
129
129
|
'x-validator': 'uid',
|
|
@@ -42,7 +42,7 @@ export class NanoidFieldInterface extends CollectionFieldInterface {
|
|
|
42
42
|
'x-component': 'Input',
|
|
43
43
|
'x-disabled': '{{ !createOnly }}',
|
|
44
44
|
description:
|
|
45
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
45
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
46
46
|
},
|
|
47
47
|
customAlphabet: {
|
|
48
48
|
type: 'string',
|
|
@@ -68,7 +68,7 @@ export class O2MFieldInterface extends CollectionFieldInterface {
|
|
|
68
68
|
'x-decorator': 'FormItem',
|
|
69
69
|
'x-component': 'Input',
|
|
70
70
|
description:
|
|
71
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
71
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
72
72
|
},
|
|
73
73
|
type: relationshipType,
|
|
74
74
|
grid: {
|
|
@@ -135,7 +135,7 @@ export class O2MFieldInterface extends CollectionFieldInterface {
|
|
|
135
135
|
required: true,
|
|
136
136
|
default: '{{ useNewId("f_") }}',
|
|
137
137
|
description:
|
|
138
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
138
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
139
139
|
'x-decorator': 'FormItem',
|
|
140
140
|
'x-component': 'ForeignKey',
|
|
141
141
|
'x-validator': 'uid',
|
|
@@ -55,7 +55,7 @@ export class OBOFieldInterface extends CollectionFieldInterface {
|
|
|
55
55
|
'x-decorator': 'FormItem',
|
|
56
56
|
'x-component': 'Input',
|
|
57
57
|
description:
|
|
58
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
58
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
59
59
|
},
|
|
60
60
|
type: relationshipType,
|
|
61
61
|
grid: {
|
|
@@ -109,7 +109,7 @@ export class OBOFieldInterface extends CollectionFieldInterface {
|
|
|
109
109
|
required: true,
|
|
110
110
|
default: '{{ useNewId("f_") }}',
|
|
111
111
|
description:
|
|
112
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
112
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
113
113
|
'x-decorator': 'FormItem',
|
|
114
114
|
'x-component': 'ForeignKey',
|
|
115
115
|
'x-validator': 'uid',
|
|
@@ -55,7 +55,7 @@ export class OHOFieldInterface extends CollectionFieldInterface {
|
|
|
55
55
|
'x-decorator': 'FormItem',
|
|
56
56
|
'x-component': 'Input',
|
|
57
57
|
description:
|
|
58
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
58
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
59
59
|
},
|
|
60
60
|
type: relationshipType,
|
|
61
61
|
grid: {
|
|
@@ -123,7 +123,7 @@ export class OHOFieldInterface extends CollectionFieldInterface {
|
|
|
123
123
|
required: true,
|
|
124
124
|
default: '{{ useNewId("f_") }}',
|
|
125
125
|
description:
|
|
126
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
126
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
127
127
|
'x-decorator': 'FormItem',
|
|
128
128
|
'x-component': 'ForeignKey',
|
|
129
129
|
'x-validator': 'uid',
|
|
@@ -141,7 +141,7 @@ export const reverseFieldProperties: Record<string, ISchema> = {
|
|
|
141
141
|
'x-validator': 'uid',
|
|
142
142
|
'x-disabled': '{{ !showReverseFieldConfig }}',
|
|
143
143
|
description:
|
|
144
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
144
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
145
145
|
},
|
|
146
146
|
},
|
|
147
147
|
},
|
|
@@ -399,7 +399,7 @@ export const defaultProps = {
|
|
|
399
399
|
'x-component': 'Input',
|
|
400
400
|
'x-validator': 'uid',
|
|
401
401
|
description:
|
|
402
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
402
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
403
403
|
},
|
|
404
404
|
};
|
|
405
405
|
|
|
@@ -46,7 +46,7 @@ export class UUIDFieldInterface extends CollectionFieldInterface {
|
|
|
46
46
|
'x-component': 'Input',
|
|
47
47
|
'x-disabled': '{{ !createOnly }}',
|
|
48
48
|
description:
|
|
49
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
49
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
50
50
|
},
|
|
51
51
|
autoFill,
|
|
52
52
|
layout: {
|
|
@@ -58,4 +58,11 @@ describe('getKey', () => {
|
|
|
58
58
|
|
|
59
59
|
expect(getKey(a)).not.toBe(getKey(b));
|
|
60
60
|
});
|
|
61
|
+
|
|
62
|
+
it('distinguishes RunJS openView route state overrides', () => {
|
|
63
|
+
const a = createViewItem({ viewUid: 'v', openViewRouteState: { mode: 'drawer' } }, 0);
|
|
64
|
+
const b = createViewItem({ viewUid: 'v', openViewRouteState: { mode: 'dialog' } }, 0);
|
|
65
|
+
|
|
66
|
+
expect(getKey(a)).not.toBe(getKey(b));
|
|
67
|
+
});
|
|
61
68
|
});
|
|
@@ -250,6 +250,40 @@ describe('resolveViewParamsToViewList', () => {
|
|
|
250
250
|
expect(result[0].hidden.value).toBe(false); // default drawer type
|
|
251
251
|
expect(result[1].hidden.value).toBe(false); // default drawer type
|
|
252
252
|
});
|
|
253
|
+
|
|
254
|
+
it('should let RunJS openView route state override persisted mode for hidden calculation', () => {
|
|
255
|
+
const model2 = createMockModel('view2', 'drawer');
|
|
256
|
+
mockFlowEngine.getModel = vi.fn().mockReturnValue(model2);
|
|
257
|
+
|
|
258
|
+
const viewParams: ViewParam[] = [
|
|
259
|
+
{ viewUid: 'view1' },
|
|
260
|
+
{ viewUid: 'view2', openViewRouteState: { mode: 'embed' } },
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
const result = resolveViewParamsToViewList(mockFlowEngine, viewParams, mockRouteModel);
|
|
264
|
+
updateViewListHidden(result);
|
|
265
|
+
|
|
266
|
+
expect(result[0].hidden.value).toBe(true);
|
|
267
|
+
expect(result[1].hidden.value).toBe(false);
|
|
268
|
+
expect(model2.getStepParams).not.toHaveBeenCalled();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should treat child views as embed in mobile hidden calculation', () => {
|
|
272
|
+
const model2 = createMockModel('view2', 'drawer');
|
|
273
|
+
mockFlowEngine.getModel = vi.fn().mockReturnValue(model2);
|
|
274
|
+
|
|
275
|
+
const viewParams: ViewParam[] = [
|
|
276
|
+
{ viewUid: 'view1' },
|
|
277
|
+
{ viewUid: 'view2', openViewRouteState: { mode: 'dialog' } },
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
const result = resolveViewParamsToViewList(mockFlowEngine, viewParams, mockRouteModel);
|
|
281
|
+
updateViewListHidden(result, true);
|
|
282
|
+
|
|
283
|
+
expect(result[0].hidden.value).toBe(true);
|
|
284
|
+
expect(result[1].hidden.value).toBe(false);
|
|
285
|
+
expect(model2.getStepParams).not.toHaveBeenCalled();
|
|
286
|
+
});
|
|
253
287
|
});
|
|
254
288
|
|
|
255
289
|
describe('edge cases', () => {
|
|
@@ -0,0 +1,73 @@
|
|
|
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 {
|
|
11
|
+
FlowEngine,
|
|
12
|
+
FlowModel,
|
|
13
|
+
FlowRuntimeContext,
|
|
14
|
+
setupRuntimeContextSteps,
|
|
15
|
+
type Collection,
|
|
16
|
+
} from '@nocobase/flow-engine';
|
|
17
|
+
import { describe, expect, it } from 'vitest';
|
|
18
|
+
import { afterSuccess, getAfterSuccessResponseRecord, getMetaTreeWithResponseRecord } from '../afterSuccess';
|
|
19
|
+
|
|
20
|
+
describe('afterSuccess response record variable', () => {
|
|
21
|
+
it('reads the submit response record from the saveResource step', () => {
|
|
22
|
+
const record = { id: 1, title: 'Saved' };
|
|
23
|
+
|
|
24
|
+
expect(
|
|
25
|
+
getAfterSuccessResponseRecord({
|
|
26
|
+
steps: {
|
|
27
|
+
confirm: { params: {}, result: undefined },
|
|
28
|
+
saveResource: { params: {}, result: record },
|
|
29
|
+
afterSuccess: { params: {} },
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
32
|
+
).toBe(record);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('injects responseRecord into the afterSuccess runtime context', async () => {
|
|
36
|
+
const record = { id: 1, title: 'Saved' };
|
|
37
|
+
const sourceCtx = {
|
|
38
|
+
steps: {
|
|
39
|
+
saveResource: { params: {}, result: record },
|
|
40
|
+
},
|
|
41
|
+
t: (value: string) => value,
|
|
42
|
+
};
|
|
43
|
+
const properties = await afterSuccess.defineProperties(sourceCtx as FlowRuntimeContext);
|
|
44
|
+
|
|
45
|
+
expect(properties.responseRecord.get()).toBe(record);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('shows responseRecord in the variable picker for settings context', () => {
|
|
49
|
+
const engine = new FlowEngine();
|
|
50
|
+
class SubmitActionModel extends FlowModel {}
|
|
51
|
+
engine.registerModels({ SubmitActionModel });
|
|
52
|
+
const model = engine.createModel<SubmitActionModel>({ use: 'SubmitActionModel' });
|
|
53
|
+
const collection = {
|
|
54
|
+
name: 'users',
|
|
55
|
+
dataSourceKey: 'main',
|
|
56
|
+
getFilterByTK: (record: { id?: number }) => record.id,
|
|
57
|
+
} as unknown as Collection;
|
|
58
|
+
model.context.defineProperty('collection', { value: collection });
|
|
59
|
+
const flow = model.registerFlow({
|
|
60
|
+
key: 'submitSettings',
|
|
61
|
+
steps: {
|
|
62
|
+
saveResource: { title: 'Save record', handler: () => undefined },
|
|
63
|
+
afterSuccess: { use: 'afterSuccess' },
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
const ctx = new FlowRuntimeContext(model, 'submitSettings', 'settings');
|
|
67
|
+
setupRuntimeContextSteps(ctx, flow.steps, model, 'submitSettings');
|
|
68
|
+
|
|
69
|
+
const metaTree = getMetaTreeWithResponseRecord(ctx);
|
|
70
|
+
|
|
71
|
+
expect(metaTree.find((node) => node.name === 'responseRecord')?.title).toBe('Response record');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -139,6 +139,64 @@ describe('normalizeDataScopeFilter', () => {
|
|
|
139
139
|
expect(resource.removeFilterGroup).not.toHaveBeenCalled();
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
+
it('dataScope handler preserves current role as server-side variable', async () => {
|
|
143
|
+
const engine = new FlowEngine();
|
|
144
|
+
const resource = {
|
|
145
|
+
addFilterGroup: vi.fn(),
|
|
146
|
+
removeFilterGroup: vi.fn(),
|
|
147
|
+
};
|
|
148
|
+
const ctx = engine.context;
|
|
149
|
+
ctx.defineProperty('api', { value: { auth: { role: '__union__' } } });
|
|
150
|
+
ctx.defineProperty('user', {
|
|
151
|
+
value: {
|
|
152
|
+
roles: [{ name: 'admin' }, { name: 'member' }],
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
ctx.defineProperty('model', {
|
|
156
|
+
value: {
|
|
157
|
+
uid: 'table-1',
|
|
158
|
+
resource,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
const params = {
|
|
162
|
+
filter: {
|
|
163
|
+
logic: '$and',
|
|
164
|
+
items: [{ path: 'roles.name', operator: '$includes', value: '{{ ctx.role }}' }],
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
await (dataScope as { handler: (ctx: typeof ctx, params: typeof params) => Promise<void> }).handler(ctx, params);
|
|
169
|
+
|
|
170
|
+
expect(resource.addFilterGroup).toHaveBeenCalledWith('table-1', {
|
|
171
|
+
$and: [{ roles: { name: { $includes: '{{$nRole}}' } } }],
|
|
172
|
+
});
|
|
173
|
+
expect(resource.removeFilterGroup).not.toHaveBeenCalled();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('setTargetDataScope handler preserves current role as server-side variable', async () => {
|
|
177
|
+
const resource = {
|
|
178
|
+
addFilterGroup: vi.fn(),
|
|
179
|
+
removeFilterGroup: vi.fn(),
|
|
180
|
+
hasData: vi.fn(() => false),
|
|
181
|
+
refresh: vi.fn(),
|
|
182
|
+
};
|
|
183
|
+
const ctx = createSetTargetDataScopeContext(resource, { resolvedValue: ['admin', 'member'] });
|
|
184
|
+
const params = {
|
|
185
|
+
targetBlockUid: 'target-1',
|
|
186
|
+
filter: {
|
|
187
|
+
logic: '$and',
|
|
188
|
+
items: [{ path: 'roles.name', operator: '$includes', value: '{{ctx.role}}' }],
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
await (setTargetDataScope as any).handler(ctx, params);
|
|
193
|
+
|
|
194
|
+
expect(resource.addFilterGroup).toHaveBeenCalledWith('setTargetDataScope_action-1', {
|
|
195
|
+
$and: [{ roles: { name: { $includes: '{{$nRole}}' } } }],
|
|
196
|
+
});
|
|
197
|
+
expect(resource.removeFilterGroup).not.toHaveBeenCalled();
|
|
198
|
+
});
|
|
199
|
+
|
|
142
200
|
it('setTargetDataScope handler sends null for empty variable dependencies', async () => {
|
|
143
201
|
const resource = {
|
|
144
202
|
addFilterGroup: vi.fn(),
|