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() {}