diff --git a/changelogs/fragments/7401.yml b/changelogs/fragments/7401.yml
new file mode 100644
index 000000000000..5aebdb156692
--- /dev/null
+++ b/changelogs/fragments/7401.yml
@@ -0,0 +1,2 @@
+fix:
+- Make breadcrumb of 4 new added applications comply with BrowserRouter. ([#7401](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7401))
\ No newline at end of file
diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav_group_enabled.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav_group_enabled.test.tsx.snap
index 1bd0458ef1b5..4201e5146669 100644
--- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav_group_enabled.test.tsx.snap
+++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav_group_enabled.test.tsx.snap
@@ -1,34 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[` should hide left navigation when in home page when workspace is enabled 1`] = `
-
-`;
-
exports[` should render correctly 1`] = `
should show all use case by default and
`;
+exports[` should show all use case when current nav group is \`all\` 1`] = `
+
+`;
+
exports[` should render correctly 1`] = `
', () => {
function mockProps(
props?: Partial
& {
navGroupsMap?: Record;
+ currentNavGroupId?: string;
+ navLinks?: ChromeNavLink[];
}
): CollapsibleNavGroupEnabledProps {
- const currentNavGroup$ = new BehaviorSubject(undefined);
const navGroupsMap$ = new BehaviorSubject>({
[ALL_USE_CASE_ID]: {
...DEFAULT_NAV_GROUPS[ALL_USE_CASE_ID],
@@ -121,6 +122,9 @@ describe('', () => {
},
...props?.navGroupsMap,
});
+ const currentNavGroup$ = new BehaviorSubject(
+ props?.currentNavGroupId ? navGroupsMap$.getValue()[props.currentNavGroupId] : undefined
+ );
return {
appId$: new BehaviorSubject('test'),
basePath: mockBasePath,
@@ -146,6 +150,7 @@ describe('', () => {
baseUrl: '',
href: '',
},
+ ...(props?.navLinks || []),
]),
storage: new StubBrowserStorage(),
onIsLockedUpdate: () => {},
@@ -226,8 +231,9 @@ describe('', () => {
expect(getAllByTestId('collapsibleNavAppLink-link-in-analytics').length).toEqual(2);
});
- it('should hide left navigation when in home page when workspace is enabled', async () => {
+ it('should show all use case when current nav group is `all`', async () => {
const props = mockProps({
+ currentNavGroupId: ALL_USE_CASE_ID,
navGroupsMap: {
[DEFAULT_NAV_GROUPS.analytics.id]: {
...DEFAULT_NAV_GROUPS.analytics,
@@ -241,12 +247,45 @@ describe('', () => {
},
},
});
- props.appId$ = new BehaviorSubject('home');
- if (props.capabilities.workspaces) {
- (props.capabilities.workspaces as Record) = {};
- (props.capabilities.workspaces as Record).enabled = true;
- }
- const { container } = render();
+ const { container, getAllByTestId, getByTestId } = render(
+
+ );
+ fireEvent.click(getAllByTestId('collapsibleNavAppLink-link-in-analytics')[1]);
+ expect(getAllByTestId('collapsibleNavAppLink-link-in-analytics').length).toEqual(1);
expect(container).toMatchSnapshot();
+ fireEvent.click(getByTestId('back'));
+ expect(getAllByTestId('collapsibleNavAppLink-link-in-analytics').length).toEqual(2);
+ });
+
+ it('should not show group if the nav link is hidden', async () => {
+ const props = mockProps({
+ currentNavGroupId: ALL_USE_CASE_ID,
+ navGroupsMap: {
+ [DEFAULT_NAV_GROUPS.analytics.id]: {
+ ...DEFAULT_NAV_GROUPS.analytics,
+ navLinks: [
+ {
+ id: 'link-in-analytics-but-hidden',
+ title: 'link-in-analytics-but-hidden',
+ showInAllNavGroup: true,
+ },
+ ],
+ },
+ },
+ navLinks: [
+ {
+ id: 'link-in-analytics-but-hidden',
+ hidden: true,
+ title: 'link-in-analytics-but-hidden',
+ baseUrl: '',
+ href: '',
+ },
+ ],
+ });
+ const { queryAllByTestId } = render();
+ expect(queryAllByTestId('collapsibleNavAppLink-link-in-analytics-but-hidden').length).toEqual(
+ 0
+ );
+ expect(queryAllByTestId('collapsibleNavAppLink-link-in-all').length).toEqual(1);
});
});
diff --git a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx
index c6669a6b99f8..ea5510ad5994 100644
--- a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx
+++ b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx
@@ -177,7 +177,7 @@ export function CollapsibleNavGroupEnabled({
basePath,
id,
isLocked,
- isNavOpen: isNavOpenProps,
+ isNavOpen,
storage = window.localStorage,
onIsLockedUpdate,
closeNav,
@@ -194,7 +194,7 @@ export function CollapsibleNavGroupEnabled({
const currentNavGroup = useObservable(observables.currentNavGroup$, undefined);
const navLinksForRender: ChromeNavLink[] = useMemo(() => {
- if (currentNavGroup) {
+ if (currentNavGroup && currentNavGroup.id !== ALL_USE_CASE_ID) {
return fulfillRegistrationLinksToChromeNavLinks(
navGroupsMap[currentNavGroup.id].navLinks || [],
navLinks
@@ -241,7 +241,10 @@ export function CollapsibleNavGroupEnabled({
label: group.title,
order: group.order,
};
- const linksForAllUseCaseWithinNavGroup = group.navLinks
+ const linksForAllUseCaseWithinNavGroup = fulfillRegistrationLinksToChromeNavLinks(
+ group.navLinks,
+ navLinks
+ )
.filter((navLink) => navLink.showInAllNavGroup)
.map((navLink) => ({
...navLink,
@@ -263,18 +266,6 @@ export function CollapsibleNavGroupEnabled({
return fulfillRegistrationLinksToChromeNavLinks(navLinksForAll, navLinks);
}, [navLinks, navGroupsMap, currentNavGroup]);
- const isNavOpen = useMemo(() => {
- // For now, only home page need to always collapse left navigation
- // when workspace is enabled.
- // If there are more pages need to collapse left navigation in the future
- // need to come up with a mechanism to register.
- if (capabilities.workspaces.enabled && appId === 'home') {
- return false;
- }
-
- return isNavOpenProps;
- }, [isNavOpenProps, capabilities.workspaces.enabled, appId]);
-
const width = useMemo(() => {
if (!isNavOpen) {
return 50;
diff --git a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx
index 9e89155a8e4e..568290da727b 100644
--- a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx
+++ b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx
@@ -18,6 +18,7 @@ import { i18n } from '@osd/i18n';
import { createEuiListItem } from './nav_link';
import { NavGroupItemInMap } from '../../nav_group';
import { ChromeNavLink } from '../../nav_links';
+import { ALL_USE_CASE_ID } from '../../../../../core/utils';
export interface CollapsibleNavTopProps {
navLinks: ChromeNavLink[];
@@ -44,6 +45,7 @@ export const CollapsibleNavTop = ({
const shouldShowBackButton = useMemo(
() =>
+ currentNavGroup?.id !== ALL_USE_CASE_ID &&
!shouldShrinkNavigation &&
Object.values(navGroupsMap).filter((item) => !item.type).length > 1 &&
currentNavGroup,
diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx
index 4e3539f2e53b..bef0f152c6a4 100644
--- a/src/core/public/chrome/ui/header/header.test.tsx
+++ b/src/core/public/chrome/ui/header/header.test.tsx
@@ -187,4 +187,22 @@ describe('Header', () => {
expect(component.find('CollapsibleNavGroupEnabled').exists()).toBeTruthy();
});
+
+ it('show hide expand icon in top left navigation when workspace enabled + homepage + new navigation enabled', () => {
+ const branding = {
+ useExpandedHeader: false,
+ };
+ const props = {
+ ...mockProps(),
+ branding,
+ };
+ props.application.currentAppId$ = new BehaviorSubject('home');
+ props.application.capabilities = { ...props.application.capabilities };
+ (props.application.capabilities.workspaces as Record) = {};
+ (props.application.capabilities.workspaces as Record).enabled = true;
+
+ const component = mountWithIntl();
+
+ expect(component.find('.header__toggleNavButtonSection').exists()).toBeFalsy();
+ });
});
diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx
index dc161456ba84..adcbba00fe8c 100644
--- a/src/core/public/chrome/ui/header/header.tsx
+++ b/src/core/public/chrome/ui/header/header.tsx
@@ -131,9 +131,17 @@ export function Header({
}: HeaderProps) {
const isVisible = useObservable(observables.isVisible$, false);
const isLocked = useObservable(observables.isLocked$, false);
+ const appId = useObservable(application.currentAppId$, '');
const [isNavOpen, setIsNavOpen] = useState(false);
const sidecarConfig = useObservable(observables.sidecarConfig$, undefined);
+ /**
+ * This is a workaround on 2.16 to hide the navigation items within left navigation
+ * when user is in homepage with workspace enabled + new navigation enabled
+ */
+ const shouldHideExpandIcon =
+ navGroupEnabled && appId === 'home' && application.capabilities.workspaces.enabled;
+
const sidecarPaddingStyle = useMemo(() => {
return getOsdSidecarPaddingStyle(sidecarConfig);
}, [sidecarConfig]);
@@ -198,29 +206,31 @@ export function Header({
-
-
-
+ setIsNavOpen(!isNavOpen)}
- aria-expanded={isNavOpen}
- aria-pressed={isNavOpen}
- aria-controls={navId}
- ref={toggleCollapsibleNavRef}
+ delay="long"
+ position="bottom"
>
-
-
-
-
+ setIsNavOpen(!isNavOpen)}
+ aria-expanded={isNavOpen}
+ aria-pressed={isNavOpen}
+ aria-controls={navId}
+ ref={toggleCollapsibleNavRef}
+ >
+
+
+
+
+ )}
@@ -293,7 +303,7 @@ export function Header({
id={navId}
isLocked={isLocked}
navLinks$={observables.navLinks$}
- isNavOpen={isNavOpen}
+ isNavOpen={shouldHideExpandIcon ? false : isNavOpen}
basePath={basePath}
navigateToApp={application.navigateToApp}
navigateToUrl={application.navigateToUrl}
diff --git a/src/plugins/advanced_settings/public/plugin.ts b/src/plugins/advanced_settings/public/plugin.ts
index f2bf05da4114..17cb7508c440 100644
--- a/src/plugins/advanced_settings/public/plugin.ts
+++ b/src/plugins/advanced_settings/public/plugin.ts
@@ -35,6 +35,7 @@ import { ComponentRegistry } from './component_registry';
import { AdvancedSettingsSetup, AdvancedSettingsStart, AdvancedSettingsPluginSetup } from './types';
import { setupTopNavThemeButton } from './register_nav_control';
import { DEFAULT_NAV_GROUPS, AppNavLinkStatus, WorkspaceAvailability } from '../../../core/public';
+import { getScopedBreadcrumbs } from '../../opensearch_dashboards_react/public';
const component = new ComponentRegistry();
@@ -81,7 +82,8 @@ export class AdvancedSettingsPlugin
{
...params,
basePath: core.http.basePath.get(),
- setBreadcrumbs: coreStart.chrome.setBreadcrumbs,
+ setBreadcrumbs: (breadCrumbs) =>
+ coreStart.chrome.setBreadcrumbs(getScopedBreadcrumbs(breadCrumbs, params.history)),
wrapInPage: true,
},
component.start
diff --git a/src/plugins/data_source_management/public/plugin.ts b/src/plugins/data_source_management/public/plugin.ts
index c51be57e4bb2..c384e30089f3 100644
--- a/src/plugins/data_source_management/public/plugin.ts
+++ b/src/plugins/data_source_management/public/plugin.ts
@@ -50,6 +50,7 @@ import {
import { AccelerationDetailsFlyout } from './components/direct_query_data_sources_components/acceleration_management/acceleration_details_flyout';
import { CreateAcceleration } from './components/direct_query_data_sources_components/acceleration_creation/create/create_acceleration';
import { AssociatedObjectsDetailsFlyout } from './components/direct_query_data_sources_components/associated_object_management/associated_objects_details_flyout';
+import { getScopedBreadcrumbs } from '../../opensearch_dashboards_react/public';
export const [
getRenderAccelerationDetailsFlyout,
@@ -141,11 +142,6 @@ export class DataSourceManagementPlugin
},
});
- // when the feature flag is disabled, we don't need to register any of the mds components
- if (!this.featureFlagStatus) {
- return undefined;
- }
-
/**
* The data sources features in observability has the same name as `DSM_APP_ID`
* Add a suffix to avoid duplication
@@ -166,7 +162,8 @@ export class DataSourceManagementPlugin
{
...params,
basePath: core.http.basePath.get(),
- setBreadcrumbs: coreStart.chrome.setBreadcrumbs,
+ setBreadcrumbs: (breadCrumbs) =>
+ coreStart.chrome.setBreadcrumbs(getScopedBreadcrumbs(breadCrumbs, params.history)),
wrapInPage: true,
},
this.authMethodsRegistry,
@@ -224,6 +221,11 @@ export class DataSourceManagementPlugin
},
]);
+ // when the feature flag is disabled, we don't need to register any of the mds components
+ if (!this.featureFlagStatus) {
+ return undefined;
+ }
+
const registerAuthenticationMethod = (authMethod: AuthenticationMethod) => {
if (this.started) {
throw new Error(
diff --git a/src/plugins/index_pattern_management/public/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts
index ef462374129e..36bc22c5d659 100644
--- a/src/plugins/index_pattern_management/public/plugin.ts
+++ b/src/plugins/index_pattern_management/public/plugin.ts
@@ -47,6 +47,7 @@ import {
import { ManagementSetup } from '../../management/public';
import { DEFAULT_NAV_GROUPS, AppStatus, DEFAULT_APP_CATEGORIES } from '../../../core/public';
+import { getScopedBreadcrumbs } from '../../opensearch_dashboards_react/public';
export interface IndexPatternManagementSetupDependencies {
management: ManagementSetup;
@@ -148,7 +149,8 @@ export class IndexPatternManagementPlugin
{
...params,
basePath: core.http.basePath.get(),
- setBreadcrumbs: coreStart.chrome.setBreadcrumbs,
+ setBreadcrumbs: (breadCrumbs) =>
+ coreStart.chrome.setBreadcrumbs(getScopedBreadcrumbs(breadCrumbs, params.history)),
wrapInPage: true,
},
() => this.indexPatternManagementService.environmentService.getEnvironment().ml(),
diff --git a/src/plugins/opensearch_dashboards_react/public/react_router_navigate/index.ts b/src/plugins/opensearch_dashboards_react/public/react_router_navigate/index.ts
index 8ba6b1e2fe90..f5a3ecceac35 100644
--- a/src/plugins/opensearch_dashboards_react/public/react_router_navigate/index.ts
+++ b/src/plugins/opensearch_dashboards_react/public/react_router_navigate/index.ts
@@ -28,4 +28,8 @@
* under the License.
*/
-export { reactRouterNavigate, reactRouterOnClickHandler } from './react_router_navigate';
+export {
+ reactRouterNavigate,
+ reactRouterOnClickHandler,
+ getScopedBreadcrumbs,
+} from './react_router_navigate';
diff --git a/src/plugins/opensearch_dashboards_react/public/react_router_navigate/react_router_navigate.test.tsx b/src/plugins/opensearch_dashboards_react/public/react_router_navigate/react_router_navigate.test.tsx
new file mode 100644
index 000000000000..138e33906ac7
--- /dev/null
+++ b/src/plugins/opensearch_dashboards_react/public/react_router_navigate/react_router_navigate.test.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { scopedHistoryMock } from '../../../../core/public/mocks';
+import { getScopedBreadcrumbs } from './react_router_navigate';
+
+describe('getScopedBreadcrumbs', () => {
+ it('should return scoped bread crumbs when given an array', () => {
+ const history = scopedHistoryMock.create({
+ pathname: '/base',
+ });
+ history.createHref.mockImplementation((location) => `/base${location.pathname}`);
+ const scopedBreadcrumbs = getScopedBreadcrumbs(
+ [
+ {
+ text: 'Home',
+ href: '/',
+ },
+ {
+ text: 'Dashboard',
+ href: '/dashboard',
+ },
+ ],
+ history
+ );
+ expect(scopedBreadcrumbs[0]).toEqual(
+ expect.objectContaining({
+ href: '/base/',
+ text: 'Home',
+ })
+ );
+ expect(scopedBreadcrumbs[1]).toEqual(
+ expect.objectContaining({
+ href: '/base/dashboard',
+ text: 'Dashboard',
+ })
+ );
+ });
+});
diff --git a/src/plugins/opensearch_dashboards_react/public/react_router_navigate/react_router_navigate.tsx b/src/plugins/opensearch_dashboards_react/public/react_router_navigate/react_router_navigate.tsx
index 5e367f8cb94b..ae249bdadea9 100644
--- a/src/plugins/opensearch_dashboards_react/public/react_router_navigate/react_router_navigate.tsx
+++ b/src/plugins/opensearch_dashboards_react/public/react_router_navigate/react_router_navigate.tsx
@@ -28,7 +28,7 @@
* under the License.
*/
-import { ScopedHistory } from 'opensearch-dashboards/public';
+import { ChromeBreadcrumb, ScopedHistory } from 'opensearch-dashboards/public';
import { History } from 'history';
interface LocationObject {
@@ -79,3 +79,14 @@ export const reactRouterOnClickHandler = (
event.preventDefault();
history.push(toLocationObject(to));
};
+
+export const getScopedBreadcrumbs = (
+ crumbs: ChromeBreadcrumb[] = [],
+ appHistory: ScopedHistory
+) => {
+ const wrapBreadcrumb = (item: ChromeBreadcrumb, scopedHistory: ScopedHistory) => ({
+ ...item,
+ ...(item.href ? reactRouterNavigate(scopedHistory, item.href) : {}),
+ });
+ return crumbs.map((item) => wrapBreadcrumb(item, appHistory));
+};
diff --git a/src/plugins/saved_objects_management/public/plugin.ts b/src/plugins/saved_objects_management/public/plugin.ts
index 1247b56b5555..8529a9555681 100644
--- a/src/plugins/saved_objects_management/public/plugin.ts
+++ b/src/plugins/saved_objects_management/public/plugin.ts
@@ -66,6 +66,7 @@ import { bootstrap } from './ui_actions_bootstrap';
import { DEFAULT_NAV_GROUPS, DEFAULT_APP_CATEGORIES } from '../../../core/public';
import { RecentWork } from './management_section/recent_work';
import { HOME_CONTENT_AREAS } from '../../../plugins/home/public';
+import { getScopedBreadcrumbs } from '../../opensearch_dashboards_react/public';
export interface SavedObjectsManagementPluginSetup {
actions: SavedObjectsManagementActionServiceSetup;
@@ -173,7 +174,8 @@ export class SavedObjectsManagementPlugin
mountParams: {
...params,
basePath: core.http.basePath.get(),
- setBreadcrumbs: coreStart.chrome.setBreadcrumbs,
+ setBreadcrumbs: (breadCrumbs) =>
+ coreStart.chrome.setBreadcrumbs(getScopedBreadcrumbs(breadCrumbs, params.history)),
wrapInPage: true,
},
dataSourceEnabled: !!dataSource,
diff --git a/src/plugins/workspace/public/plugin.test.ts b/src/plugins/workspace/public/plugin.test.ts
index 00f0f302ef8c..dc601137789b 100644
--- a/src/plugins/workspace/public/plugin.test.ts
+++ b/src/plugins/workspace/public/plugin.test.ts
@@ -186,7 +186,7 @@ describe('Workspace plugin', () => {
expect(setupMock.application.register).toHaveBeenCalledWith(
expect.objectContaining({
id: 'workspace_detail',
- navLinkStatus: AppNavLinkStatus.visible,
+ navLinkStatus: AppNavLinkStatus.hidden,
})
);
diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts
index 5452bdd7f2fd..dd4dc78fdf2c 100644
--- a/src/plugins/workspace/public/plugin.ts
+++ b/src/plugins/workspace/public/plugin.ts
@@ -115,6 +115,12 @@ export class WorkspacePlugin
if (app.id === 'home' && isAllUseCase) {
return { navLinkStatus: AppNavLinkStatus.hidden };
}
+
+ // show the overview page in all use case
+ if (app.id === WORKSPACE_DETAIL_APP_ID && isAllUseCase) {
+ return { navLinkStatus: AppNavLinkStatus.visible };
+ }
+
if (isAppAccessibleInWorkspace(app, currentWorkspace, registeredUseCases)) {
return;
}
@@ -334,9 +340,7 @@ export class WorkspacePlugin
title: i18n.translate('workspace.settings.workspaceDetail', {
defaultMessage: 'Workspace Detail',
}),
- navLinkStatus: core.chrome.navGroup.getNavGroupEnabled()
- ? AppNavLinkStatus.visible
- : AppNavLinkStatus.hidden,
+ navLinkStatus: AppNavLinkStatus.hidden,
async mount(params: AppMountParameters) {
const { renderDetailApp } = await import('./application');
return mountWorkspaceApp(params, renderDetailApp);