From 452d032921f30a17ef271d23253064234df6d806 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Thu, 14 Mar 2024 12:41:56 +0800 Subject: [PATCH] [Backport workspace-pr-integr] Add workspace id in base path Signed-off-by: SuZhou-Joe --- .../collapsible_nav.test.tsx.snap | 8 ++++ .../header/__snapshots__/header.test.tsx.snap | 4 ++ src/core/public/http/base_path.test.ts | 43 +++++++++++++------ src/core/public/http/base_path.ts | 12 +++--- src/core/public/http/http_service.mock.ts | 10 ++--- src/core/public/http/http_service.ts | 6 +-- src/core/public/http/types.ts | 17 +++++--- src/core/utils/workspace.test.ts | 14 ++++-- src/core/utils/workspace.ts | 6 +-- .../dashboard_listing.test.tsx.snap | 5 +++ .../dashboard_top_nav.test.tsx.snap | 6 +++ .../dashboard_empty_screen.test.tsx.snap | 3 ++ .../saved_objects_table.test.tsx.snap | 1 + .../__snapshots__/flyout.test.tsx.snap | 1 + ...telemetry_management_section.test.tsx.snap | 1 + src/plugins/workspace/server/plugin.ts | 1 + 16 files changed, 97 insertions(+), 41 deletions(-) diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index f0cd8afddfa3..d6094f78e24b 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -54,6 +54,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` basePath={ BasePath { "basePath": "/test", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -2007,6 +2008,7 @@ exports[`CollapsibleNav renders the default nav 1`] = ` basePath={ BasePath { "basePath": "/test", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -2311,6 +2313,7 @@ exports[`CollapsibleNav renders the default nav 2`] = ` basePath={ BasePath { "basePath": "/test", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -2616,6 +2619,7 @@ exports[`CollapsibleNav renders the default nav 3`] = ` basePath={ BasePath { "basePath": "/test", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -3212,6 +3216,7 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` basePath={ BasePath { "basePath": "/test", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -4329,6 +4334,7 @@ exports[`CollapsibleNav with custom branding renders the nav bar in default mode basePath={ BasePath { "basePath": "/test", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -5445,6 +5451,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode basePath={ BasePath { "basePath": "/test", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -6554,6 +6561,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in default m basePath={ BasePath { "basePath": "/test", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index b763760e58d5..a054b95c5683 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -242,6 +242,7 @@ exports[`Header handles visibility and lock changes 1`] = ` basePath={ BasePath { "basePath": "/test", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -5875,6 +5876,7 @@ exports[`Header handles visibility and lock changes 1`] = ` basePath={ BasePath { "basePath": "/test", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -6938,6 +6940,7 @@ exports[`Header renders condensed header 1`] = ` basePath={ BasePath { "basePath": "/test", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -11352,6 +11355,7 @@ exports[`Header renders condensed header 1`] = ` basePath={ BasePath { "basePath": "/test", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], diff --git a/src/core/public/http/base_path.test.ts b/src/core/public/http/base_path.test.ts index f80d41631b9b..921ec13e6db2 100644 --- a/src/core/public/http/base_path.test.ts +++ b/src/core/public/http/base_path.test.ts @@ -111,35 +111,50 @@ describe('BasePath', () => { }); }); - describe('workspaceBasePath', () => { - it('get path with workspace', () => { - expect(new BasePath('/foo/bar', '/foo/bar', '/workspace').get()).toEqual( - '/foo/bar/workspace' + describe('clientBasePath', () => { + it('get with clientBasePath provided when construct', () => { + expect(new BasePath('/foo/bar', '/foo/bar', '/client_base_path').get()).toEqual( + '/foo/bar/client_base_path' ); }); - it('getBasePath with workspace provided', () => { - expect(new BasePath('/foo/bar', '/foo/bar', '/workspace').getBasePath()).toEqual('/foo/bar'); + it('getBasePath with clientBasePath provided when construct', () => { + expect(new BasePath('/foo/bar', '/foo/bar', '/client_base_path').getBasePath()).toEqual( + '/foo/bar' + ); }); - it('prepend with workspace provided', () => { - expect(new BasePath('/foo/bar', '/foo/bar', '/workspace').prepend('/prepend')).toEqual( - '/foo/bar/workspace/prepend' + it('prepend with clientBasePath provided when construct', () => { + expect(new BasePath('/foo/bar', '/foo/bar', '/client_base_path').prepend('/prepend')).toEqual( + '/foo/bar/client_base_path/prepend' ); }); - it('prepend with workspace provided but calls without workspace', () => { + it('construct with clientBasePath provided but calls prepend with withoutClientBasePath is true', () => { expect( - new BasePath('/foo/bar', '/foo/bar', '/workspace').prepend('/prepend', { - withoutWorkspace: true, + new BasePath('/foo/bar', '/foo/bar', '/client_base_path').prepend('/prepend', { + withoutClientBasePath: true, }) ).toEqual('/foo/bar/prepend'); }); - it('remove with workspace provided', () => { + it('remove with clientBasePath provided when construct', () => { expect( - new BasePath('/foo/bar', '/foo/bar', '/workspace').remove('/foo/bar/workspace/remove') + new BasePath('/foo/bar', '/foo/bar', '/client_base_path').remove( + '/foo/bar/client_base_path/remove' + ) ).toEqual('/remove'); }); + + it('construct with clientBasePath provided but calls remove with withoutClientBasePath is true', () => { + expect( + new BasePath('/foo/bar', '/foo/bar', '/client_base_path').remove( + '/foo/bar/client_base_path/remove', + { + withoutClientBasePath: true, + } + ) + ).toEqual('/client_base_path/remove'); + }); }); }); diff --git a/src/core/public/http/base_path.ts b/src/core/public/http/base_path.ts index 254e4e2e6ad8..c88602c35b9d 100644 --- a/src/core/public/http/base_path.ts +++ b/src/core/public/http/base_path.ts @@ -35,11 +35,11 @@ export class BasePath { constructor( private readonly basePath: string = '', public readonly serverBasePath: string = basePath, - private readonly workspaceBasePath: string = '' + private readonly clientBasePath: string = '' ) {} public get = () => { - return `${this.basePath}${this.workspaceBasePath}`; + return `${this.basePath}${this.clientBasePath}`; }; public getBasePath = () => { @@ -47,8 +47,8 @@ export class BasePath { }; public prepend = (path: string, prependOptions?: PrependOptions): string => { - const { withoutWorkspace } = prependOptions || {}; - const basePath = withoutWorkspace ? this.basePath : this.get(); + const { withoutClientBasePath } = prependOptions || {}; + const basePath = withoutClientBasePath ? this.basePath : this.get(); if (!basePath) return path; return modifyUrl(path, (parts) => { if (!parts.hostname && parts.pathname && parts.pathname.startsWith('/')) { @@ -58,8 +58,8 @@ export class BasePath { }; public remove = (path: string, prependOptions?: PrependOptions): string => { - const { withoutWorkspace } = prependOptions || {}; - const basePath = withoutWorkspace ? this.basePath : this.get(); + const { withoutClientBasePath } = prependOptions || {}; + const basePath = withoutClientBasePath ? this.basePath : this.get(); if (!basePath) { return path; } diff --git a/src/core/public/http/http_service.mock.ts b/src/core/public/http/http_service.mock.ts index 934e4cbc9394..b34b4d1cfa88 100644 --- a/src/core/public/http/http_service.mock.ts +++ b/src/core/public/http/http_service.mock.ts @@ -39,7 +39,7 @@ export type HttpSetupMock = jest.Mocked & { anonymousPaths: jest.Mocked; }; -const createServiceMock = ({ basePath = '', workspaceBasePath = '' } = {}): HttpSetupMock => ({ +const createServiceMock = ({ basePath = '', clientBasePath = '' } = {}): HttpSetupMock => ({ fetch: jest.fn(), get: jest.fn(), head: jest.fn(), @@ -48,7 +48,7 @@ const createServiceMock = ({ basePath = '', workspaceBasePath = '' } = {}): Http patch: jest.fn(), delete: jest.fn(), options: jest.fn(), - basePath: new BasePath(basePath, undefined, workspaceBasePath), + basePath: new BasePath(basePath, undefined, clientBasePath), anonymousPaths: { register: jest.fn(), isAnonymous: jest.fn(), @@ -58,14 +58,14 @@ const createServiceMock = ({ basePath = '', workspaceBasePath = '' } = {}): Http intercept: jest.fn(), }); -const createMock = ({ basePath = '', workspaceBasePath = '' } = {}) => { +const createMock = ({ basePath = '', clientBasePath = '' } = {}) => { const mocked: jest.Mocked> = { setup: jest.fn(), start: jest.fn(), stop: jest.fn(), }; - mocked.setup.mockReturnValue(createServiceMock({ basePath, workspaceBasePath })); - mocked.start.mockReturnValue(createServiceMock({ basePath, workspaceBasePath })); + mocked.setup.mockReturnValue(createServiceMock({ basePath, clientBasePath })); + mocked.start.mockReturnValue(createServiceMock({ basePath, clientBasePath })); return mocked; }; diff --git a/src/core/public/http/http_service.ts b/src/core/public/http/http_service.ts index c2caf18be880..6832703c7925 100644 --- a/src/core/public/http/http_service.ts +++ b/src/core/public/http/http_service.ts @@ -52,15 +52,15 @@ export class HttpService implements CoreService { public setup({ injectedMetadata, fatalErrors }: HttpDeps): HttpSetup { const opensearchDashboardsVersion = injectedMetadata.getOpenSearchDashboardsVersion(); - let workspaceBasePath = ''; + let clientBasePath = ''; const workspaceId = getWorkspaceIdFromUrl(window.location.href); if (workspaceId) { - workspaceBasePath = `${WORKSPACE_PATH_PREFIX}/${workspaceId}`; + clientBasePath = `${WORKSPACE_PATH_PREFIX}/${workspaceId}`; } const basePath = new BasePath( injectedMetadata.getBasePath(), injectedMetadata.getServerBasePath(), - workspaceBasePath + clientBasePath ); const fetchService = new Fetch({ basePath, opensearchDashboardsVersion }); const loadingCount = this.loadingCount.setup({ fatalErrors }); diff --git a/src/core/public/http/types.ts b/src/core/public/http/types.ts index 709494963162..6e93e1cee94a 100644 --- a/src/core/public/http/types.ts +++ b/src/core/public/http/types.ts @@ -90,11 +90,14 @@ export type HttpStart = HttpSetup; /** * prepend options * - * withoutWorkspace option will prepend a relative url with only basePath - * workspaceId will rewrite the /w/{workspaceId} part, if workspace id is an empty string, prepend will remove the workspaceId part + * withoutClientBasePath option will prepend a relative url with serverBasePath only. + * For now, clientBasePath is consist of: + * workspacePath, which has the pattern of /w/{workspaceId}. + * + * In the future, clientBasePath may have other parts but keep `withoutClientBasePath` for now to not over-design the interface, */ export interface PrependOptions { - withoutWorkspace?: boolean; + withoutClientBasePath?: boolean; } /** @@ -103,22 +106,22 @@ export interface PrependOptions { */ export interface IBasePath { /** - * Gets the `basePath + workspace` string. + * Gets the `basePath + clientBasePath` string. */ get: () => string; /** - * Gets the `basePath + * Gets the `basePath` string */ getBasePath: () => string; /** - * Prepends `path` with the basePath + workspace. + * Prepends `path` with the basePath + clientBasePath. */ prepend: (url: string, prependOptions?: PrependOptions) => string; /** - * Removes the prepended basePath + workspace from the `path`. + * Removes the prepended basePath + clientBasePath from the `path`. */ remove: (url: string, prependOptions?: PrependOptions) => string; diff --git a/src/core/utils/workspace.test.ts b/src/core/utils/workspace.test.ts index 7d2a1f700c5f..a852ddcc5190 100644 --- a/src/core/utils/workspace.test.ts +++ b/src/core/utils/workspace.test.ts @@ -14,19 +14,27 @@ describe('#getWorkspaceIdFromUrl', () => { it('return empty when there is not a match', () => { expect(getWorkspaceIdFromUrl('http://localhost/w2/foo')).toEqual(''); }); + + it('return workspace when there is a match with basePath provided', () => { + expect(getWorkspaceIdFromUrl('http://localhost/basepath/w/foo', '/basepath')).toEqual('foo'); + }); + + it('return empty when there is a match without basePath but basePath provided', () => { + expect(getWorkspaceIdFromUrl('http://localhost/w/foo', '/w')).toEqual(''); + }); }); describe('#formatUrlWithWorkspaceId', () => { - const basePathWithoutWorkspaceBasePath = httpServiceMock.createSetupContract().basePath; + const basePathWithoutClientBasePath = httpServiceMock.createSetupContract().basePath; it('return url with workspace prefix when format with a id provided', () => { expect( - formatUrlWithWorkspaceId('/app/dashboard', 'foo', basePathWithoutWorkspaceBasePath) + formatUrlWithWorkspaceId('/app/dashboard', 'foo', basePathWithoutClientBasePath) ).toEqual('http://localhost/w/foo/app/dashboard'); }); it('return url without workspace prefix when format without a id', () => { expect( - formatUrlWithWorkspaceId('/w/foo/app/dashboard', '', basePathWithoutWorkspaceBasePath) + formatUrlWithWorkspaceId('/w/foo/app/dashboard', '', basePathWithoutClientBasePath) ).toEqual('http://localhost/app/dashboard'); }); }); diff --git a/src/core/utils/workspace.ts b/src/core/utils/workspace.ts index c369f95d5817..c383967483a8 100644 --- a/src/core/utils/workspace.ts +++ b/src/core/utils/workspace.ts @@ -6,8 +6,8 @@ import { WORKSPACE_PATH_PREFIX } from './constants'; import { IBasePath } from '../public'; -export const getWorkspaceIdFromUrl = (url: string): string => { - const regexp = /\/w\/([^\/]*)/; +export const getWorkspaceIdFromUrl = (url: string, basePath?: string): string => { + const regexp = new RegExp(`^${basePath || ''}\/w\/([^\/]*)`); const urlObject = new URL(url); const matchedResult = urlObject.pathname.match(regexp); if (matchedResult) { @@ -35,7 +35,7 @@ export const formatUrlWithWorkspaceId = (url: string, workspaceId: string, baseP } newUrl.pathname = basePath.prepend(newUrl.pathname, { - withoutWorkspace: true, + withoutClientBasePath: true, }); return newUrl.toString(); diff --git a/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap b/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap index 0a259ca26c7c..c82d883da4ff 100644 --- a/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap +++ b/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap @@ -857,6 +857,7 @@ exports[`dashboard listing hideWriteControls 1`] = ` }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -1993,6 +1994,7 @@ exports[`dashboard listing render table listing with initial filters from URL 1` }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -3190,6 +3192,7 @@ exports[`dashboard listing renders call to action when no dashboards exist 1`] = }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -4387,6 +4390,7 @@ exports[`dashboard listing renders table rows 1`] = ` }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -5584,6 +5588,7 @@ exports[`dashboard listing renders warning when listingLimit is exceeded 1`] = ` }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], diff --git a/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap b/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap index 4c5945a3d266..3a49450eacfa 100644 --- a/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap +++ b/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap @@ -749,6 +749,7 @@ exports[`Dashboard top nav render in embed mode 1`] = ` }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -1710,6 +1711,7 @@ exports[`Dashboard top nav render in embed mode, and force hide filter bar 1`] = }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -2671,6 +2673,7 @@ exports[`Dashboard top nav render in embed mode, components can be forced show b }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -3632,6 +3635,7 @@ exports[`Dashboard top nav render in full screen mode with appended URL param bu }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -4593,6 +4597,7 @@ exports[`Dashboard top nav render in full screen mode, no componenets should be }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -5554,6 +5559,7 @@ exports[`Dashboard top nav render with all components 1`] = ` }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], diff --git a/src/plugins/dashboard/public/application/embeddable/empty/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/embeddable/empty/__snapshots__/dashboard_empty_screen.test.tsx.snap index 187c24ba1528..8c8043ae7a99 100644 --- a/src/plugins/dashboard/public/application/embeddable/empty/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/plugins/dashboard/public/application/embeddable/empty/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -11,6 +11,7 @@ exports[`DashboardEmptyScreen renders correctly with readonly mode 1`] = ` }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -380,6 +381,7 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = ` }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], @@ -759,6 +761,7 @@ exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`] }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap index c501b2c7457b..ab8a16be5cbe 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap @@ -264,6 +264,7 @@ exports[`SavedObjectsTable should render normally 1`] = ` basePath={ BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap index f18ced5be1f5..3d27ba8139db 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap @@ -168,6 +168,7 @@ exports[`Flyout conflicts should allow conflict resolution 2`] = ` }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], diff --git a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap index 1576310d60e9..7ad1fb8cd938 100644 --- a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap +++ b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap @@ -313,6 +313,7 @@ exports[`TelemetryManagementSectionComponent renders null because allowChangingO }, "basePath": BasePath { "basePath": "", + "clientBasePath": "", "get": [Function], "getBasePath": [Function], "prepend": [Function], diff --git a/src/plugins/workspace/server/plugin.ts b/src/plugins/workspace/server/plugin.ts index d425f17d08eb..9d4535cfb51d 100644 --- a/src/plugins/workspace/server/plugin.ts +++ b/src/plugins/workspace/server/plugin.ts @@ -75,6 +75,7 @@ export class WorkspacePlugin implements Plugin<{}, {}> { WORKSPACE_CONFLICT_CONTROL_SAVED_OBJECTS_CLIENT_WRAPPER_ID, this.workspaceConflictControl.wrapperFactory ); + this.proxyWorkspaceTrafficToRealHandler(core); this.logger.info('Workspace permission control enabled:' + isPermissionControlEnabled); if (isPermissionControlEnabled) {