Skip to content

Commit

Permalink
[Workspace] Register a workspace dropdown menu at the top of left nav…
Browse files Browse the repository at this point in the history
… bar (opensearch-project#6150)

When workspace is enabled, the workspace plugin will register a
workspace dropdown menu via `chrome.registerCollapsibleNavHeader`
which displays a list of workspaces, links to create workspace page
and workspace list page.

---------

Signed-off-by: Yulong Ruan <[email protected]>
  • Loading branch information
ruanyl authored Mar 21, 2024
1 parent 0cc91ab commit a2ed39f
Show file tree
Hide file tree
Showing 12 changed files with 372 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [Multiple Datasource] Add default functionality for customer to choose default datasource ([#6058](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/6058))
- [Multiple Datasource] Add import support for Vega when specifying a datasource ([#6123](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6123))
- [Workspace] Validate if workspace exists when setup inside a workspace ([#6154](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6154))
- [Workspace] Register a workspace dropdown menu at the top of left nav bar ([#6150](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6150))

### 🐛 Bug Fixes

Expand Down
6 changes: 2 additions & 4 deletions src/core/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export type { Logos } from '../common';
export { PackageInfo, EnvironmentMode } from '../server/types';
/** @interal */
export { CoreContext, CoreSystem } from './core_system';
export { DEFAULT_APP_CATEGORIES } from '../utils';
export { DEFAULT_APP_CATEGORIES, WORKSPACE_TYPE } from '../utils';
export {
AppCategory,
UiSettingsParams,
Expand Down Expand Up @@ -357,6 +357,4 @@ export {

export { __osdBootstrap__ } from './osd_bootstrap';

export { WorkspacesStart, WorkspacesSetup, WorkspacesService } from './workspace';

export { WORKSPACE_TYPE } from '../utils';
export { WorkspacesStart, WorkspacesSetup, WorkspacesService, WorkspaceObject } from './workspace';
1 change: 1 addition & 0 deletions src/core/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function createCoreSetupMock({
} = {}) {
const mock = {
application: applicationServiceMock.createSetupContract(),
chrome: chromeServiceMock.createSetupContract(),
context: contextServiceMock.createSetupContract(),
docLinks: docLinksServiceMock.createSetupContract(),
fatalErrors: fatalErrorsServiceMock.createSetupContract(),
Expand Down
1 change: 1 addition & 0 deletions src/core/public/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ export {
WORKSPACE_TYPE,
formatUrlWithWorkspaceId,
getWorkspaceIdFromUrl,
cleanWorkspaceId,
} from '../../utils';
7 changes: 6 additions & 1 deletion src/core/public/workspace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@
* SPDX-License-Identifier: Apache-2.0
*/

export { WorkspacesStart, WorkspacesService, WorkspacesSetup } from './workspaces_service';
export {
WorkspacesStart,
WorkspacesService,
WorkspacesSetup,
WorkspaceObject,
} from './workspaces_service';
44 changes: 25 additions & 19 deletions src/core/public/workspace/workspaces_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,33 @@
import { BehaviorSubject } from 'rxjs';
import type { PublicMethodsOf } from '@osd/utility-types';

import { WorkspacesService } from './workspaces_service';
import { WorkspaceAttribute } from '..';
import { WorkspacesService, WorkspaceObject } from './workspaces_service';

const currentWorkspaceId$ = new BehaviorSubject<string>('');
const workspaceList$ = new BehaviorSubject<WorkspaceAttribute[]>([]);
const currentWorkspace$ = new BehaviorSubject<WorkspaceAttribute | null>(null);
const initialized$ = new BehaviorSubject<boolean>(false);

const createWorkspacesSetupContractMock = () => ({
currentWorkspaceId$,
workspaceList$,
currentWorkspace$,
initialized$,
});
const createWorkspacesSetupContractMock = () => {
const currentWorkspaceId$ = new BehaviorSubject<string>('');
const workspaceList$ = new BehaviorSubject<WorkspaceObject[]>([]);
const currentWorkspace$ = new BehaviorSubject<WorkspaceObject | null>(null);
const initialized$ = new BehaviorSubject<boolean>(false);
return {
currentWorkspaceId$,
workspaceList$,
currentWorkspace$,
initialized$,
};
};

const createWorkspacesStartContractMock = () => ({
currentWorkspaceId$,
workspaceList$,
currentWorkspace$,
initialized$,
});
const createWorkspacesStartContractMock = () => {
const currentWorkspaceId$ = new BehaviorSubject<string>('');
const workspaceList$ = new BehaviorSubject<WorkspaceObject[]>([]);
const currentWorkspace$ = new BehaviorSubject<WorkspaceObject | null>(null);
const initialized$ = new BehaviorSubject<boolean>(false);
return {
currentWorkspaceId$,
workspaceList$,
currentWorkspace$,
initialized$,
};
};

export type WorkspacesServiceContract = PublicMethodsOf<WorkspacesService>;
const createMock = (): jest.Mocked<WorkspacesServiceContract> => ({
Expand Down
2 changes: 1 addition & 1 deletion src/core/public/workspace/workspaces_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { isEqual } from 'lodash';

import { CoreService, WorkspaceAttribute } from '../../types';

type WorkspaceObject = WorkspaceAttribute & { readonly?: boolean };
export type WorkspaceObject = WorkspaceAttribute & { readonly?: boolean };

interface WorkspaceObservables {
/**
Expand Down
4 changes: 3 additions & 1 deletion src/plugins/workspace/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
* SPDX-License-Identifier: Apache-2.0
*/

export const WORKSPACE_OVERVIEW_APP_ID = 'workspace_overview';
export const WORKSPACE_FATAL_ERROR_APP_ID = 'workspace_fatal_error';
export const WORKSPACE_CREATE_APP_ID = 'workspace_create';
export const WORKSPACE_LIST_APP_ID = 'workspace_list';
export const WORKSPACE_OVERVIEW_APP_ID = 'workspace_overview';
export const WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID = 'workspace';
export const WORKSPACE_CONFLICT_CONTROL_SAVED_OBJECTS_CLIENT_WRAPPER_ID =
'workspace_conflict_control';
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';

import { WorkspaceMenu } from './workspace_menu';
import { coreMock } from '../../../../../core/public/mocks';
import { CoreStart } from '../../../../../core/public';

describe('<WorkspaceMenu />', () => {
let coreStartMock: CoreStart;

beforeEach(() => {
coreStartMock = coreMock.createStart();
coreStartMock.workspaces.initialized$.next(true);
jest.spyOn(coreStartMock.application, 'getUrlForApp').mockImplementation((appId: string) => {
return `https://test.com/app/${appId}`;
});
});

afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});

it('should display a list of workspaces in the dropdown', () => {
coreStartMock.workspaces.workspaceList$.next([
{ id: 'workspace-1', name: 'workspace 1' },
{ id: 'workspace-2', name: 'workspace 2' },
]);

render(<WorkspaceMenu coreStart={coreStartMock} />);
fireEvent.click(screen.getByText(/select a workspace/i));

expect(screen.getByText(/workspace 1/i)).toBeInTheDocument();
expect(screen.getByText(/workspace 2/i)).toBeInTheDocument();
});

it('should display current workspace name', () => {
coreStartMock.workspaces.currentWorkspace$.next({ id: 'workspace-1', name: 'workspace 1' });
render(<WorkspaceMenu coreStart={coreStartMock} />);
expect(screen.getByText(/workspace 1/i)).toBeInTheDocument();
});

it('should close the workspace dropdown list', async () => {
render(<WorkspaceMenu coreStart={coreStartMock} />);
fireEvent.click(screen.getByText(/select a workspace/i));

expect(screen.getByLabelText(/close workspace dropdown/i)).toBeInTheDocument();
fireEvent.click(screen.getByLabelText(/close workspace dropdown/i));
await waitFor(() => {
expect(screen.queryByLabelText(/close workspace dropdown/i)).not.toBeInTheDocument();
});
});

it('should navigate to the workspace', () => {
coreStartMock.workspaces.workspaceList$.next([
{ id: 'workspace-1', name: 'workspace 1' },
{ id: 'workspace-2', name: 'workspace 2' },
]);

const originalLocation = window.location;
Object.defineProperty(window, 'location', {
value: {
assign: jest.fn(),
},
});

render(<WorkspaceMenu coreStart={coreStartMock} />);
fireEvent.click(screen.getByText(/select a workspace/i));
fireEvent.click(screen.getByText(/workspace 1/i));

expect(window.location.assign).toHaveBeenCalledWith(
'https://test.com/w/workspace-1/app/workspace_overview'
);

Object.defineProperty(window, 'location', {
value: originalLocation,
});
});

it('should navigate to create workspace page', () => {
const originalLocation = window.location;
Object.defineProperty(window, 'location', {
value: {
assign: jest.fn(),
},
});

render(<WorkspaceMenu coreStart={coreStartMock} />);
fireEvent.click(screen.getByText(/select a workspace/i));
fireEvent.click(screen.getByText(/create workspace/i));
expect(window.location.assign).toHaveBeenCalledWith('https://test.com/app/workspace_create');

Object.defineProperty(window, 'location', {
value: originalLocation,
});
});

it('should navigate to workspace list page', () => {
const originalLocation = window.location;
Object.defineProperty(window, 'location', {
value: {
assign: jest.fn(),
},
});

render(<WorkspaceMenu coreStart={coreStartMock} />);
fireEvent.click(screen.getByText(/select a workspace/i));
fireEvent.click(screen.getByText(/all workspace/i));
expect(window.location.assign).toHaveBeenCalledWith('https://test.com/app/workspace_list');

Object.defineProperty(window, 'location', {
value: originalLocation,
});
});
});
Loading

0 comments on commit a2ed39f

Please sign in to comment.