Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Refractor] Feature flag #102

Merged
merged 9 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/plugins/workspace/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(), {
Expand All @@ -22,3 +21,5 @@ export const configSchema = schema.object({
});

export type ConfigSchema = TypeOf<typeof configSchema>;

export const FEATURE_FLAG_KEY_IN_UI_SETTING = 'workspace_enabled';
14 changes: 13 additions & 1 deletion src/plugins/workspace/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.getFeatureFlag();
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);

Expand Down Expand Up @@ -241,6 +250,9 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps>
}

public start(core: CoreStart) {
if (!core.workspaces.workspaceEnabled$.getValue()) {
return {};
}
this.coreStart = core;

mountDropdownList({
Expand Down
12 changes: 12 additions & 0 deletions src/plugins/workspace/public/workspace_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,18 @@ export class WorkspaceClient {
return result;
}

public async getFeatureFlag(): 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();
Expand Down
76 changes: 70 additions & 6 deletions src/plugins/workspace/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<ConfigSchema>;
private enabled$: BehaviorSubject<boolean> = 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);

Expand Down Expand Up @@ -75,12 +84,27 @@ export class WorkspacePlugin implements Plugin<{}, {}> {
workspaceSavedObjectsClientWrapper.wrapperFactory
);

core.savedObjects.setClientFactoryProvider(
(repositoryFactory) => ({ request, includedHiddenTypes }) => {
const enabled = this.isEnabled;
if (enabled) {
return new SavedObjectsClient(repositoryFactory.createInternalRepository());
}

return new SavedObjectsClient(
repositoryFactory.createScopedRepository(request, includedHiddenTypes)
);
}
);

this.proxyWorkspaceTrafficToRealHandler(core);

registerRoutes({
http: core.http,
logger: this.logger,
client: this.client as IWorkspaceDBImpl,
enabled$: this.enabled$,
config$: this.config$,
});

core.savedObjects.setClientFactoryProvider((repositoryFactory) => () =>
Expand All @@ -89,6 +113,8 @@ export class WorkspacePlugin implements Plugin<{}, {}> {

return {
client: this.client,
enabled$: this.enabled$,
setWorkspaceFeatureFlag: this.setWorkspaceFeatureFlag,
};
}

Expand Down Expand Up @@ -120,8 +146,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],
{
Expand Down Expand Up @@ -158,15 +187,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();
}
}
26 changes: 26 additions & 0 deletions src/plugins/workspace/server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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';

Expand Down Expand Up @@ -82,10 +85,14 @@ export function registerRoutes({
client,
logger,
http,
enabled$,
config$,
}: {
client: IWorkspaceDBImpl;
logger: Logger;
http: CoreSetup['http'];
enabled$: BehaviorSubject<boolean>;
config$: Observable<ConfigSchema>;
}) {
const router = http.createRouter();
router.post(
Expand Down Expand Up @@ -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) => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it call context.core.uiSettings.client.get(FEATURE_FLAG_KEY_IN_UI_SETTING) to get the flag directly from saved objects?

Copy link
Collaborator Author

@SuZhou-Joe SuZhou-Joe Aug 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the flag in browser side should be consistent to the flag in server side, but not the opensearch side. The flag in server side may not be consistent to the flag in kibana index because user may call index update API to update the flag directly.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, so if changing the flag in the index but not via setWorkspaceFeatureFlag will require a server restart?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and that is not a expected behavior to update flag.

const config = await config$.pipe(first()).toPromise();
return res.ok({
body: {
success: true,
result: {
...config,
enabled: enabled$.getValue(),
},
},
});
})
);
}
Loading