Skip to content

Commit

Permalink
supports configure workspace features with wildcard (#96)
Browse files Browse the repository at this point in the history
supports configure workspace features with wildcard

---------

Signed-off-by: Yulong Ruan <ruanyl@amazon.com>
  • Loading branch information
ruanyl authored and SuZhou-Joe committed Aug 31, 2023
1 parent 88be9b2 commit 665a303
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 30 deletions.
2 changes: 1 addition & 1 deletion src/core/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export {
StringValidation,
StringValidationRegex,
StringValidationRegexString,
WorkspaceAttribute,
} from '../types';

export {
Expand Down Expand Up @@ -352,7 +353,6 @@ export {
WorkspaceStart,
WorkspaceSetup,
WorkspaceService,
WorkspaceAttribute,
WorkspaceObservables,
} from './workspace';

Expand Down
1 change: 0 additions & 1 deletion src/core/public/workspace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@ export {
WorkspaceSetup,
WorkspaceObservables,
} from './workspaces_service';
export type { WorkspaceAttribute } from './workspaces_service';
2 changes: 1 addition & 1 deletion src/core/public/workspace/workspaces_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { BehaviorSubject } from 'rxjs';
import { WorkspaceAttribute } from '../workspace';
import { WorkspaceAttribute } from '..';

const currentWorkspaceId$ = new BehaviorSubject<string>('');
const workspaceList$ = new BehaviorSubject<WorkspaceAttribute[]>([]);
Expand Down
12 changes: 1 addition & 11 deletions src/core/public/workspace/workspaces_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { BehaviorSubject } from 'rxjs';
import { CoreService } from '../../types';
import { CoreService, WorkspaceAttribute } from '../../types';
import { InternalApplicationStart } from '../application';
import { HttpSetup } from '../http';

Expand Down Expand Up @@ -36,16 +36,6 @@ export interface WorkspaceStart extends WorkspaceObservables {
renderWorkspaceMenu: () => JSX.Element | null;
}

export interface WorkspaceAttribute {
id: string;
name: string;
description?: string;
features?: string[];
color?: string;
icon?: string;
defaultVISTheme?: string;
}

export class WorkspaceService implements CoreService<WorkspaceSetup, WorkspaceStart> {
private currentWorkspaceId$ = new BehaviorSubject<string>('');
private workspaceList$ = new BehaviorSubject<WorkspaceAttribute[]>([]);
Expand Down
2 changes: 1 addition & 1 deletion src/core/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ export {
MetricsServiceStart,
} from './metrics';

export { AppCategory } from '../types';
export { AppCategory, WorkspaceAttribute } from '../types';
export {
DEFAULT_APP_CATEGORIES,
WorkspacePermissionMode,
Expand Down
1 change: 1 addition & 0 deletions src/core/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ export * from './ui_settings';
export * from './saved_objects';
export * from './serializable';
export * from './custom_branding';
export * from './workspace';
14 changes: 14 additions & 0 deletions src/core/types/workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export interface WorkspaceAttribute {
id: string;
name: string;
description?: string;
features?: string[];
color?: string;
icon?: string;
defaultVISTheme?: string;
}
3 changes: 2 additions & 1 deletion src/plugins/workspace/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { WorkspaceClient } from './workspace_client';
import { IndexPatternManagementSetup } from '../../index_pattern_management/public';
import { renderWorkspaceMenu } from './render_workspace_menu';
import { Services } from './types';
import { featureMatchesConfig } from './utils';

interface WorkspacePluginSetupDeps {
savedObjectsManagement?: SavedObjectsManagementPluginSetup;
Expand Down Expand Up @@ -163,7 +164,7 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps>
private filterByWorkspace(workspace: WorkspaceAttribute | null, allNavLinks: ChromeNavLink[]) {
if (!workspace) return allNavLinks;
const features = workspace.features ?? [];
return allNavLinks.filter((item) => features.includes(item.id));
return allNavLinks.filter(featureMatchesConfig(features));
}

private filterNavLinks(core: CoreStart) {
Expand Down
83 changes: 83 additions & 0 deletions src/plugins/workspace/public/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { featureMatchesConfig } from './utils';

describe('workspace utils: featureMatchesConfig', () => {
it('feature configured with `*` should match any features', () => {
const match = featureMatchesConfig(['*']);
expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe(
true
);
expect(
match({ id: 'discover', category: { id: 'opensearchDashboards', label: 'Library' } })
).toBe(true);
});

it('should NOT match the config if feature id not matches', () => {
const match = featureMatchesConfig(['discover', 'dashboards', 'visualize']);
expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe(
false
);
});

it('should match the config if feature id matches', () => {
const match = featureMatchesConfig(['discover', 'dashboards', 'visualize']);
expect(
match({ id: 'discover', category: { id: 'opensearchDashboards', label: 'Library' } })
).toBe(true);
});

it('should match the config if feature category matches', () => {
const match = featureMatchesConfig(['discover', 'dashboards', '@management', 'visualize']);
expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe(
true
);
});

it('should match any features but not the excluded feature id', () => {
const match = featureMatchesConfig(['*', '!discover']);
expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe(
true
);
expect(
match({ id: 'discover', category: { id: 'opensearchDashboards', label: 'Library' } })
).toBe(false);
});

it('should match any features but not the excluded feature category', () => {
const match = featureMatchesConfig(['*', '!@management']);
expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe(
false
);
expect(match({ id: 'integrations', category: { id: 'management', label: 'Management' } })).toBe(
false
);
expect(
match({ id: 'discover', category: { id: 'opensearchDashboards', label: 'Library' } })
).toBe(true);
});

it('should match features of a category but NOT the excluded feature', () => {
const match = featureMatchesConfig(['@management', '!dev_tools']);
expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe(
false
);
expect(match({ id: 'integrations', category: { id: 'management', label: 'Management' } })).toBe(
true
);
});

it('a config presents later in the config array should override the previous config', () => {
// though `dev_tools` is excluded, but this config will override by '@management' as dev_tools has category 'management'
const match = featureMatchesConfig(['!dev_tools', '@management']);
expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe(
true
);
expect(match({ id: 'integrations', category: { id: 'management', label: 'Management' } })).toBe(
true
);
});
});
52 changes: 51 additions & 1 deletion src/plugins/workspace/public/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { WORKSPACE_PATH_PREFIX } from '../../../core/public/utils';
import { IBasePath } from '../../../core/public';
import { AppCategory, IBasePath } from '../../../core/public';

export const formatUrlWithWorkspaceId = (
url: string,
Expand All @@ -29,3 +29,53 @@ export const formatUrlWithWorkspaceId = (

return newUrl.toString();
};

/**
* Given a list of feature config, check if a feature matches config
* Rules:
* 1. `*` matches any feature
* 2. config starts with `@` matches category, for example, @management matches any feature of `management` category
* 3. to match a specific feature, just use the feature id, such as `discover`
* 4. to exclude feature or category, use `!@management` or `!discover`
* 5. the order of featureConfig array matters, from left to right, the later config override the previous config,
* for example, ['!@management', '*'] matches any feature because '*' overrides the previous setting: '!@management'
*/
export const featureMatchesConfig = (featureConfigs: string[]) => ({
id,
category,
}: {
id: string;
category?: AppCategory;
}) => {
let matched = false;

for (const featureConfig of featureConfigs) {
// '*' matches any feature
if (featureConfig === '*') {
matched = true;
}

// The config starts with `@` matches a category
if (category && featureConfig === `@${category.id}`) {
matched = true;
}

// The config matches a feature id
if (featureConfig === id) {
matched = true;
}

// If a config starts with `!`, such feature or category will be excluded
if (featureConfig.startsWith('!')) {
if (category && featureConfig === `!@${category.id}`) {
matched = false;
}

if (featureConfig === `!${id}`) {
matched = false;
}
}
}

return matched;
};
5 changes: 4 additions & 1 deletion src/plugins/workspace/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import {
Permissions,
WorkspacePermissionMode,
SavedObjectsClient,
WorkspaceAttribute,
DEFAULT_APP_CATEGORIES,
} from '../../../core/server';
import { IWorkspaceDBImpl, WorkspaceAttribute } from './types';
import { IWorkspaceDBImpl } from './types';
import { WorkspaceClientWithSavedObject } from './workspace_client';
import { WorkspaceSavedObjectsClientWrapper } from './saved_objects';
import { registerRoutes } from './routes';
Expand Down Expand Up @@ -147,6 +149,7 @@ export class WorkspacePlugin implements Plugin<{}, {}> {
name: i18n.translate('workspaces.management.workspace.default.name', {
defaultMessage: 'Management',
}),
features: [`@${DEFAULT_APP_CATEGORIES.management.id}`],
},
managementWorkspaceACL.getPermissions()
),
Expand Down
11 changes: 1 addition & 10 deletions src/plugins/workspace/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,9 @@ import {
CoreSetup,
WorkspacePermissionMode,
Permissions,
WorkspaceAttribute,
} from '../../../core/server';

export interface WorkspaceAttribute {
id: string;
name: string;
description?: string;
features?: string[];
color?: string;
icon?: string;
defaultVISTheme?: string;
}

export interface WorkspaceAttributeWithPermission extends WorkspaceAttribute {
permissions: Permissions;
}
Expand Down
8 changes: 6 additions & 2 deletions src/plugins/workspace/server/workspace_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import type { SavedObject, SavedObjectsClientContract, CoreSetup } from '../../../core/server';
import type {
SavedObject,
SavedObjectsClientContract,
CoreSetup,
WorkspaceAttribute,
} from '../../../core/server';
import { WORKSPACE_TYPE } from '../../../core/server';
import {
IWorkspaceDBImpl,
WorkspaceAttribute,
WorkspaceFindOptions,
IResponse,
IRequestDetail,
Expand Down

0 comments on commit 665a303

Please sign in to comment.