@nocobase/client-v2 2.3.0-alpha.1 → 2.3.0-beta.10
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/APIClient.d.ts +12 -0
- package/es/components/form/ScanInput/useCodeScanner.d.ts +12 -2
- package/es/components/form/ScanInput/zxingWasmDecoder.d.ts +9 -0
- package/es/flow/actions/linkageRules.d.ts +25 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +6 -30
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/components/field-value-variable/DateVariableEditor.d.ts +24 -0
- package/es/flow/components/field-value-variable/FieldValueVariableInput.d.ts +32 -0
- package/es/flow/components/field-value-variable/dateValue.d.ts +36 -0
- package/es/flow/components/field-value-variable/index.d.ts +11 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +7 -0
- package/es/flow/components/placeholders/BlockPlaceholder.d.ts +1 -0
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/flow/internal/utils/operatorSchemaHelper.d.ts +2 -2
- package/es/flow/models/actions/UpdateRecordActionUtils.d.ts +8 -2
- package/es/flow/models/base/ActionModelCore.d.ts +4 -2
- package/es/flow/models/base/BlockGridModel.d.ts +3 -0
- package/es/flow/models/base/CollectionBlockModel.d.ts +3 -0
- package/es/flow/models/blocks/details/DetailsBlockModel.d.ts +3 -0
- package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +1 -0
- package/es/flow/models/blocks/filter-form/FilterFormSubmitActionModel.d.ts +3 -1
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +7 -0
- package/es/flow/models/blocks/form/FormGridModel.d.ts +6 -0
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +1 -0
- package/es/flow/models/blocks/table/JSColumnModel.d.ts +2 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +12 -0
- package/es/flow/models/blocks/table/utils.d.ts +1 -0
- package/es/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.d.ts +2 -1
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +0 -17
- package/es/flow/models/fields/AssociationFieldModel/recordSelectShared.d.ts +41 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- package/es/flow-compat/passwordUtils.d.ts +1 -1
- package/es/index.d.ts +1 -0
- package/es/index.mjs +148 -145
- package/es/ui-operation/index.d.ts +15 -0
- package/es/ui-operation/ui-operation-codec.d.ts +10 -0
- package/lib/index.js +170 -167
- package/lib/locale/languageCodes.js +1 -0
- package/package.json +10 -8
- package/src/APIClient.ts +92 -0
- package/src/Application.tsx +3 -1
- package/src/__tests__/APIClient.test.tsx +58 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +7 -2
- package/src/__tests__/settings-center.test.tsx +270 -6
- package/src/acl/ACLProvider.tsx +2 -0
- package/src/acl/__tests__/ACLProvider.test.tsx +92 -0
- package/src/collection-manager/__tests__/field-configure.test.ts +52 -0
- package/src/collection-manager/field-configure.ts +2 -2
- package/src/components/form/ScanInput/CodeScanner.tsx +64 -35
- package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +107 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +294 -24
- package/src/components/form/ScanInput/__tests__/zxingWasmDecoder.test.ts +78 -0
- package/src/components/form/ScanInput/useCodeScanner.ts +281 -23
- package/src/components/form/ScanInput/zxingWasmDecoder.ts +64 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +4 -2
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +17 -0
- package/src/components/form/table/Table.tsx +81 -6
- package/src/components/form/table/__tests__/Table.columnWidth.test.tsx +433 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +126 -8
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
- package/src/flow/actions/__tests__/linkageRules.actionStates.test.ts +33 -0
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +72 -0
- package/src/flow/actions/linkageRules.tsx +59 -16
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +78 -59
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +228 -0
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +42 -23
- package/src/flow/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/FieldAssignValueInput.tsx +42 -621
- package/src/flow/components/FlowRoute.tsx +71 -29
- package/src/flow/components/field-value-variable/DateVariableEditor.tsx +181 -0
- package/src/flow/components/field-value-variable/FieldValueVariableInput.tsx +335 -0
- package/src/flow/components/field-value-variable/__tests__/FieldValueVariableInput.test.tsx +389 -0
- package/src/flow/components/field-value-variable/dateValue.ts +223 -0
- package/src/flow/components/field-value-variable/index.ts +12 -0
- package/src/flow/components/filter/VariableFilterItem.tsx +23 -6
- package/src/flow/components/filter/__tests__/VariableFilterItem.rightMetaTree.test.tsx +158 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +125 -0
- package/src/flow/components/placeholders/BlockPlaceholder.tsx +70 -23
- package/src/flow/components/placeholders/__tests__/BlockPlaceholder.test.tsx +57 -7
- package/src/flow/index.ts +2 -0
- package/src/flow/internal/components/Markdown/DisplayMarkdown.tsx +14 -9
- package/src/flow/internal/components/Markdown/__tests__/DisplayMarkdown.test.tsx +46 -0
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- package/src/flow/internal/utils/operatorSchemaHelper.ts +3 -2
- package/src/flow/models/actions/UpdateRecordActionModel.tsx +18 -1
- package/src/flow/models/actions/UpdateRecordActionUtils.ts +18 -6
- package/src/flow/models/actions/__tests__/UpdateRecordActionModel.test.ts +76 -4
- package/src/flow/models/base/ActionModelCore.tsx +11 -2
- package/src/flow/models/base/BlockGridModel.tsx +26 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +38 -3
- package/src/flow/models/base/GridModel.tsx +0 -1
- package/src/flow/models/base/PageModel/PageTabModel.tsx +67 -17
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +509 -12
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
- package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.initialBeforeRenderRefresh.test.ts +125 -9
- package/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx +22 -1
- package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +36 -50
- package/src/flow/models/blocks/assign-form/__tests__/assignFieldValuesFlow.editor.test.tsx +140 -0
- package/src/flow/models/blocks/details/DetailsBlockModel.tsx +8 -3
- package/src/flow/models/blocks/details/__tests__/DetailsBlockModel.initialPaginationChangeRefresh.test.ts +32 -1
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +27 -3
- package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +36 -5
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +129 -14
- package/src/flow/models/blocks/filter-form/FilterFormSubmitActionModel.tsx +57 -4
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.onModelCreated.test.ts +174 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.toggleFormFieldsCollapse.test.ts +29 -0
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +359 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.getFilterValue.test.ts +72 -0
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormSubmitActionModel.lifecycle.test.ts +90 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +32 -4
- package/src/flow/models/blocks/filter-form/fields/FieldComponentProps.tsx +1 -1
- package/src/flow/models/blocks/filter-form/fields/__tests__/FieldComponentProps.options.test.tsx +61 -0
- package/src/flow/models/blocks/filter-manager/FilterManager.ts +5 -0
- package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +40 -0
- package/src/flow/models/blocks/form/FormBlockModel.tsx +73 -5
- package/src/flow/models/blocks/form/FormGridModel.tsx +4 -0
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +153 -2
- package/src/flow/models/blocks/form/__tests__/popupLinkage.test.tsx +175 -0
- package/src/flow/models/blocks/form/__tests__/runJsFormSubmit.test.ts +131 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +435 -0
- package/src/flow/models/blocks/form/value-runtime/rules.ts +67 -14
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +43 -19
- package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
- package/src/flow/models/blocks/table/JSColumnModel.tsx +10 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +28 -2
- package/src/flow/models/blocks/table/TableColumnModel.tsx +48 -9
- package/src/flow/models/blocks/table/__tests__/JSColumnModel.test.tsx +52 -5
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.filterReset.test.ts +78 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +33 -0
- package/src/flow/models/blocks/table/utils.ts +7 -0
- package/src/flow/models/fields/AssociationFieldModel/CascadeSelectFieldModel.tsx +33 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +8 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/__tests__/popupContext.test.ts +120 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/actions/PopupSubTableEditActionModel.tsx +10 -2
- package/src/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.tsx +29 -3
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +9 -136
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +191 -3
- package/src/flow/models/fields/AssociationFieldModel/recordSelectShared.tsx +156 -0
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
- package/src/flow/models/fields/DisplayAssociationField/DisplaySubTableFieldModel.tsx +42 -7
- package/src/flow/models/fields/DisplayAssociationField/__tests__/DisplaySubTableFieldModel.test.tsx +351 -0
- package/src/flow/models/fields/DisplayHtmlFieldModel.tsx +2 -1
- package/src/flow/models/fields/DisplayNumberFieldModel.tsx +1 -1
- package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
- package/src/flow/models/fields/NumberFieldModel.tsx +1 -1
- package/src/flow/models/fields/RichTextFieldModel/__tests__/RichTextFieldModel.test.tsx +74 -0
- package/src/flow/models/fields/RichTextFieldModel/index.tsx +27 -6
- package/src/flow/models/fields/__tests__/DisplayNumberFieldModel.test.ts +33 -0
- package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
- package/src/flow/models/fields/__tests__/NumberFieldModel.test.tsx +47 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +6 -0
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +27 -2
- package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +139 -13
- package/src/flow/models/topbar/TopbarActionModel.tsx +5 -5
- package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
- package/src/index.ts +1 -0
- package/src/layout-manager/LayoutContentRoute.tsx +2 -1
- package/src/layout-manager/LayoutRoute.tsx +2 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +87 -1
- package/src/locale/languageCodes.ts +1 -0
- package/src/settings-center/AdminSettingsLayout.tsx +23 -2
- package/src/ui-operation/index.ts +33 -0
- package/src/ui-operation/ui-operation-codec.ts +54 -0
|
@@ -64,6 +64,7 @@ const languageCodes = {
|
|
|
64
64
|
"ml-IN": { label: "\u0D2E\u0D32\u0D2F\u0D3E\u0D33\u0D02" },
|
|
65
65
|
"mn-MN": { label: "\u041C\u043E\u043D\u0433\u043E\u043B \u0445\u044D\u043B" },
|
|
66
66
|
"ms-MY": { label: "\u0628\u0647\u0627\u0633 \u0645\u0644\u0627\u064A\u0648" },
|
|
67
|
+
"my-MM": { label: "\u1019\u103C\u1014\u103A\u1019\u102C\u1018\u102C\u101E\u102C" },
|
|
67
68
|
"nb-NO": { label: "Norsk bokm\xE5l" },
|
|
68
69
|
"ne-NP": { label: "\u0928\u0947\u092A\u093E\u0932\u0940" },
|
|
69
70
|
"nl-BE": { label: "Vlaams" },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/client-v2",
|
|
3
|
-
"version": "2.3.0-
|
|
3
|
+
"version": "2.3.0-beta.10",
|
|
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.3.0-
|
|
31
|
-
"@nocobase/flow-engine": "2.3.0-
|
|
32
|
-
"@nocobase/sdk": "2.3.0-
|
|
33
|
-
"@nocobase/shared": "2.3.0-
|
|
34
|
-
"@nocobase/utils": "2.3.0-
|
|
30
|
+
"@nocobase/evaluators": "2.3.0-beta.10",
|
|
31
|
+
"@nocobase/flow-engine": "2.3.0-beta.10",
|
|
32
|
+
"@nocobase/sdk": "2.3.0-beta.10",
|
|
33
|
+
"@nocobase/shared": "2.3.0-beta.10",
|
|
34
|
+
"@nocobase/utils": "2.3.0-beta.10",
|
|
35
35
|
"ahooks": "^3.7.2",
|
|
36
36
|
"antd": "5.24.2",
|
|
37
37
|
"antd-style": "3.7.1",
|
|
@@ -44,8 +44,10 @@
|
|
|
44
44
|
"json5": "^2.2.3",
|
|
45
45
|
"jsqr": "^1.4.0",
|
|
46
46
|
"lodash": "4.17.21",
|
|
47
|
+
"react-device-detect": "2.2.3",
|
|
47
48
|
"react-i18next": "^11.15.1",
|
|
48
|
-
"react-router-dom": "^6.30.1"
|
|
49
|
+
"react-router-dom": "^6.30.1",
|
|
50
|
+
"zxing-wasm": "^3.1.3"
|
|
49
51
|
},
|
|
50
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "7aa3dce6cf3201752882f7012c72997898015d89"
|
|
51
53
|
}
|
package/src/APIClient.ts
CHANGED
|
@@ -8,6 +8,68 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { APIClient as APIClientSDK, hasHeaderValue } from '@nocobase/sdk';
|
|
11
|
+
import type { NotificationInstance } from 'antd/es/notification/interface';
|
|
12
|
+
import type { AxiosRequestConfig } from 'axios';
|
|
13
|
+
import React from 'react';
|
|
14
|
+
|
|
15
|
+
type ResponseMessage = string | { message?: unknown };
|
|
16
|
+
|
|
17
|
+
interface APIClientApplication {
|
|
18
|
+
getName?: () => string | undefined;
|
|
19
|
+
context?: {
|
|
20
|
+
notification?: NotificationInstance;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface NotificationError {
|
|
25
|
+
config?: AxiosRequestConfig & {
|
|
26
|
+
skipNotify?: boolean | ((error: unknown) => boolean);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const notificationCache = new Map<string, number>();
|
|
31
|
+
|
|
32
|
+
function getMessageText(item: ResponseMessage): string {
|
|
33
|
+
if (typeof item === 'string') {
|
|
34
|
+
return item;
|
|
35
|
+
}
|
|
36
|
+
return typeof item?.message === 'string' ? item.message : '';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function deduplicateMessages(messages: ResponseMessage[]): ResponseMessage[] {
|
|
40
|
+
if (notificationCache.size > 10) {
|
|
41
|
+
notificationCache.clear();
|
|
42
|
+
}
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
return messages.filter((item) => {
|
|
45
|
+
const message = getMessageText(item);
|
|
46
|
+
if (!message) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const lastTime = notificationCache.get(message);
|
|
50
|
+
if (lastTime && now - lastTime < 500) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
notificationCache.set(message, now);
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function notify(type: 'success' | 'error', messages: ResponseMessage[], instance?: NotificationInstance) {
|
|
59
|
+
if (!instance || messages.length === 0) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const filteredMessages = deduplicateMessages(messages);
|
|
63
|
+
if (filteredMessages.length === 0) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
instance[type]({
|
|
67
|
+
message: filteredMessages.map((item, index) => {
|
|
68
|
+
const message = getMessageText(item);
|
|
69
|
+
return React.createElement('div', { key: `${index}_${message}` }, message);
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
11
73
|
|
|
12
74
|
function offsetToTimeZone(offset: number) {
|
|
13
75
|
const hours = Math.floor(Math.abs(offset));
|
|
@@ -23,6 +85,12 @@ function getCurrentTimezone() {
|
|
|
23
85
|
}
|
|
24
86
|
|
|
25
87
|
export class APIClient extends APIClientSDK {
|
|
88
|
+
app?: APIClientApplication;
|
|
89
|
+
|
|
90
|
+
get notification() {
|
|
91
|
+
return this.app?.context?.notification;
|
|
92
|
+
}
|
|
93
|
+
|
|
26
94
|
getHostname() {
|
|
27
95
|
if (process.env.API_BASE_URL) {
|
|
28
96
|
try {
|
|
@@ -55,5 +123,29 @@ export class APIClient extends APIClientSDK {
|
|
|
55
123
|
return config;
|
|
56
124
|
});
|
|
57
125
|
super.interceptors();
|
|
126
|
+
this.useNotificationMiddleware();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
handleNotificationError(error: unknown) {
|
|
130
|
+
const notificationError = error as NotificationError;
|
|
131
|
+
const skipNotify = notificationError.config?.skipNotify;
|
|
132
|
+
if (skipNotify && (skipNotify === true || (typeof skipNotify === 'function' && skipNotify(error)))) {
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
const messages = this.toErrMessages(error);
|
|
136
|
+
if (Array.isArray(messages)) {
|
|
137
|
+
notify('error', messages, this.notification);
|
|
138
|
+
}
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
useNotificationMiddleware() {
|
|
143
|
+
this.axios.interceptors.response.use((response) => {
|
|
144
|
+
const messages = response.data?.messages;
|
|
145
|
+
if (Array.isArray(messages)) {
|
|
146
|
+
notify('success', messages, this.notification);
|
|
147
|
+
}
|
|
148
|
+
return response;
|
|
149
|
+
}, this.handleNotificationError.bind(this));
|
|
58
150
|
}
|
|
59
151
|
}
|
package/src/Application.tsx
CHANGED
|
@@ -45,7 +45,7 @@ export class Application extends BaseApplication<
|
|
|
45
45
|
public hasLoadError = false;
|
|
46
46
|
|
|
47
47
|
protected createApiClient(options: ApplicationOptions) {
|
|
48
|
-
|
|
48
|
+
const apiClient = new APIClient({
|
|
49
49
|
// Cross-origin API deployments rely on cookies for auth (e.g. permanent file
|
|
50
50
|
// URLs); trust is enforced server-side via the CORS origin whitelist and the
|
|
51
51
|
// CSRF middleware, not by omitting credentials.
|
|
@@ -53,6 +53,8 @@ export class Application extends BaseApplication<
|
|
|
53
53
|
...options.apiClient,
|
|
54
54
|
appName: options.name || getSubAppName(options.publicPath),
|
|
55
55
|
});
|
|
56
|
+
apiClient.app = this;
|
|
57
|
+
return apiClient;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
protected configureRuntimeAdapters() {
|
|
@@ -0,0 +1,58 @@
|
|
|
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 { APIClient } from '@nocobase/client-v2';
|
|
11
|
+
import type { NotificationInstance } from 'antd/es/notification/interface';
|
|
12
|
+
import MockAdapter from 'axios-mock-adapter';
|
|
13
|
+
import React from 'react';
|
|
14
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
15
|
+
|
|
16
|
+
function createAPIClient() {
|
|
17
|
+
const notification = {
|
|
18
|
+
error: vi.fn(),
|
|
19
|
+
success: vi.fn(),
|
|
20
|
+
} as unknown as NotificationInstance;
|
|
21
|
+
const apiClient = new APIClient();
|
|
22
|
+
apiClient.app = { context: { notification } };
|
|
23
|
+
const apiMock = new MockAdapter(apiClient.axios);
|
|
24
|
+
return { apiClient, apiMock, notification };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('APIClient response notifications', () => {
|
|
28
|
+
it('shows error messages returned by failed requests', async () => {
|
|
29
|
+
const { apiClient, apiMock, notification } = createAPIClient();
|
|
30
|
+
apiMock.onPost('/posts:create').reply(400, { errors: [{ message: 'Request rejected by workflow' }] });
|
|
31
|
+
|
|
32
|
+
await expect(apiClient.request({ url: '/posts:create', method: 'post' })).rejects.toBeDefined();
|
|
33
|
+
|
|
34
|
+
expect(notification.error).toHaveBeenCalledOnce();
|
|
35
|
+
const [{ message }] = vi.mocked(notification.error).mock.calls[0];
|
|
36
|
+
expect(renderToStaticMarkup(<>{message}</>)).toContain('Request rejected by workflow');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('does not show errors when skipNotify is enabled', async () => {
|
|
40
|
+
const { apiClient, apiMock, notification } = createAPIClient();
|
|
41
|
+
apiMock.onGet('/health').reply(500, { errors: [{ message: 'Health check failed' }] });
|
|
42
|
+
|
|
43
|
+
await expect(apiClient.request({ url: '/health', skipNotify: true })).rejects.toBeDefined();
|
|
44
|
+
|
|
45
|
+
expect(notification.error).not.toHaveBeenCalled();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('shows messages returned by successful requests', async () => {
|
|
49
|
+
const { apiClient, apiMock, notification } = createAPIClient();
|
|
50
|
+
apiMock.onPost('/posts:create').reply(200, { messages: [{ message: 'Created successfully' }] });
|
|
51
|
+
|
|
52
|
+
await apiClient.request({ url: '/posts:create', method: 'post' });
|
|
53
|
+
|
|
54
|
+
expect(notification.success).toHaveBeenCalledOnce();
|
|
55
|
+
const [{ message }] = vi.mocked(notification.success).mock.calls[0];
|
|
56
|
+
expect(renderToStaticMarkup(<>{message}</>)).toContain('Created successfully');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -415,7 +415,7 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
415
415
|
});
|
|
416
416
|
|
|
417
417
|
it.each(['/v2/admin/legacy-page/tab/tab-1', '/v2/admin/legacy-page/view/detail'])(
|
|
418
|
-
'should
|
|
418
|
+
'should explain authenticated direct v1-style v2 page access: %s',
|
|
419
419
|
async (pathname) => {
|
|
420
420
|
const app = createMockClient({
|
|
421
421
|
publicPath: '/v2/',
|
|
@@ -441,7 +441,12 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
441
441
|
const Root = app.getRootComponent();
|
|
442
442
|
render(<Root />);
|
|
443
443
|
|
|
444
|
-
expect(await screen.findByText('
|
|
444
|
+
expect(await screen.findByText('This page is not supported in the /v2/ branch')).toBeInTheDocument();
|
|
445
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
446
|
+
expect(screen.getByRole('link', { name: 'Open from the original entry' })).toHaveAttribute(
|
|
447
|
+
'href',
|
|
448
|
+
pathname.replace(/^\/v2/, ''),
|
|
449
|
+
);
|
|
445
450
|
expect(app.router.router.state.location.pathname).toBe(pathname);
|
|
446
451
|
},
|
|
447
452
|
);
|
|
@@ -7,10 +7,17 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
|
|
10
|
+
import {
|
|
11
|
+
ACLRolesCheckProvider,
|
|
12
|
+
createMockClient,
|
|
13
|
+
NocoBaseDesktopRouteType,
|
|
14
|
+
type NocoBaseDesktopRoute,
|
|
15
|
+
Plugin,
|
|
16
|
+
} from '@nocobase/client-v2';
|
|
17
|
+
import { act, render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
12
18
|
import React from 'react';
|
|
13
19
|
import { message } from 'antd';
|
|
20
|
+
import { vi } from 'vitest';
|
|
14
21
|
import { AdminSettingsLayoutModel as ClientV2AdminSettingsLayoutModel } from '../settings-center';
|
|
15
22
|
import { AdminSettingsLayoutModel as ClientV1AdminSettingsLayoutModel } from '../../../client/src/pm/AdminSettingsLayoutModel';
|
|
16
23
|
import { NocoBaseBuildInPlugin } from '../nocobase-buildin-plugin';
|
|
@@ -46,9 +53,15 @@ const mockAdminRuntime = (
|
|
|
46
53
|
snippets?: string[];
|
|
47
54
|
pmList?: any[];
|
|
48
55
|
systemSettings?: Record<string, any>;
|
|
56
|
+
desktopRoutes?: NocoBaseDesktopRoute[];
|
|
49
57
|
} = {},
|
|
50
58
|
) => {
|
|
51
|
-
const {
|
|
59
|
+
const {
|
|
60
|
+
snippets = ['pm', 'pm.system-settings.system-settings'],
|
|
61
|
+
pmList = [],
|
|
62
|
+
systemSettings = {},
|
|
63
|
+
desktopRoutes = [],
|
|
64
|
+
} = options;
|
|
52
65
|
|
|
53
66
|
app.dataSourceManager.getCollection = ((name: string, collectionName: string) => {
|
|
54
67
|
if (name === 'main' && collectionName === 'attachments') {
|
|
@@ -92,7 +105,7 @@ const mockAdminRuntime = (
|
|
|
92
105
|
},
|
|
93
106
|
});
|
|
94
107
|
app.apiMock.onGet('/desktopRoutes:listAccessible').reply(200, {
|
|
95
|
-
data:
|
|
108
|
+
data: desktopRoutes,
|
|
96
109
|
});
|
|
97
110
|
app.apiMock.onGet('systemSettings:get').reply(200, {
|
|
98
111
|
data: {
|
|
@@ -243,6 +256,39 @@ describe('settings center', () => {
|
|
|
243
256
|
expect(await screen.findByText('Demo plugin')).toBeInTheDocument();
|
|
244
257
|
});
|
|
245
258
|
|
|
259
|
+
it('should redirect to the first accessible page when the role cannot access settings', async () => {
|
|
260
|
+
const app = createMockClient({
|
|
261
|
+
plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
|
|
262
|
+
router: { type: 'memory', initialEntries: ['/admin/settings/system-settings'] },
|
|
263
|
+
});
|
|
264
|
+
mockAdminRuntime(app, {
|
|
265
|
+
snippets: ['!pm', '!pm.system-settings.system-settings'],
|
|
266
|
+
desktopRoutes: [
|
|
267
|
+
{
|
|
268
|
+
id: 1,
|
|
269
|
+
schemaUid: 'first-accessible-page',
|
|
270
|
+
title: 'First accessible page',
|
|
271
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: 2,
|
|
275
|
+
schemaUid: 'second-accessible-page',
|
|
276
|
+
title: 'Second accessible page',
|
|
277
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
await renderApp(app);
|
|
283
|
+
await waitForGetRequests(app, ['/auth:check', 'roles:check', '/desktopRoutes:listAccessible']);
|
|
284
|
+
|
|
285
|
+
await waitFor(() => {
|
|
286
|
+
expect(app.router.state.location.pathname).toBe('/admin/first-accessible-page');
|
|
287
|
+
});
|
|
288
|
+
expect(app.router.state.historyAction).toBe('REPLACE');
|
|
289
|
+
expect(screen.queryByText('Current settings page is unavailable')).not.toBeInTheDocument();
|
|
290
|
+
});
|
|
291
|
+
|
|
246
292
|
it('should hide plugin-manager menu item when pm snippet is missing', async () => {
|
|
247
293
|
const app = createMockClient({
|
|
248
294
|
plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
|
|
@@ -264,12 +310,23 @@ describe('settings center', () => {
|
|
|
264
310
|
plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
|
|
265
311
|
router: { type: 'memory', initialEntries: ['/admin/settings/unknown'] },
|
|
266
312
|
});
|
|
267
|
-
mockAdminRuntime(app
|
|
313
|
+
mockAdminRuntime(app, {
|
|
314
|
+
snippets: ['!pm', '!pm.system-settings.system-settings'],
|
|
315
|
+
desktopRoutes: [
|
|
316
|
+
{
|
|
317
|
+
id: 1,
|
|
318
|
+
schemaUid: 'first-accessible-page',
|
|
319
|
+
title: 'First accessible page',
|
|
320
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
});
|
|
268
324
|
|
|
269
325
|
await renderApp(app);
|
|
270
326
|
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
271
327
|
|
|
272
328
|
expect(await screen.findByText('Current settings page is unavailable')).toBeInTheDocument();
|
|
329
|
+
expect(app.router.state.location.pathname).toBe('/admin/settings/unknown');
|
|
273
330
|
});
|
|
274
331
|
|
|
275
332
|
it('should allow direct access to hidden page without showing menu entry', async () => {
|
|
@@ -290,13 +347,23 @@ describe('settings center', () => {
|
|
|
290
347
|
plugins: [NocoBaseBuildInPlugin, TestAclPlugin, HiddenSettingsPlugin],
|
|
291
348
|
router: { type: 'memory', initialEntries: ['/admin/settings/hidden-demo'] },
|
|
292
349
|
});
|
|
293
|
-
mockAdminRuntime(app
|
|
350
|
+
mockAdminRuntime(app, {
|
|
351
|
+
desktopRoutes: [
|
|
352
|
+
{
|
|
353
|
+
id: 1,
|
|
354
|
+
schemaUid: 'first-accessible-page',
|
|
355
|
+
title: 'First accessible page',
|
|
356
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
});
|
|
294
360
|
|
|
295
361
|
await renderApp(app);
|
|
296
362
|
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
297
363
|
|
|
298
364
|
expect(await screen.findByText('Hidden settings page')).toBeInTheDocument();
|
|
299
365
|
expect(screen.queryByRole('menuitem', { name: 'Hidden demo' })).not.toBeInTheDocument();
|
|
366
|
+
expect(app.router.state.location.pathname).toBe('/admin/settings/hidden-demo');
|
|
300
367
|
});
|
|
301
368
|
|
|
302
369
|
it('should show route empty state when direct access page has no permission', async () => {
|
|
@@ -319,6 +386,14 @@ describe('settings center', () => {
|
|
|
319
386
|
});
|
|
320
387
|
mockAdminRuntime(app, {
|
|
321
388
|
snippets: ['pm', 'pm.system-settings.system-settings', '!pm.secure-demo.index'],
|
|
389
|
+
desktopRoutes: [
|
|
390
|
+
{
|
|
391
|
+
id: 1,
|
|
392
|
+
schemaUid: 'first-accessible-page',
|
|
393
|
+
title: 'First accessible page',
|
|
394
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
395
|
+
},
|
|
396
|
+
],
|
|
322
397
|
});
|
|
323
398
|
|
|
324
399
|
await renderApp(app);
|
|
@@ -326,6 +401,195 @@ describe('settings center', () => {
|
|
|
326
401
|
|
|
327
402
|
expect(await screen.findByText('Current settings page is unavailable')).toBeInTheDocument();
|
|
328
403
|
expect(screen.queryByText('Secure settings page')).not.toBeInTheDocument();
|
|
404
|
+
expect(app.router.state.location.pathname).toBe('/admin/settings/secure-demo');
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('should redirect a denied settings tab to the first accessible tab', async () => {
|
|
408
|
+
const renderDeniedTab = vi.fn(() => <div>Denied tab content</div>);
|
|
409
|
+
|
|
410
|
+
class ProtectedSettingsTabsPlugin extends Plugin {
|
|
411
|
+
async load() {
|
|
412
|
+
this.pluginSettingsManager.addMenuItem({ key: 'protected-tabs', title: 'Protected tabs' });
|
|
413
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
414
|
+
menuKey: 'protected-tabs',
|
|
415
|
+
key: 'a-second',
|
|
416
|
+
title: 'Second accessible tab',
|
|
417
|
+
aclSnippet: 'pm.protected-tabs.second',
|
|
418
|
+
sort: 20,
|
|
419
|
+
Component: () => <div>Second accessible tab content</div>,
|
|
420
|
+
});
|
|
421
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
422
|
+
menuKey: 'protected-tabs',
|
|
423
|
+
key: 'z-first',
|
|
424
|
+
title: 'First accessible tab',
|
|
425
|
+
aclSnippet: 'pm.protected-tabs.first',
|
|
426
|
+
sort: 10,
|
|
427
|
+
Component: () => <div>First accessible tab content</div>,
|
|
428
|
+
});
|
|
429
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
430
|
+
menuKey: 'protected-tabs',
|
|
431
|
+
key: 'denied',
|
|
432
|
+
title: 'Denied tab',
|
|
433
|
+
aclSnippet: 'pm.protected-tabs.denied',
|
|
434
|
+
sort: 30,
|
|
435
|
+
Component: renderDeniedTab,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const app = createMockClient({
|
|
441
|
+
plugins: [NocoBaseBuildInPlugin, TestAclPlugin, ProtectedSettingsTabsPlugin],
|
|
442
|
+
router: { type: 'memory', initialEntries: ['/admin/settings/protected-tabs/denied'] },
|
|
443
|
+
});
|
|
444
|
+
mockAdminRuntime(app, {
|
|
445
|
+
snippets: ['pm', '!pm.protected-tabs.denied'],
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
await renderApp(app);
|
|
449
|
+
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
450
|
+
|
|
451
|
+
await waitFor(() => {
|
|
452
|
+
expect(app.router.router.state.location.pathname).toBe('/admin/settings/protected-tabs/z-first');
|
|
453
|
+
});
|
|
454
|
+
expect(app.router.router.state.historyAction).toBe('REPLACE');
|
|
455
|
+
expect(await screen.findByText('First accessible tab content')).toBeInTheDocument();
|
|
456
|
+
expect(screen.queryByText('Denied tab content')).not.toBeInTheDocument();
|
|
457
|
+
expect(renderDeniedTab).not.toHaveBeenCalled();
|
|
458
|
+
|
|
459
|
+
await act(async () => {
|
|
460
|
+
await app.router.router.navigate('/admin/settings/protected-tabs/a-second');
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
expect(await screen.findByText('Second accessible tab content')).toBeInTheDocument();
|
|
464
|
+
expect(app.router.router.state.location.pathname).toBe('/admin/settings/protected-tabs/a-second');
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('should skip an accessible dynamic tab when its route params cannot be resolved', async () => {
|
|
468
|
+
class DynamicSettingsTabsPlugin extends Plugin {
|
|
469
|
+
async load() {
|
|
470
|
+
this.pluginSettingsManager.addMenuItem({ key: 'dynamic-tabs', title: 'Dynamic tabs' });
|
|
471
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
472
|
+
menuKey: 'dynamic-tabs',
|
|
473
|
+
key: ':name',
|
|
474
|
+
title: 'Dynamic tab',
|
|
475
|
+
aclSnippet: 'pm.dynamic-tabs.dynamic',
|
|
476
|
+
sort: 1,
|
|
477
|
+
Component: () => <div>Dynamic tab content</div>,
|
|
478
|
+
});
|
|
479
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
480
|
+
menuKey: 'dynamic-tabs',
|
|
481
|
+
key: 'fallback',
|
|
482
|
+
title: 'Static fallback tab',
|
|
483
|
+
aclSnippet: 'pm.dynamic-tabs.fallback',
|
|
484
|
+
sort: 2,
|
|
485
|
+
Component: () => <div>Static fallback tab content</div>,
|
|
486
|
+
});
|
|
487
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
488
|
+
menuKey: 'dynamic-tabs',
|
|
489
|
+
key: 'denied',
|
|
490
|
+
title: 'Denied tab',
|
|
491
|
+
aclSnippet: 'pm.dynamic-tabs.denied',
|
|
492
|
+
sort: 3,
|
|
493
|
+
Component: () => <div>Denied dynamic tab content</div>,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const app = createMockClient({
|
|
499
|
+
plugins: [NocoBaseBuildInPlugin, TestAclPlugin, DynamicSettingsTabsPlugin],
|
|
500
|
+
router: { type: 'memory', initialEntries: ['/admin/settings/dynamic-tabs/denied'] },
|
|
501
|
+
});
|
|
502
|
+
mockAdminRuntime(app, {
|
|
503
|
+
snippets: ['pm', '!pm.dynamic-tabs.denied'],
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
await renderApp(app);
|
|
507
|
+
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
508
|
+
|
|
509
|
+
await waitFor(() => {
|
|
510
|
+
expect(app.router.router.state.location.pathname).toBe('/admin/settings/dynamic-tabs/fallback');
|
|
511
|
+
});
|
|
512
|
+
expect(await screen.findByText('Static fallback tab content')).toBeInTheDocument();
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('should preserve resolved route params when redirecting between dynamic tabs', async () => {
|
|
516
|
+
class DynamicSiblingTabsPlugin extends Plugin {
|
|
517
|
+
async load() {
|
|
518
|
+
this.pluginSettingsManager.addMenuItem({ key: 'dynamic-siblings', title: 'Dynamic sibling tabs' });
|
|
519
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
520
|
+
menuKey: 'dynamic-siblings',
|
|
521
|
+
key: ':name/channels',
|
|
522
|
+
title: 'Dynamic channels tab',
|
|
523
|
+
aclSnippet: 'pm.dynamic-siblings.channels',
|
|
524
|
+
sort: 1,
|
|
525
|
+
Component: () => <div>Dynamic channels tab content</div>,
|
|
526
|
+
});
|
|
527
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
528
|
+
menuKey: 'dynamic-siblings',
|
|
529
|
+
key: ':name/logs',
|
|
530
|
+
title: 'Dynamic logs tab',
|
|
531
|
+
aclSnippet: 'pm.dynamic-siblings.logs',
|
|
532
|
+
sort: 2,
|
|
533
|
+
Component: () => <div>Dynamic logs tab content</div>,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const app = createMockClient({
|
|
539
|
+
plugins: [NocoBaseBuildInPlugin, TestAclPlugin, DynamicSiblingTabsPlugin],
|
|
540
|
+
router: { type: 'memory', initialEntries: ['/admin/settings/dynamic-siblings/email:primary/logs'] },
|
|
541
|
+
});
|
|
542
|
+
mockAdminRuntime(app, {
|
|
543
|
+
snippets: ['pm', '!pm.dynamic-siblings.logs'],
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
await renderApp(app);
|
|
547
|
+
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
548
|
+
|
|
549
|
+
await waitFor(() => {
|
|
550
|
+
expect(app.router.router.state.location.pathname).toBe('/admin/settings/dynamic-siblings/email:primary/channels');
|
|
551
|
+
});
|
|
552
|
+
expect(await screen.findByText('Dynamic channels tab content')).toBeInTheDocument();
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
it('should resolve hyphenated route param names when redirecting between dynamic tabs', async () => {
|
|
556
|
+
class HyphenatedParamTabsPlugin extends Plugin {
|
|
557
|
+
async load() {
|
|
558
|
+
this.pluginSettingsManager.addMenuItem({ key: 'hyphenated-params', title: 'Hyphenated param tabs' });
|
|
559
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
560
|
+
menuKey: 'hyphenated-params',
|
|
561
|
+
key: ':data-source/channels',
|
|
562
|
+
title: 'Hyphenated channels tab',
|
|
563
|
+
aclSnippet: 'pm.hyphenated-params.channels',
|
|
564
|
+
sort: 1,
|
|
565
|
+
Component: () => <div>Hyphenated channels tab content</div>,
|
|
566
|
+
});
|
|
567
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
568
|
+
menuKey: 'hyphenated-params',
|
|
569
|
+
key: ':data-source/logs',
|
|
570
|
+
title: 'Hyphenated logs tab',
|
|
571
|
+
aclSnippet: 'pm.hyphenated-params.logs',
|
|
572
|
+
sort: 2,
|
|
573
|
+
Component: () => <div>Hyphenated logs tab content</div>,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const app = createMockClient({
|
|
579
|
+
plugins: [NocoBaseBuildInPlugin, TestAclPlugin, HyphenatedParamTabsPlugin],
|
|
580
|
+
router: { type: 'memory', initialEntries: ['/admin/settings/hyphenated-params/email/logs'] },
|
|
581
|
+
});
|
|
582
|
+
mockAdminRuntime(app, {
|
|
583
|
+
snippets: ['pm', '!pm.hyphenated-params.logs'],
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
await renderApp(app);
|
|
587
|
+
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
588
|
+
|
|
589
|
+
await waitFor(() => {
|
|
590
|
+
expect(app.router.router.state.location.pathname).toBe('/admin/settings/hyphenated-params/email/channels');
|
|
591
|
+
});
|
|
592
|
+
expect(await screen.findByText('Hyphenated channels tab content')).toBeInTheDocument();
|
|
329
593
|
});
|
|
330
594
|
|
|
331
595
|
it('should keep menu visible when menu acl is denied but child page is visible', async () => {
|
package/src/acl/ACLProvider.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import { observable } from '@formily/reactive';
|
|
|
11
11
|
import React, { useRef } from 'react';
|
|
12
12
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type FC } from 'react';
|
|
13
13
|
import { useLocation } from 'react-router-dom';
|
|
14
|
+
import { writeFlowSettingsPreference } from '../flow/admin-shell/admin-layout/flowSettingsPreference';
|
|
14
15
|
import { useApp } from '../hooks/useApp';
|
|
15
16
|
import { createAclSnippetAllow } from './createAclSnippetAllow';
|
|
16
17
|
|
|
@@ -118,6 +119,7 @@ export const ACLRolesCheckProvider: FC = ({ children }) => {
|
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
if (!createAclSnippetAllow(nextData?.snippets || [], !!nextData?.allowAll)('ui.*')) {
|
|
122
|
+
writeFlowSettingsPreference(false);
|
|
121
123
|
await app.flowEngine.flowSettings.disable();
|
|
122
124
|
}
|
|
123
125
|
} catch (error) {
|