diff --git a/CHANGELOG.md b/CHANGELOG.md index 17059703937e..56f04ea211fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -107,6 +107,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Multiple Datasource] Extract the button component for datasource picker to avoid duplicate code ([#6559](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6559)) - [Workspace] Add workspaces filter to saved objects page. ([#6458](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6458)) - [Multiple Datasource] Support multi data source in Region map ([#6654](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6654)) +- Add `rightNavigationButton` component in chrome service for applications to register and add dev tool to top right navigation. ([#6553](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6553)) ### 🐛 Bug Fixes diff --git a/src/core/public/chrome/constants.ts b/src/core/public/chrome/constants.ts index 5008f8b4a69a..4f98257ea5f8 100644 --- a/src/core/public/chrome/constants.ts +++ b/src/core/public/chrome/constants.ts @@ -31,3 +31,9 @@ export const OPENSEARCH_DASHBOARDS_ASK_OPENSEARCH_LINK = 'https://forum.opensearch.org/'; export const GITHUB_CREATE_ISSUE_LINK = 'https://github.com/opensearch-project/OpenSearch-Dashboards/issues/new/choose'; + +export enum RightNavigationOrder { + // order of dev tool should be after advance settings + Settings = 10, + DevTool = 20, +} diff --git a/src/core/public/chrome/index.ts b/src/core/public/chrome/index.ts index 4004c2c323f9..eb92ccdc6ba3 100644 --- a/src/core/public/chrome/index.ts +++ b/src/core/public/chrome/index.ts @@ -44,8 +44,9 @@ export { ChromeHelpExtensionMenuDocumentationLink, ChromeHelpExtensionMenuGitHubLink, } from './ui/header/header_help_menu'; -export { NavType } from './ui'; +export { NavType, RightNavigationButton, RightNavigationButtonProps } from './ui'; export { ChromeNavLink, ChromeNavLinks, ChromeNavLinkUpdateableFields } from './nav_links'; export { ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem } from './recently_accessed'; export { ChromeNavControl, ChromeNavControls } from './nav_controls'; export { ChromeDocTitle } from './doc_title'; +export { RightNavigationOrder } from './constants'; 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..27b5748febcf --- /dev/null +++ b/src/core/public/chrome/ui/header/__snapshots__/right_navigation_button.test.tsx.snap @@ -0,0 +1,32 @@ +// 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/index.ts b/src/core/public/chrome/ui/header/index.ts index 0bde1cef241a..811eca0cad84 100644 --- a/src/core/public/chrome/ui/header/index.ts +++ b/src/core/public/chrome/ui/header/index.ts @@ -37,3 +37,4 @@ export { ChromeHelpExtensionMenuDocumentationLink, ChromeHelpExtensionMenuGitHubLink, } from './header_help_menu'; +export { RightNavigationButton, RightNavigationButtonProps } from './right_navigation_button'; 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 new file mode 100644 index 000000000000..31464759aea5 --- /dev/null +++ b/src/core/public/chrome/ui/header/right_navigation_button.tsx @@ -0,0 +1,63 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiHeaderSectionItemButton, EuiIcon } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { CoreStart } from '../../..'; + +import { isModifiedOrPrevented } from './nav_link'; +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 targetUrl = useMemo(() => { + const appUrl = application.getUrlForApp(appId, { + path: '/', + absolute: false, + }); + // Remove prefix in Url including workspace and other prefix + return http.basePath.prepend(http.basePath.remove(appUrl), { + withoutClientBasePath: true, + }); + }, [application, http.basePath, appId]); + + const isLeftClickEvent = (event: React.MouseEvent) => { + return event.button === 0; + }; + + const navigateToApp = (event: React.MouseEvent) => { + /* Use href and onClick to support "open in new tab" and SPA navigation in the same link */ + if ( + isLeftClickEvent(event) && // ignore everything but left clicks + !isModifiedOrPrevented(event) + ) { + event.preventDefault(); + application.navigateToUrl(targetUrl); + } + return; + }; + + return ( + + + + ); +}; diff --git a/src/core/public/chrome/ui/index.ts b/src/core/public/chrome/ui/index.ts index 93e3371b2892..11f0c0302df5 100644 --- a/src/core/public/chrome/ui/index.ts +++ b/src/core/public/chrome/ui/index.ts @@ -37,4 +37,6 @@ export { ChromeHelpExtensionMenuDocumentationLink, ChromeHelpExtensionMenuGitHubLink, NavType, + RightNavigationButton, + RightNavigationButtonProps, } from './header'; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 2107ab668f3e..10d1928d6cf2 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -68,6 +68,9 @@ import { ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem, NavType, + RightNavigationOrder, + RightNavigationButton, + RightNavigationButtonProps, } from './chrome'; import { FatalErrorsSetup, FatalErrorsStart, FatalErrorInfo } from './fatal_errors'; import { HttpSetup, HttpStart } from './http'; @@ -360,6 +363,9 @@ export { UiSettingsState, NavType, Branding, + RightNavigationOrder, + RightNavigationButton, + RightNavigationButtonProps, }; export { __osdBootstrap__ } from './osd_bootstrap'; diff --git a/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap b/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap index db34c4f229bb..b0ee4d34705f 100644 --- a/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap +++ b/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap @@ -5967,4 +5967,4 @@ exports[`dashboard listing renders warning when listingLimit is exceeded 1`] = ` -`; +`; \ No newline at end of file diff --git a/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap b/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap index 51679fa90b36..3649379ec583 100644 --- a/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap +++ b/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap @@ -5823,4 +5823,4 @@ exports[`Dashboard top nav render with all components 1`] = ` -`; +`; \ No newline at end of file diff --git a/src/plugins/dev_tools/opensearch_dashboards.json b/src/plugins/dev_tools/opensearch_dashboards.json index c8f4ccc88439..9b4a419c9ff4 100644 --- a/src/plugins/dev_tools/opensearch_dashboards.json +++ b/src/plugins/dev_tools/opensearch_dashboards.json @@ -4,5 +4,6 @@ "server": false, "ui": true, "optionalPlugins": ["dataSource", "managementOverview", "dataSourceManagement"], - "requiredPlugins": ["urlForwarding"] + "requiredPlugins": ["urlForwarding"], + "requiredBundles": ["opensearchDashboardsReact"] } diff --git a/src/plugins/dev_tools/public/plugin.ts b/src/plugins/dev_tools/public/plugin.ts index 944ea6d96ba3..45f07f8d651e 100644 --- a/src/plugins/dev_tools/public/plugin.ts +++ b/src/plugins/dev_tools/public/plugin.ts @@ -28,19 +28,26 @@ * under the License. */ +import React from 'react'; 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'; import { DataSourceManagementPluginSetup } from 'src/plugins/data_source_management/public'; import { DataSourcePluginSetup } from 'src/plugins/data_source/public'; -import { AppNavLinkStatus, DEFAULT_APP_CATEGORIES } from '../../../core/public'; +import { + AppNavLinkStatus, + DEFAULT_APP_CATEGORIES, + RightNavigationOrder, + RightNavigationButton, +} from '../../../core/public'; import { UrlForwardingSetup } from '../../url_forwarding/public'; import { CreateDevToolArgs, DevToolApp, createDevToolApp } from './dev_tool'; import './index.scss'; import { ManagementOverViewPluginSetup } from '../../management_overview/public'; +import { toMountPoint } from '../../opensearch_dashboards_react/public'; export interface DevToolsSetupDependencies { urlForwarding: UrlForwardingSetup; @@ -75,12 +82,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', @@ -99,7 +108,7 @@ export class DevToolsPlugin implements Plugin { }); managementOverview?.register({ - id: 'dev_tools', + id: this.id, title: this.title, description: i18n.translate('devTools.devToolsDescription', { defaultMessage: @@ -125,10 +134,22 @@ 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.registerRight({ + order: RightNavigationOrder.DevTool, + mount: toMountPoint( + React.createElement(RightNavigationButton, { + appId: this.id, + iconType: 'consoleApp', + title: this.title, + application: core.application, + http: core.http, + }) + ), + }); } public stop() {}