From 92896bba3e8b80cdc1723e1981835b6792a238d5 Mon Sep 17 00:00:00 2001 From: tygao Date: Mon, 15 Apr 2024 17:03:19 +0800 Subject: [PATCH 1/6] feat: support right navigation register Signed-off-by: tygao --- .../nav_controls/nav_controls_service.ts | 23 +++++++++ .../ui/header/right_navigation_button.tsx | 49 +++++++++++++++++++ .../advanced_settings/public/plugin.ts | 12 ++++- src/plugins/dev_tools/public/plugin.ts | 19 +++++-- 4 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 src/core/public/chrome/ui/header/right_navigation_button.tsx diff --git a/src/core/public/chrome/nav_controls/nav_controls_service.ts b/src/core/public/chrome/nav_controls/nav_controls_service.ts index 57298dac39ff..68fad1429373 100644 --- a/src/core/public/chrome/nav_controls/nav_controls_service.ts +++ b/src/core/public/chrome/nav_controls/nav_controls_service.ts @@ -28,10 +28,16 @@ * under the License. */ +import React from 'react'; import { sortBy } from 'lodash'; import { BehaviorSubject, ReplaySubject, Observable } from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; import { MountPoint } from '../../types'; +import { + RightNavigationButton, + RightNavigationButtonProps, +} from '../ui/header/right_navigation_button'; +import { mountReactNode } from '../../utils'; /** @public */ export interface ChromeNavControl { @@ -39,6 +45,10 @@ export interface ChromeNavControl { mount: MountPoint; } +interface RightNavigationProps extends RightNavigationButtonProps { + order: number; +} + /** * {@link ChromeNavControls | APIs} for registering new controls to be displayed in the navigation bar. * @@ -62,6 +72,8 @@ export interface ChromeNavControls { registerRight(navControl: ChromeNavControl): void; /** Register a nav control to be presented on the top-center side of the chrome header. */ registerCenter(navControl: ChromeNavControl): void; + /** Register a nav control to be presented on the top-right side of the chrome header. The component and style will be maintained in chrome */ + registerRightNavigation(props: RightNavigationProps): void; /** @internal */ getLeft$(): Observable; /** @internal */ @@ -104,6 +116,17 @@ export class NavControlsService { navControlsExpandedCenter$.next( new Set([...navControlsExpandedCenter$.value.values(), navControl]) ), + registerRightNavigation: (props: RightNavigationProps) => { + const nav = { + order: props.order, + mount: mountReactNode( + React.createElement(RightNavigationButton, { + ...props, + }) + ), + }; + return navControlsRight$.next(new Set([...navControlsRight$.value.values(), nav])); + }, getLeft$: () => navControlsLeft$.pipe( diff --git a/src/core/public/chrome/ui/header/right_navigation_button.tsx b/src/core/public/chrome/ui/header/right_navigation_button.tsx new file mode 100644 index 000000000000..5efd9d90269c --- /dev/null +++ b/src/core/public/chrome/ui/header/right_navigation_button.tsx @@ -0,0 +1,49 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiButtonIcon } from '@elastic/eui'; +import React from 'react'; +import { CoreStart } from 'src/core/public'; + +export interface RightNavigationButtonProps { + application: CoreStart['application']; + http: CoreStart['http']; + appId: string; + iconType: string; + title: string; +} + +export const RightNavigationButton = ({ + application, + http, + appId, + iconType, + title, +}: RightNavigationButtonProps) => { + const navigateToApp = () => { + const appUrl = application.getUrlForApp(appId, { + path: '/', + absolute: false, + }); + // Remove prefix in Url including workspace and other prefix + const targetUrl = http.basePath.prepend(http.basePath.remove(appUrl), { + withoutClientBasePath: true, + }); + application.navigateToUrl(targetUrl); + }; + + return ( + + ); +}; diff --git a/src/plugins/advanced_settings/public/plugin.ts b/src/plugins/advanced_settings/public/plugin.ts index 608bfc6a25e7..0db39ed23772 100644 --- a/src/plugins/advanced_settings/public/plugin.ts +++ b/src/plugins/advanced_settings/public/plugin.ts @@ -29,10 +29,10 @@ */ import { i18n } from '@osd/i18n'; -import { CoreSetup, Plugin } from 'opensearch-dashboards/public'; import { FeatureCatalogueCategory } from '../../home/public'; import { ComponentRegistry } from './component_registry'; import { AdvancedSettingsSetup, AdvancedSettingsStart, AdvancedSettingsPluginSetup } from './types'; +import { CoreSetup, Plugin, CoreStart } from '../../../core/public'; const component = new ComponentRegistry(); @@ -77,7 +77,15 @@ export class AdvancedSettingsPlugin }; } - public start() { + public start(core: CoreStart) { + core.chrome.navControls.registerRightNavigation({ + order: 1, + appId: 'management/opensearch-dashboards/settings', + http: core.http, + application: core.application, + iconType: 'gear', + title, + }); return { component: component.start, }; diff --git a/src/plugins/dev_tools/public/plugin.ts b/src/plugins/dev_tools/public/plugin.ts index bb0b6ee1d981..59f68beb4b8b 100644 --- a/src/plugins/dev_tools/public/plugin.ts +++ b/src/plugins/dev_tools/public/plugin.ts @@ -29,7 +29,7 @@ */ import { BehaviorSubject } from 'rxjs'; -import { Plugin, CoreSetup, AppMountParameters } from 'src/core/public'; +import { Plugin, CoreSetup, AppMountParameters, CoreStart } from 'src/core/public'; import { AppUpdater } from 'opensearch-dashboards/public'; import { i18n } from '@osd/i18n'; import { sortBy } from 'lodash'; @@ -74,12 +74,14 @@ export class DevToolsPlugin implements Plugin { defaultMessage: 'Dev Tools', }); + private id = 'dev_tools'; + public setup(coreSetup: CoreSetup, deps: DevToolsSetupDependencies) { const { application: applicationSetup, getStartServices } = coreSetup; const { urlForwarding, managementOverview } = deps; applicationSetup.register({ - id: 'dev_tools', + id: this.id, title: this.title, updater$: this.appStateUpdater, icon: '/ui/logos/opensearch_mark.svg', @@ -98,7 +100,7 @@ export class DevToolsPlugin implements Plugin { }); managementOverview?.register({ - id: 'dev_tools', + id: this.id, title: this.title, description: i18n.translate('devTools.devToolsDescription', { defaultMessage: @@ -124,10 +126,19 @@ export class DevToolsPlugin implements Plugin { }; } - public start() { + public start(core: CoreStart) { if (this.getSortedDevTools().length === 0) { this.appStateUpdater.next(() => ({ navLinkStatus: AppNavLinkStatus.hidden })); } + core.chrome.navControls.registerRightNavigation({ + // order of dev tool should be after advance settings + order: 2, + appId: this.id, + http: core.http, + application: core.application, + iconType: 'consoleApp', + title: this.title, + }); } public stop() {} From 0cd63f40135bfe18040866370bb5af838a146ac6 Mon Sep 17 00:00:00 2001 From: tygao Date: Mon, 15 Apr 2024 17:31:49 +0800 Subject: [PATCH 2/6] update mock type Signed-off-by: tygao --- src/core/public/chrome/chrome_service.mock.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index b6ce429528a7..74ee7474bbf7 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -69,6 +69,7 @@ const createStartContractMock = () => { getLeft$: jest.fn(), getCenter$: jest.fn(), getRight$: jest.fn(), + registerRightNavigation: jest.fn(), }, setAppTitle: jest.fn(), setIsVisible: jest.fn(), From 4de20fcfc33889c994fdfcd7c8b11e53135b533f Mon Sep 17 00:00:00 2001 From: tygao Date: Fri, 19 Apr 2024 10:50:26 +0800 Subject: [PATCH 3/6] test: add tests Signed-off-by: tygao --- .../nav_controls/nav_controls_service.test.ts | 45 +++++++++++++++++++ .../nav_controls/nav_controls_service.ts | 2 +- .../right_navigation_button.test.tsx.snap | 23 ++++++++++ .../header/right_navigation_button.test.tsx | 45 +++++++++++++++++++ .../ui/header/right_navigation_button.tsx | 1 + 5 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/core/public/chrome/ui/header/__snapshots__/right_navigation_button.test.tsx.snap create mode 100644 src/core/public/chrome/ui/header/right_navigation_button.test.tsx diff --git a/src/core/public/chrome/nav_controls/nav_controls_service.test.ts b/src/core/public/chrome/nav_controls/nav_controls_service.test.ts index 6e2a71537e17..aa4d9dff2b15 100644 --- a/src/core/public/chrome/nav_controls/nav_controls_service.test.ts +++ b/src/core/public/chrome/nav_controls/nav_controls_service.test.ts @@ -30,6 +30,12 @@ import { NavControlsService } from './nav_controls_service'; import { take } from 'rxjs/operators'; +import { applicationServiceMock, httpServiceMock } from '../../../../core/public/mocks'; + +const mockMountReactNode = jest.fn().mockReturnValue('mock mount point'); +jest.mock('../../utils', () => ({ + mountReactNode: () => mockMountReactNode(), +})); describe('RecentlyAccessed#start()', () => { const getStart = () => { @@ -76,6 +82,45 @@ describe('RecentlyAccessed#start()', () => { }); }); + describe('top right navigation', () => { + const mockProps = { + application: applicationServiceMock.createStartContract(), + http: httpServiceMock.createStartContract(), + appId: 'app_id', + iconType: 'icon', + title: 'title', + order: 1, + }; + it('allows registration', async () => { + const navControls = getStart(); + navControls.registerRightNavigation(mockProps); + expect(await navControls.getRight$().pipe(take(1)).toPromise()).toEqual([ + { + mount: 'mock mount point', + order: 1, + }, + ]); + }); + + it('sorts controls by order property', async () => { + const navControls = getStart(); + const props1 = { ...mockProps, order: 10 }; + const props2 = { ...mockProps, order: 0 }; + navControls.registerRightNavigation(props1); + navControls.registerRightNavigation(props2); + expect(await navControls.getRight$().pipe(take(1)).toPromise()).toEqual([ + { + mount: 'mock mount point', + order: 0, + }, + { + mount: 'mock mount point', + order: 10, + }, + ]); + }); + }); + describe('center controls', () => { it('allows registration', async () => { const navControls = getStart(); diff --git a/src/core/public/chrome/nav_controls/nav_controls_service.ts b/src/core/public/chrome/nav_controls/nav_controls_service.ts index 68fad1429373..6e2d8b05b452 100644 --- a/src/core/public/chrome/nav_controls/nav_controls_service.ts +++ b/src/core/public/chrome/nav_controls/nav_controls_service.ts @@ -72,7 +72,7 @@ export interface ChromeNavControls { registerRight(navControl: ChromeNavControl): void; /** Register a nav control to be presented on the top-center side of the chrome header. */ registerCenter(navControl: ChromeNavControl): void; - /** Register a nav control to be presented on the top-right side of the chrome header. The component and style will be maintained in chrome */ + /** Register a nav control to be presented on the top-right side of the chrome header. The component and style will be uniformly maintained in chrome */ registerRightNavigation(props: RightNavigationProps): void; /** @internal */ getLeft$(): Observable; diff --git a/src/core/public/chrome/ui/header/__snapshots__/right_navigation_button.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/right_navigation_button.test.tsx.snap new file mode 100644 index 000000000000..1df64b25ca22 --- /dev/null +++ b/src/core/public/chrome/ui/header/__snapshots__/right_navigation_button.test.tsx.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Right navigation button should render base element normally 1`] = ` + +
+ +
+ +`; diff --git a/src/core/public/chrome/ui/header/right_navigation_button.test.tsx b/src/core/public/chrome/ui/header/right_navigation_button.test.tsx new file mode 100644 index 000000000000..bbc77af24111 --- /dev/null +++ b/src/core/public/chrome/ui/header/right_navigation_button.test.tsx @@ -0,0 +1,45 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; +import { RightNavigationButton } from './right_navigation_button'; +import { applicationServiceMock, httpServiceMock } from '../../../../../core/public/mocks'; + +const mockProps = { + application: applicationServiceMock.createStartContract(), + http: httpServiceMock.createStartContract(), + appId: 'app_id', + iconType: 'mock_icon', + title: 'title', +}; + +describe('Right navigation button', () => { + it('should render base element normally', () => { + const { baseElement } = render(); + expect(baseElement).toMatchSnapshot(); + }); + + it('should call application getUrlForApp and navigateToUrl after clicked', () => { + const navigateToUrl = jest.fn(); + const getUrlForApp = jest.fn(); + const props = { + ...mockProps, + application: { + ...applicationServiceMock.createStartContract(), + getUrlForApp, + navigateToUrl, + }, + }; + const { getByTestId } = render(); + const icon = getByTestId('rightNavigationButton'); + fireEvent.click(icon); + expect(getUrlForApp).toHaveBeenCalledWith('app_id', { + path: '/', + absolute: false, + }); + expect(navigateToUrl).toHaveBeenCalled(); + }); +}); diff --git a/src/core/public/chrome/ui/header/right_navigation_button.tsx b/src/core/public/chrome/ui/header/right_navigation_button.tsx index 5efd9d90269c..132abc0c4ec1 100644 --- a/src/core/public/chrome/ui/header/right_navigation_button.tsx +++ b/src/core/public/chrome/ui/header/right_navigation_button.tsx @@ -37,6 +37,7 @@ export const RightNavigationButton = ({ return ( Date: Fri, 19 Apr 2024 10:53:22 +0800 Subject: [PATCH 4/6] omit settings changes Signed-off-by: tygao --- src/plugins/advanced_settings/public/plugin.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/plugins/advanced_settings/public/plugin.ts b/src/plugins/advanced_settings/public/plugin.ts index 0db39ed23772..3e6c62d92ddb 100644 --- a/src/plugins/advanced_settings/public/plugin.ts +++ b/src/plugins/advanced_settings/public/plugin.ts @@ -29,10 +29,10 @@ */ import { i18n } from '@osd/i18n'; +import { CoreSetup, Plugin } from '../../../core/public'; import { FeatureCatalogueCategory } from '../../home/public'; import { ComponentRegistry } from './component_registry'; import { AdvancedSettingsSetup, AdvancedSettingsStart, AdvancedSettingsPluginSetup } from './types'; -import { CoreSetup, Plugin, CoreStart } from '../../../core/public'; const component = new ComponentRegistry(); @@ -77,15 +77,7 @@ export class AdvancedSettingsPlugin }; } - public start(core: CoreStart) { - core.chrome.navControls.registerRightNavigation({ - order: 1, - appId: 'management/opensearch-dashboards/settings', - http: core.http, - application: core.application, - iconType: 'gear', - title, - }); + public start() { return { component: component.start, }; From 26f295be22247b7d76e1da293e3fdbeef83bab7a Mon Sep 17 00:00:00 2001 From: tygao Date: Fri, 19 Apr 2024 10:53:49 +0800 Subject: [PATCH 5/6] omit settings changes Signed-off-by: tygao --- src/plugins/advanced_settings/public/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/advanced_settings/public/plugin.ts b/src/plugins/advanced_settings/public/plugin.ts index 3e6c62d92ddb..608bfc6a25e7 100644 --- a/src/plugins/advanced_settings/public/plugin.ts +++ b/src/plugins/advanced_settings/public/plugin.ts @@ -29,7 +29,7 @@ */ import { i18n } from '@osd/i18n'; -import { CoreSetup, Plugin } from '../../../core/public'; +import { CoreSetup, Plugin } from 'opensearch-dashboards/public'; import { FeatureCatalogueCategory } from '../../home/public'; import { ComponentRegistry } from './component_registry'; import { AdvancedSettingsSetup, AdvancedSettingsStart, AdvancedSettingsPluginSetup } from './types'; From 8152c91226155e3a7434a063ff5ba63ab20a86e1 Mon Sep 17 00:00:00 2001 From: tygao Date: Fri, 19 Apr 2024 11:17:10 +0800 Subject: [PATCH 6/6] chore: update component Signed-off-by: tygao --- .../right_navigation_button.test.tsx.snap | 25 +++++++++++++------ .../ui/header/right_navigation_button.tsx | 14 ++++------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/core/public/chrome/ui/header/__snapshots__/right_navigation_button.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/right_navigation_button.test.tsx.snap index 1df64b25ca22..3bd3bc57c9c7 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/right_navigation_button.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/right_navigation_button.test.tsx.snap @@ -5,18 +5,27 @@ exports[`Right navigation button should render base element normally 1`] = `
diff --git a/src/core/public/chrome/ui/header/right_navigation_button.tsx b/src/core/public/chrome/ui/header/right_navigation_button.tsx index 132abc0c4ec1..eac47b1ce812 100644 --- a/src/core/public/chrome/ui/header/right_navigation_button.tsx +++ b/src/core/public/chrome/ui/header/right_navigation_button.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiButtonIcon } from '@elastic/eui'; +import { EuiHeaderSectionItemButton, EuiIcon } from '@elastic/eui'; import React from 'react'; import { CoreStart } from 'src/core/public'; @@ -35,16 +35,12 @@ export const RightNavigationButton = ({ }; return ( - + > + + ); };