From 212bc947ab3cf2349e11c287c8cf759dc57fe521 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Mon, 28 Aug 2023 13:27:04 +0800 Subject: [PATCH] [Refractor] Feature flag (#102) * feat: add some flag Signed-off-by: SuZhou-Joe * refractor: add enabled$ in server side Signed-off-by: SuZhou-Joe * feat: add logic check in public side Signed-off-by: SuZhou-Joe * feature: remove useless property Signed-off-by: SuZhou-Joe * feat: some modify * feat: merge Signed-off-by: SuZhou-Joe * feat: remove useless code Signed-off-by: SuZhou-Joe * feat: remove useless code Signed-off-by: SuZhou-Joe * feat: optimize code Signed-off-by: SuZhou-Joe --------- Signed-off-by: SuZhou-Joe --- src/plugins/workspace/config.ts | 3 +- src/plugins/workspace/public/plugin.ts | 14 +++- .../workspace/public/workspace_client.ts | 12 +++ src/plugins/workspace/server/plugin.ts | 76 +++++++++++++++++-- src/plugins/workspace/server/routes/index.ts | 26 +++++++ 5 files changed, 121 insertions(+), 10 deletions(-) diff --git a/src/plugins/workspace/config.ts b/src/plugins/workspace/config.ts index 6fc163b67e45..40008907e71d 100644 --- a/src/plugins/workspace/config.ts +++ b/src/plugins/workspace/config.ts @@ -6,7 +6,6 @@ import { schema, TypeOf } from '@osd/config-schema'; export const configSchema = schema.object({ - enabled: schema.boolean({ defaultValue: false }), dashboardAdmin: schema.object( { backendRoles: schema.arrayOf(schema.string(), { @@ -22,3 +21,5 @@ export const configSchema = schema.object({ }); export type ConfigSchema = TypeOf; + +export const FEATURE_FLAG_KEY_IN_UI_SETTING = 'workspace_enabled'; diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index d6b150609ba0..99d4423da524 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -52,7 +52,16 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> ) { const workspaceClient = new WorkspaceClient(core.http, core.workspaces); workspaceClient.init(); - core.workspaces.workspaceEnabled$.next(true); + const featureFlagResp = await workspaceClient.getSettings(); + if (featureFlagResp.success) { + core.workspaces.workspaceEnabled$.next(featureFlagResp.result.enabled); + } else { + core.workspaces.workspaceEnabled$.next(false); + } + + if (!core.workspaces.workspaceEnabled$.getValue()) { + return {}; + } core.workspaces.registerWorkspaceMenuRender(renderWorkspaceMenu); @@ -241,6 +250,9 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> } public start(core: CoreStart) { + if (!core.workspaces.workspaceEnabled$.getValue()) { + return {}; + } this.coreStart = core; mountDropdownList({ diff --git a/src/plugins/workspace/public/workspace_client.ts b/src/plugins/workspace/public/workspace_client.ts index ab0b50c2b795..77edb920334b 100644 --- a/src/plugins/workspace/public/workspace_client.ts +++ b/src/plugins/workspace/public/workspace_client.ts @@ -329,6 +329,18 @@ export class WorkspaceClient { return result; } + public async getSettings(): Promise< + IResponse<{ + enabled: boolean; + }> + > { + const result = await this.safeFetch(this.getPath(['settings']), { + method: 'get', + }); + + return result; + } + public stop() { this.workspaces.workspaceList$.unsubscribe(); this.workspaces.currentWorkspaceId$.unsubscribe(); diff --git a/src/plugins/workspace/server/plugin.ts b/src/plugins/workspace/server/plugin.ts index ad4a4418dd2f..8ec28a102563 100644 --- a/src/plugins/workspace/server/plugin.ts +++ b/src/plugins/workspace/server/plugin.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ import { i18n } from '@osd/i18n'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; import { PluginInitializerContext, @@ -26,19 +26,28 @@ import { IWorkspaceDBImpl } from './types'; import { WorkspaceClientWithSavedObject } from './workspace_client'; import { WorkspaceSavedObjectsClientWrapper } from './saved_objects'; import { registerRoutes } from './routes'; -import { ConfigSchema } from '../config'; import { WORKSPACE_OVERVIEW_APP_ID } from '../common/constants'; +import { ConfigSchema, FEATURE_FLAG_KEY_IN_UI_SETTING } from '../config'; export class WorkspacePlugin implements Plugin<{}, {}> { private readonly logger: Logger; private client?: IWorkspaceDBImpl; + private coreStart?: CoreStart; private config$: Observable; + private enabled$: BehaviorSubject = new BehaviorSubject(false); + + private get isEnabled() { + return this.enabled$.getValue(); + } private proxyWorkspaceTrafficToRealHandler(setupDeps: CoreSetup) { /** * Proxy all {basePath}/w/{workspaceId}{osdPath*} paths to {basePath}{osdPath*} */ setupDeps.http.registerOnPreRouting(async (request, response, toolkit) => { + if (!this.isEnabled) { + return toolkit.next(); + } const regexp = /\/w\/([^\/]*)/; const matchedResult = request.url.pathname.match(regexp); @@ -81,14 +90,27 @@ export class WorkspacePlugin implements Plugin<{}, {}> { http: core.http, logger: this.logger, client: this.client as IWorkspaceDBImpl, + enabled$: this.enabled$, + config$: this.config$, }); - core.savedObjects.setClientFactoryProvider((repositoryFactory) => () => - new SavedObjectsClient(repositoryFactory.createInternalRepository()) + core.savedObjects.setClientFactoryProvider( + (repositoryFactory) => ({ request, includedHiddenTypes }) => { + const enabled = this.isEnabled; + if (enabled) { + return new SavedObjectsClient(repositoryFactory.createInternalRepository()); + } + + return new SavedObjectsClient( + repositoryFactory.createScopedRepository(request, includedHiddenTypes) + ); + } ); return { client: this.client, + enabled$: this.enabled$, + setWorkspaceFeatureFlag: this.setWorkspaceFeatureFlag, }; } @@ -120,8 +142,11 @@ export class WorkspacePlugin implements Plugin<{}, {}> { } } - private async setupWorkspaces(startDeps: CoreStart) { - const internalRepository = startDeps.savedObjects.createInternalRepository(); + private async setupWorkspaces() { + if (!this.coreStart) { + throw new Error('UI setting client can not be found'); + } + const internalRepository = this.coreStart.savedObjects.createInternalRepository(); const publicWorkspaceACL = new ACL().addPermission( [WorkspacePermissionMode.LibraryRead, WorkspacePermissionMode.LibraryWrite], { @@ -158,15 +183,50 @@ export class WorkspacePlugin implements Plugin<{}, {}> { ]); } + private async getUISettingClient() { + if (!this.coreStart) { + throw new Error('UI setting client can not be found'); + } + const { uiSettings, savedObjects } = this.coreStart as CoreStart; + const internalRepository = savedObjects.createInternalRepository(); + const savedObjectClient = new SavedObjectsClient(internalRepository); + return uiSettings.asScopedToClient(savedObjectClient); + } + + private async setWorkspaceFeatureFlag(featureFlag: boolean) { + const uiSettingClient = await this.getUISettingClient(); + await uiSettingClient.set(FEATURE_FLAG_KEY_IN_UI_SETTING, featureFlag); + this.enabled$.next(featureFlag); + } + + private async setupWorkspaceFeatureFlag() { + const uiSettingClient = await this.getUISettingClient(); + const workspaceEnabled = await uiSettingClient.get(FEATURE_FLAG_KEY_IN_UI_SETTING); + this.enabled$.next(!!workspaceEnabled); + return workspaceEnabled; + } + public start(core: CoreStart) { this.logger.debug('Starting SavedObjects service'); - this.setupWorkspaces(core); + this.coreStart = core; + + this.setupWorkspaceFeatureFlag(); + + this.enabled$.subscribe((enabled) => { + if (enabled) { + this.setupWorkspaces(); + } + }); return { client: this.client as IWorkspaceDBImpl, + enabled$: this.enabled$, + setWorkspaceFeatureFlag: this.setWorkspaceFeatureFlag, }; } - public stop() {} + public stop() { + this.enabled$.unsubscribe(); + } } diff --git a/src/plugins/workspace/server/routes/index.ts b/src/plugins/workspace/server/routes/index.ts index 0f37f14173d1..e8baf5bdb20d 100644 --- a/src/plugins/workspace/server/routes/index.ts +++ b/src/plugins/workspace/server/routes/index.ts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ import { schema } from '@osd/config-schema'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; import { ensureRawRequest } from '../../../../core/server'; import { @@ -13,6 +15,7 @@ import { WorkspacePermissionMode, } from '../../../../core/server'; import { IWorkspaceDBImpl, WorkspaceRoutePermissionItem } from '../types'; +import { ConfigSchema } from '../../config'; const WORKSPACES_API_BASE_URL = '/api/workspaces'; @@ -82,10 +85,14 @@ export function registerRoutes({ client, logger, http, + enabled$, + config$, }: { client: IWorkspaceDBImpl; logger: Logger; http: CoreSetup['http']; + enabled$: BehaviorSubject; + config$: Observable; }) { const router = http.createRouter(); router.post( @@ -255,4 +262,23 @@ export function registerRoutes({ return res.ok({ body: result }); }) ); + + router.get( + { + path: `${WORKSPACES_API_BASE_URL}/settings`, + validate: {}, + }, + router.handleLegacyErrors(async (context, req, res) => { + const config = await config$.pipe(first()).toPromise(); + return res.ok({ + body: { + success: true, + result: { + ...config, + enabled: enabled$.getValue(), + }, + }, + }); + }) + ); }