mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Serverless nav] Handle groups and vertical space (#169251)
This commit is contained in:
parent
3156d8b9e3
commit
33fe17e56b
18 changed files with 690 additions and 336 deletions
|
@ -43,4 +43,5 @@ export type {
|
|||
NodeDefinition,
|
||||
NodeDefinitionWithChildren,
|
||||
NodeRenderAs,
|
||||
EuiThemeSize,
|
||||
} from './src';
|
||||
|
|
|
@ -42,4 +42,5 @@ export type {
|
|||
NodeDefinition,
|
||||
NodeDefinitionWithChildren,
|
||||
RenderAs as NodeRenderAs,
|
||||
EuiThemeSize,
|
||||
} from './project_navigation';
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import type { ComponentType } from 'react';
|
||||
import type { Location } from 'history';
|
||||
import { EuiAccordionProps, IconType } from '@elastic/eui';
|
||||
import type { EuiAccordionProps, EuiThemeSizes, IconType } from '@elastic/eui';
|
||||
import type { AppId as DevToolsApp, DeepLinkId as DevToolsLink } from '@kbn/deeplinks-devtools';
|
||||
import type {
|
||||
AppId as AnalyticsApp,
|
||||
|
@ -53,6 +53,8 @@ export type SideNavNodeStatus = 'hidden' | 'visible';
|
|||
|
||||
export type RenderAs = 'block' | 'accordion' | 'panelOpener' | 'item';
|
||||
|
||||
export type EuiThemeSize = Exclude<typeof EuiThemeSizes[number], 'base' | 'xxs' | 'xxxl' | 'xxxxl'>;
|
||||
|
||||
export type GetIsActiveFn = (params: {
|
||||
/** The current path name including the basePath + hash value but **without** any query params */
|
||||
pathNameSerialized: string;
|
||||
|
@ -93,16 +95,15 @@ interface NodeDefinitionBase {
|
|||
* Optional function to get the active state. This function is called whenever the location changes.
|
||||
*/
|
||||
getIsActive?: GetIsActiveFn;
|
||||
/**
|
||||
* Add vertical space before this node
|
||||
*/
|
||||
spaceBefore?: EuiThemeSize | null;
|
||||
/**
|
||||
* ----------------------------------------------------------------------------------------------
|
||||
* ------------------------------- GROUP NODES ONLY PROPS ---------------------------------------
|
||||
* ----------------------------------------------------------------------------------------------
|
||||
*/
|
||||
/**
|
||||
* ["group" nodes only] Optional flag to indicate if the node must be treated as a group title.
|
||||
* Can not be used with `children`
|
||||
*/
|
||||
isGroupTitle?: boolean;
|
||||
/**
|
||||
* ["group" nodes only] Property to indicate how the group should be rendered.
|
||||
* - Accordion: wraps the items in an EuiAccordion
|
||||
|
|
|
@ -19,6 +19,7 @@ export const defaultNavigation: AnalyticsNodeDefinition = {
|
|||
defaultMessage: 'Data exploration',
|
||||
}),
|
||||
icon: 'stats',
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
link: 'discover',
|
||||
|
|
|
@ -19,6 +19,7 @@ export const defaultNavigation: DevToolsNodeDefinition = {
|
|||
}),
|
||||
id: 'rootNav:devtools',
|
||||
icon: 'editorCodeBlock',
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
link: 'dev_tools:console',
|
||||
|
|
|
@ -27,6 +27,7 @@ export const defaultNavigation: ManagementNodeDefinition = {
|
|||
defaultMessage: 'Management',
|
||||
}),
|
||||
icon: 'gear',
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
link: 'monitoring',
|
||||
|
@ -36,6 +37,7 @@ export const defaultNavigation: ManagementNodeDefinition = {
|
|||
title: i18n.translate('defaultNavigation.management.integrationManagement', {
|
||||
defaultMessage: 'Integration management',
|
||||
}),
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
link: 'integrations',
|
||||
|
@ -53,12 +55,14 @@ export const defaultNavigation: ManagementNodeDefinition = {
|
|||
title: i18n.translate('defaultNavigation.management.stackManagement', {
|
||||
defaultMessage: 'Stack management',
|
||||
}),
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
id: 'ingest',
|
||||
title: i18n.translate('defaultNavigation.management.ingest', {
|
||||
defaultMessage: 'Ingest',
|
||||
}),
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
link: 'management:ingest_pipelines',
|
||||
|
@ -73,6 +77,7 @@ export const defaultNavigation: ManagementNodeDefinition = {
|
|||
title: i18n.translate('defaultNavigation.management.stackManagementData', {
|
||||
defaultMessage: 'Data',
|
||||
}),
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
link: 'management:index_management',
|
||||
|
@ -87,6 +92,7 @@ export const defaultNavigation: ManagementNodeDefinition = {
|
|||
title: i18n.translate('defaultNavigation.management.alertAndInsights', {
|
||||
defaultMessage: 'Alerts and insights',
|
||||
}),
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
// Rules
|
||||
|
@ -108,6 +114,7 @@ export const defaultNavigation: ManagementNodeDefinition = {
|
|||
{
|
||||
id: 'kibana',
|
||||
title: 'Kibana',
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
link: 'management:dataViews',
|
||||
|
|
|
@ -38,6 +38,7 @@ export const defaultNavigation: MlNodeDefinition = {
|
|||
defaultMessage: 'Anomaly Detection',
|
||||
}),
|
||||
id: 'anomaly_detection',
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
title: i18n.translate('defaultNavigation.ml.jobs', {
|
||||
|
@ -61,6 +62,7 @@ export const defaultNavigation: MlNodeDefinition = {
|
|||
title: i18n.translate('defaultNavigation.ml.dataFrameAnalytics', {
|
||||
defaultMessage: 'Data Frame Analytics',
|
||||
}),
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
title: 'Jobs',
|
||||
|
@ -79,6 +81,7 @@ export const defaultNavigation: MlNodeDefinition = {
|
|||
title: i18n.translate('defaultNavigation.ml.modelManagement', {
|
||||
defaultMessage: 'Model Management',
|
||||
}),
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
link: 'ml:nodesOverview',
|
||||
|
@ -93,6 +96,7 @@ export const defaultNavigation: MlNodeDefinition = {
|
|||
title: i18n.translate('defaultNavigation.ml.dataVisualizer', {
|
||||
defaultMessage: 'Data Visualizer',
|
||||
}),
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
title: i18n.translate('defaultNavigation.ml.file', {
|
||||
|
@ -119,6 +123,7 @@ export const defaultNavigation: MlNodeDefinition = {
|
|||
title: i18n.translate('defaultNavigation.ml.aiopsLabs', {
|
||||
defaultMessage: 'AIOps labs',
|
||||
}),
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
link: 'ml:logRateAnalysis',
|
||||
|
|
|
@ -143,6 +143,7 @@ Array [
|
|||
"path": Array [
|
||||
"rootNav:analytics",
|
||||
],
|
||||
"renderAs": "accordion",
|
||||
"sideNavStatus": "visible",
|
||||
"title": "Data exploration",
|
||||
"type": "navGroup",
|
||||
|
@ -285,6 +286,7 @@ Array [
|
|||
"rootNav:ml",
|
||||
"anomaly_detection",
|
||||
],
|
||||
"renderAs": "accordion",
|
||||
"sideNavStatus": "visible",
|
||||
"title": "Anomaly Detection",
|
||||
},
|
||||
|
@ -363,6 +365,7 @@ Array [
|
|||
"rootNav:ml",
|
||||
"data_frame_analytics",
|
||||
],
|
||||
"renderAs": "accordion",
|
||||
"sideNavStatus": "visible",
|
||||
"title": "Data Frame Analytics",
|
||||
},
|
||||
|
@ -420,6 +423,7 @@ Array [
|
|||
"rootNav:ml",
|
||||
"model_management",
|
||||
],
|
||||
"renderAs": "accordion",
|
||||
"sideNavStatus": "visible",
|
||||
"title": "Model Management",
|
||||
},
|
||||
|
@ -498,6 +502,7 @@ Array [
|
|||
"rootNav:ml",
|
||||
"data_visualizer",
|
||||
],
|
||||
"renderAs": "accordion",
|
||||
"sideNavStatus": "visible",
|
||||
"title": "Data Visualizer",
|
||||
},
|
||||
|
@ -576,6 +581,7 @@ Array [
|
|||
"rootNav:ml",
|
||||
"aiops_labs",
|
||||
],
|
||||
"renderAs": "accordion",
|
||||
"sideNavStatus": "visible",
|
||||
"title": "AIOps labs",
|
||||
},
|
||||
|
|
|
@ -15,6 +15,8 @@ import { render } from '@testing-library/react';
|
|||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { BehaviorSubject, of, type Observable } from 'rxjs';
|
||||
import { EuiThemeProvider } from '@elastic/eui';
|
||||
|
||||
import { getServicesMock } from '../../../mocks/src/jest';
|
||||
import { NavigationProvider } from '../../services';
|
||||
import { Navigation } from './navigation';
|
||||
|
@ -38,20 +40,32 @@ describe('<Navigation />', () => {
|
|||
const onProjectNavigationChange = jest.fn();
|
||||
|
||||
const { findByTestId } = render(
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<Navigation>
|
||||
<Navigation.Group id="group1" defaultIsCollapsed={false}>
|
||||
<Navigation.Item id="item1" title="Item 1" href="https://foo" />
|
||||
<Navigation.Item id="item2" title="Item 2" href="https://foo" />
|
||||
<Navigation.Group id="group1A" title="Group1A" defaultIsCollapsed={false}>
|
||||
<Navigation.Item id="item1" title="Group 1A Item 1" href="https://foo" />
|
||||
<Navigation.Group id="group1A_1" title="Group1A_1" defaultIsCollapsed={false}>
|
||||
<Navigation.Item id="item1" title="Group 1A_1 Item 1" href="https://foo" />
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<Navigation>
|
||||
<Navigation.Group id="group1" renderAs="accordion" defaultIsCollapsed={false}>
|
||||
<Navigation.Item id="item1" title="Item 1" href="https://foo" />
|
||||
<Navigation.Item id="item2" title="Item 2" href="https://foo" />
|
||||
<Navigation.Group
|
||||
id="group1A"
|
||||
renderAs="accordion"
|
||||
title="Group1A"
|
||||
defaultIsCollapsed={false}
|
||||
>
|
||||
<Navigation.Item id="item1" title="Group 1A Item 1" href="https://foo" />
|
||||
<Navigation.Group
|
||||
id="group1A_1"
|
||||
renderAs="accordion"
|
||||
title="Group1A_1"
|
||||
defaultIsCollapsed={false}
|
||||
>
|
||||
<Navigation.Item id="item1" title="Group 1A_1 Item 1" href="https://foo" />
|
||||
</Navigation.Group>
|
||||
</Navigation.Group>
|
||||
</Navigation.Group>
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
|
@ -152,6 +166,7 @@ describe('<Navigation />', () => {
|
|||
"group1A",
|
||||
"group1A_1",
|
||||
],
|
||||
"renderAs": "accordion",
|
||||
"sideNavStatus": "visible",
|
||||
"title": "Group1A_1",
|
||||
},
|
||||
|
@ -165,6 +180,7 @@ describe('<Navigation />', () => {
|
|||
"group1",
|
||||
"group1A",
|
||||
],
|
||||
"renderAs": "accordion",
|
||||
"sideNavStatus": "visible",
|
||||
"title": "Group1A",
|
||||
},
|
||||
|
@ -177,6 +193,7 @@ describe('<Navigation />', () => {
|
|||
"path": Array [
|
||||
"group1",
|
||||
],
|
||||
"renderAs": "accordion",
|
||||
"sideNavStatus": "visible",
|
||||
"title": "",
|
||||
},
|
||||
|
@ -198,23 +215,25 @@ describe('<Navigation />', () => {
|
|||
const onProjectNavigationChange = jest.fn();
|
||||
|
||||
render(
|
||||
<NavigationProvider
|
||||
{...services}
|
||||
navLinks$={navLinks$}
|
||||
onProjectNavigationChange={onProjectNavigationChange}
|
||||
>
|
||||
<Navigation>
|
||||
<Navigation.Group id="root">
|
||||
<Navigation.Group id="group1">
|
||||
{/* Title from deeplink */}
|
||||
<Navigation.Item<any> id="item1" link="item1" />
|
||||
<Navigation.Item<any> id="item2" link="item1" title="Overwrite deeplink title" />
|
||||
<Navigation.Item id="item3" title="Title in props" />
|
||||
<Navigation.Item id="item4">Title in children</Navigation.Item>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider
|
||||
{...services}
|
||||
navLinks$={navLinks$}
|
||||
onProjectNavigationChange={onProjectNavigationChange}
|
||||
>
|
||||
<Navigation>
|
||||
<Navigation.Group id="root">
|
||||
<Navigation.Group id="group1">
|
||||
{/* Title from deeplink */}
|
||||
<Navigation.Item<any> id="item1" link="item1" />
|
||||
<Navigation.Item<any> id="item2" link="item1" title="Overwrite deeplink title" />
|
||||
<Navigation.Item id="item3" title="Title in props" />
|
||||
<Navigation.Item id="item4">Title in children</Navigation.Item>
|
||||
</Navigation.Group>
|
||||
</Navigation.Group>
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
|
@ -347,22 +366,28 @@ describe('<Navigation />', () => {
|
|||
const onProjectNavigationChange = jest.fn();
|
||||
|
||||
const { findByTestId } = render(
|
||||
<NavigationProvider
|
||||
{...services}
|
||||
navLinks$={navLinks$}
|
||||
onProjectNavigationChange={onProjectNavigationChange}
|
||||
>
|
||||
<Navigation>
|
||||
<Navigation.Group id="root" defaultIsCollapsed={false}>
|
||||
<Navigation.Group id="group1" defaultIsCollapsed={false}>
|
||||
{/* Title from deeplink */}
|
||||
<Navigation.Item<any> id="item1" link="item1" />
|
||||
{/* Should not appear */}
|
||||
<Navigation.Item<any> id="unknownLink" link="unknown" title="Should NOT be there" />
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider
|
||||
{...services}
|
||||
navLinks$={navLinks$}
|
||||
onProjectNavigationChange={onProjectNavigationChange}
|
||||
>
|
||||
<Navigation>
|
||||
<Navigation.Group id="root" defaultIsCollapsed={false}>
|
||||
<Navigation.Group id="group1" defaultIsCollapsed={false}>
|
||||
{/* Title from deeplink */}
|
||||
<Navigation.Item<any> id="item1" link="item1" />
|
||||
{/* Should not appear */}
|
||||
<Navigation.Item<any>
|
||||
id="unknownLink"
|
||||
link="unknown"
|
||||
title="Should NOT be there"
|
||||
/>
|
||||
</Navigation.Group>
|
||||
</Navigation.Group>
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
|
@ -446,22 +471,24 @@ describe('<Navigation />', () => {
|
|||
const onProjectNavigationChange = jest.fn();
|
||||
|
||||
const { queryByTestId } = render(
|
||||
<NavigationProvider
|
||||
{...services}
|
||||
navLinks$={navLinks$}
|
||||
onProjectNavigationChange={onProjectNavigationChange}
|
||||
>
|
||||
<Navigation>
|
||||
<Navigation.Group id="root" defaultIsCollapsed={false}>
|
||||
<Navigation.Group id="group1" defaultIsCollapsed={false}>
|
||||
<Navigation.Item<any> id="item1" link="notRegistered" />
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider
|
||||
{...services}
|
||||
navLinks$={navLinks$}
|
||||
onProjectNavigationChange={onProjectNavigationChange}
|
||||
>
|
||||
<Navigation>
|
||||
<Navigation.Group id="root" defaultIsCollapsed={false}>
|
||||
<Navigation.Group id="group1" defaultIsCollapsed={false}>
|
||||
<Navigation.Item<any> id="item1" link="notRegistered" />
|
||||
</Navigation.Group>
|
||||
<Navigation.Group id="group2" defaultIsCollapsed={false}>
|
||||
<Navigation.Item<any> id="item1" link="item1" />
|
||||
</Navigation.Group>
|
||||
</Navigation.Group>
|
||||
<Navigation.Group id="group2" defaultIsCollapsed={false}>
|
||||
<Navigation.Item<any> id="item1" link="item1" />
|
||||
</Navigation.Group>
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
|
@ -550,14 +577,16 @@ describe('<Navigation />', () => {
|
|||
const onProjectNavigationChange = jest.fn();
|
||||
|
||||
render(
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<Navigation>
|
||||
<Navigation.Group preset="analytics" />
|
||||
<Navigation.Group preset="ml" />
|
||||
<Navigation.Group preset="devtools" />
|
||||
<Navigation.Group preset="management" />
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<Navigation>
|
||||
<Navigation.Group preset="analytics" />
|
||||
<Navigation.Group preset="ml" />
|
||||
<Navigation.Group preset="devtools" />
|
||||
<Navigation.Group preset="management" />
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
|
@ -581,15 +610,17 @@ describe('<Navigation />', () => {
|
|||
]);
|
||||
|
||||
const { findByTestId } = render(
|
||||
<NavigationProvider {...services} recentlyAccessed$={recentlyAccessed$}>
|
||||
<Navigation>
|
||||
<Navigation.Group id="root">
|
||||
<Navigation.Group id="group1">
|
||||
<Navigation.RecentlyAccessed />
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider {...services} recentlyAccessed$={recentlyAccessed$}>
|
||||
<Navigation>
|
||||
<Navigation.Group id="root">
|
||||
<Navigation.Group id="group1">
|
||||
<Navigation.RecentlyAccessed />
|
||||
</Navigation.Group>
|
||||
</Navigation.Group>
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
|
@ -606,13 +637,15 @@ describe('<Navigation />', () => {
|
|||
const onProjectNavigationChange = jest.fn();
|
||||
|
||||
render(
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<Navigation>
|
||||
<Navigation.Group id="group1">
|
||||
<Navigation.Item id="item1" title="Item 1" href="https://example.com" />
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<Navigation>
|
||||
<Navigation.Group id="group1">
|
||||
<Navigation.Item id="item1" title="Item 1" href="https://example.com" />
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
|
@ -670,13 +703,15 @@ describe('<Navigation />', () => {
|
|||
|
||||
const expectToThrow = () => {
|
||||
render(
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<Navigation>
|
||||
<Navigation.Group id="group1">
|
||||
<Navigation.Item id="item1" title="Item 1" href="../dashboards" />
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<Navigation>
|
||||
<Navigation.Group id="group1">
|
||||
<Navigation.Item id="item1" title="Item 1" href="../dashboards" />
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -722,14 +757,16 @@ describe('<Navigation />', () => {
|
|||
const getActiveNodes$ = () => activeNodes$;
|
||||
|
||||
const { findByTestId } = render(
|
||||
<NavigationProvider {...services} activeNodes$={getActiveNodes$()} navLinks$={navLinks$}>
|
||||
<Navigation>
|
||||
<Navigation.Group id="group1">
|
||||
<Navigation.Item<any> link="item1" title="Item 1" />
|
||||
<Navigation.Item<any> link="item2" title="Item 2" />
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider {...services} activeNodes$={getActiveNodes$()} navLinks$={navLinks$}>
|
||||
<Navigation>
|
||||
<Navigation.Group id="group1">
|
||||
<Navigation.Item<any> link="item1" title="Item 1" />
|
||||
<Navigation.Item<any> link="item2" title="Item 2" />
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
expect((await findByTestId(/nav-item-group1.item1/)).dataset.testSubj).toMatch(
|
||||
|
@ -791,24 +828,26 @@ describe('<Navigation />', () => {
|
|||
};
|
||||
|
||||
const { findByTestId } = render(
|
||||
<NavigationProvider
|
||||
{...services}
|
||||
activeNodes$={getActiveNodes$()}
|
||||
navLinks$={navLinks$}
|
||||
onProjectNavigationChange={onProjectNavigationChange}
|
||||
>
|
||||
<Navigation>
|
||||
<Navigation.Group id="group1">
|
||||
<Navigation.Item<any>
|
||||
link="item1"
|
||||
title="Item 1"
|
||||
getIsActive={() => {
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider
|
||||
{...services}
|
||||
activeNodes$={getActiveNodes$()}
|
||||
navLinks$={navLinks$}
|
||||
onProjectNavigationChange={onProjectNavigationChange}
|
||||
>
|
||||
<Navigation>
|
||||
<Navigation.Group id="group1">
|
||||
<Navigation.Item<any>
|
||||
link="item1"
|
||||
title="Item 1"
|
||||
getIsActive={() => {
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
jest.advanceTimersByTime(SET_NAVIGATION_DELAY);
|
||||
|
@ -824,15 +863,17 @@ describe('<Navigation />', () => {
|
|||
const onProjectNavigationChange = jest.fn();
|
||||
|
||||
const { findByTestId } = render(
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<Navigation>
|
||||
<Navigation.Group id="group1" defaultIsCollapsed={false}>
|
||||
<Navigation.Item id="cloudLink1" cloudLink="userAndRoles" />
|
||||
<Navigation.Item id="cloudLink2" cloudLink="performance" />
|
||||
<Navigation.Item id="cloudLink3" cloudLink="billingAndSub" />
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<Navigation>
|
||||
<Navigation.Group id="group1" defaultIsCollapsed={false}>
|
||||
<Navigation.Item id="cloudLink1" cloudLink="userAndRoles" />
|
||||
<Navigation.Item id="cloudLink2" cloudLink="performance" />
|
||||
<Navigation.Item id="cloudLink3" cloudLink="billingAndSub" />
|
||||
</Navigation.Group>
|
||||
</Navigation>
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
expect(await findByTestId(/nav-item-group1.cloudLink1/)).toBeVisible();
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
type EuiThemeComputed,
|
||||
useEuiTheme,
|
||||
transparentize,
|
||||
useIsWithinMinBreakpoint,
|
||||
} from '@elastic/eui';
|
||||
import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
|
||||
import type { NavigateToUrlFn } from '../../../types/internal';
|
||||
|
@ -53,6 +54,8 @@ export const NavigationItemOpenPanel: FC<Props> = ({ item, navigateToUrl }: Prop
|
|||
const { title, deepLink, isActive, children } = item;
|
||||
const id = nodePathToString(item);
|
||||
const href = deepLink?.url ?? item.href;
|
||||
const isNotMobile = useIsWithinMinBreakpoint('s');
|
||||
const isIconVisible = isNotMobile && !!children && children.length > 0;
|
||||
|
||||
const itemClassNames = classNames(
|
||||
'sideNavItem',
|
||||
|
@ -73,12 +76,16 @@ export const NavigationItemOpenPanel: FC<Props> = ({ item, navigateToUrl }: Prop
|
|||
);
|
||||
|
||||
const onIconClick = useCallback(() => {
|
||||
openPanel(item);
|
||||
}, [openPanel, item]);
|
||||
if (selectedNode?.id === item.id) {
|
||||
closePanel();
|
||||
} else {
|
||||
openPanel(item);
|
||||
}
|
||||
}, [openPanel, closePanel, item, selectedNode]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="xs">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexItem style={{ flexBasis: isIconVisible ? '80%' : '100%' }}>
|
||||
<EuiListGroup gutterSize="none">
|
||||
<EuiListGroupItem
|
||||
label={title}
|
||||
|
@ -92,8 +99,8 @@ export const NavigationItemOpenPanel: FC<Props> = ({ item, navigateToUrl }: Prop
|
|||
/>
|
||||
</EuiListGroup>
|
||||
</EuiFlexItem>
|
||||
{!!children && children.length > 0 && (
|
||||
<EuiFlexItem grow={0}>
|
||||
{isIconVisible && (
|
||||
<EuiFlexItem grow={0} style={{ flexBasis: '15%' }}>
|
||||
<EuiButtonIcon
|
||||
display={nodePathToString(selectedNode) === id ? 'base' : 'empty'}
|
||||
size="s"
|
||||
|
@ -104,7 +111,7 @@ export const NavigationItemOpenPanel: FC<Props> = ({ item, navigateToUrl }: Prop
|
|||
aria-label={i18n.translate('sharedUXPackages.chrome.sideNavigation.togglePanel', {
|
||||
defaultMessage: 'Toggle panel navigation',
|
||||
})}
|
||||
data-test-subj={`solutionSideNavItemButton-${id}`}
|
||||
data-test-subj={`panelOpener-${id}`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
|
|
@ -9,14 +9,16 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import {
|
||||
EuiAccordionProps,
|
||||
EuiCollapsibleNavItem,
|
||||
EuiCollapsibleNavItemProps,
|
||||
EuiCollapsibleNavSubItemProps,
|
||||
EuiTitle,
|
||||
EuiCollapsibleNavItem,
|
||||
EuiSpacer,
|
||||
type EuiAccordionProps,
|
||||
type EuiCollapsibleNavItemProps,
|
||||
type EuiCollapsibleNavSubItemProps,
|
||||
} from '@elastic/eui';
|
||||
import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
|
||||
import classnames from 'classnames';
|
||||
import type { EuiThemeSize, RenderAs } from '@kbn/core-chrome-browser/src/project_navigation';
|
||||
|
||||
import type { NavigateToUrlFn } from '../../../types/internal';
|
||||
import { useNavigation as useServices } from '../../services';
|
||||
|
@ -29,6 +31,8 @@ const nodeHasLink = (navNode: ChromeProjectNavigationNode) =>
|
|||
|
||||
const nodeHasChildren = (navNode: ChromeProjectNavigationNode) => Boolean(navNode.children?.length);
|
||||
|
||||
const DEFAULT_SPACE_BETWEEN_LEVEL_1_GROUPS: EuiThemeSize = 'm';
|
||||
|
||||
/**
|
||||
* Predicate to determine if a node should be visible in the main side nav.
|
||||
* If it is not visible it will be filtered out and not rendered.
|
||||
|
@ -36,11 +40,6 @@ const nodeHasChildren = (navNode: ChromeProjectNavigationNode) => Boolean(navNod
|
|||
const itemIsVisible = (item: ChromeProjectNavigationNode) => {
|
||||
if (item.sideNavStatus === 'hidden') return false;
|
||||
|
||||
const isGroupTitle = Boolean(item.isGroupTitle);
|
||||
if (isGroupTitle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nodeHasLink(item)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -52,6 +51,12 @@ const itemIsVisible = (item: ChromeProjectNavigationNode) => {
|
|||
return false;
|
||||
};
|
||||
|
||||
const getRenderAs = (navNode: ChromeProjectNavigationNode): RenderAs => {
|
||||
if (navNode.renderAs) return navNode.renderAs;
|
||||
if (!navNode.children) return 'item';
|
||||
return 'block';
|
||||
};
|
||||
|
||||
const filterChildren = (
|
||||
children?: ChromeProjectNavigationNode[]
|
||||
): ChromeProjectNavigationNode[] | undefined => {
|
||||
|
@ -60,13 +65,15 @@ const filterChildren = (
|
|||
};
|
||||
|
||||
const serializeNavNode = (navNode: ChromeProjectNavigationNode) => {
|
||||
const serialized = {
|
||||
const serialized: ChromeProjectNavigationNode = {
|
||||
...navNode,
|
||||
id: nodePathToString(navNode),
|
||||
children: filterChildren(navNode.children),
|
||||
href: getNavigationNodeHref(navNode),
|
||||
};
|
||||
|
||||
serialized.renderAs = getRenderAs(serialized);
|
||||
|
||||
return {
|
||||
navNode: serialized,
|
||||
hasChildren: nodeHasChildren(serialized),
|
||||
|
@ -83,6 +90,53 @@ const isEuiCollapsibleNavItemProps = (
|
|||
);
|
||||
};
|
||||
|
||||
const renderBlockTitle: (
|
||||
{ title }: ChromeProjectNavigationNode,
|
||||
{ spaceBefore }: { spaceBefore: EuiThemeSize | null }
|
||||
) => Required<EuiCollapsibleNavSubItemProps>['renderItem'] =
|
||||
({ title }, { spaceBefore }) =>
|
||||
() =>
|
||||
(
|
||||
<EuiTitle
|
||||
size="xxxs"
|
||||
className="eui-textTruncate"
|
||||
css={({ euiTheme }: any) => {
|
||||
return {
|
||||
marginTop: spaceBefore ? euiTheme.size[spaceBefore] : undefined,
|
||||
// marginTop: euiTheme.size.base,
|
||||
paddingBlock: euiTheme.size.xs,
|
||||
paddingInline: euiTheme.size.s,
|
||||
};
|
||||
}}
|
||||
>
|
||||
<div>{title}</div>
|
||||
</EuiTitle>
|
||||
);
|
||||
|
||||
const renderGroup = (
|
||||
navGroup: ChromeProjectNavigationNode,
|
||||
groupItems: Array<EuiCollapsibleNavItemProps | EuiCollapsibleNavSubItemProps>,
|
||||
{ spaceBefore = DEFAULT_SPACE_BETWEEN_LEVEL_1_GROUPS }: { spaceBefore?: EuiThemeSize | null } = {}
|
||||
): Required<EuiCollapsibleNavItemProps>['items'] => {
|
||||
let itemPrepend: EuiCollapsibleNavItemProps | EuiCollapsibleNavSubItemProps | null = null;
|
||||
|
||||
if (!!navGroup.title) {
|
||||
itemPrepend = {
|
||||
renderItem: renderBlockTitle(navGroup, { spaceBefore }),
|
||||
};
|
||||
} else if (spaceBefore) {
|
||||
itemPrepend = {
|
||||
renderItem: () => <EuiSpacer size={spaceBefore} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (!itemPrepend) {
|
||||
return groupItems;
|
||||
}
|
||||
|
||||
return [itemPrepend, ...groupItems];
|
||||
};
|
||||
|
||||
// Generate the EuiCollapsible props for both the root component (EuiCollapsibleNavItem) and its
|
||||
// "items" props. Both are compatible with the exception of "renderItem" which is only used for
|
||||
// sub items.
|
||||
|
@ -101,10 +155,22 @@ const nodeToEuiCollapsibleNavProps = (
|
|||
isSideNavCollapsed: boolean;
|
||||
treeDepth: number;
|
||||
}
|
||||
): { props: EuiCollapsibleNavItemProps | EuiCollapsibleNavSubItemProps; isVisible: boolean } => {
|
||||
): {
|
||||
items: Array<EuiCollapsibleNavItemProps | EuiCollapsibleNavSubItemProps>;
|
||||
isVisible: boolean;
|
||||
} => {
|
||||
const { navNode, isItem, hasChildren, hasLink } = serializeNavNode(_navNode);
|
||||
|
||||
const { id, title, href, icon, renderAs, isActive, deepLink, isGroupTitle } = navNode;
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
href,
|
||||
icon,
|
||||
renderAs,
|
||||
isActive,
|
||||
deepLink,
|
||||
spaceBefore: _spaceBefore,
|
||||
} = navNode;
|
||||
const isExternal = Boolean(href) && isAbsoluteLink(href!);
|
||||
const isSelected = hasChildren ? false : isActive;
|
||||
const dataTestSubj = classnames(`nav-item`, `nav-item-${id}`, {
|
||||
|
@ -113,34 +179,25 @@ const nodeToEuiCollapsibleNavProps = (
|
|||
[`nav-item-isActive`]: isSelected,
|
||||
});
|
||||
|
||||
// Note: this can be replaced with an `isGroup` API or whatever you prefer
|
||||
// Could also probably be pulled out to a separate component vs inlined
|
||||
if (isGroupTitle) {
|
||||
const props: EuiCollapsibleNavSubItemProps = {
|
||||
renderItem: () => (
|
||||
<EuiTitle
|
||||
size="xxxs"
|
||||
className="eui-textTruncate"
|
||||
css={({ euiTheme }: any) => ({
|
||||
marginTop: euiTheme.size.base,
|
||||
paddingBlock: euiTheme.size.xs,
|
||||
paddingInline: euiTheme.size.s,
|
||||
})}
|
||||
>
|
||||
<div id={id} data-test-subj={dataTestSubj}>
|
||||
{title}
|
||||
</div>
|
||||
</EuiTitle>
|
||||
),
|
||||
};
|
||||
return { props, isVisible: true };
|
||||
let spaceBefore = _spaceBefore;
|
||||
if (spaceBefore === undefined && treeDepth === 1 && hasChildren) {
|
||||
// For groups at level 1 that don't have a space specified we default to add a "m"
|
||||
// space. For all other groups, unless specified, there is no vertical space.
|
||||
spaceBefore = DEFAULT_SPACE_BETWEEN_LEVEL_1_GROUPS;
|
||||
}
|
||||
|
||||
if (renderAs === 'panelOpener') {
|
||||
const props: EuiCollapsibleNavSubItemProps = {
|
||||
renderItem: () => <NavigationItemOpenPanel item={navNode} navigateToUrl={navigateToUrl} />,
|
||||
};
|
||||
return { props, isVisible: true };
|
||||
const items: EuiCollapsibleNavSubItemProps[] = [
|
||||
{
|
||||
renderItem: () => <NavigationItemOpenPanel item={navNode} navigateToUrl={navigateToUrl} />,
|
||||
},
|
||||
];
|
||||
if (spaceBefore) {
|
||||
items.unshift({
|
||||
renderItem: () => <EuiSpacer size={spaceBefore!} />,
|
||||
});
|
||||
}
|
||||
return { items, isVisible: true };
|
||||
}
|
||||
|
||||
const onClick = (e: React.MouseEvent) => {
|
||||
|
@ -152,7 +209,7 @@ const nodeToEuiCollapsibleNavProps = (
|
|||
}
|
||||
};
|
||||
|
||||
const items: EuiCollapsibleNavItemProps['items'] = isItem
|
||||
const subItems: EuiCollapsibleNavItemProps['items'] | undefined = isItem
|
||||
? undefined
|
||||
: navNode.children
|
||||
?.map((child) =>
|
||||
|
@ -165,7 +222,11 @@ const nodeToEuiCollapsibleNavProps = (
|
|||
})
|
||||
)
|
||||
.filter(({ isVisible }) => isVisible)
|
||||
.map((res) => res.props);
|
||||
.map((res) => {
|
||||
const itemsFlattened: EuiCollapsibleNavItemProps['items'] = res.items.flat();
|
||||
return itemsFlattened;
|
||||
})
|
||||
.flat();
|
||||
|
||||
const linkProps: EuiCollapsibleNavItemProps['linkProps'] | undefined = hasLink
|
||||
? {
|
||||
|
@ -190,22 +251,43 @@ const nodeToEuiCollapsibleNavProps = (
|
|||
...navNode.accordionProps,
|
||||
};
|
||||
|
||||
const props: EuiCollapsibleNavItemProps = {
|
||||
id,
|
||||
title,
|
||||
isSelected,
|
||||
accordionProps,
|
||||
linkProps,
|
||||
onClick,
|
||||
href,
|
||||
items,
|
||||
['data-test-subj']: dataTestSubj,
|
||||
icon,
|
||||
iconProps: { size: treeDepth === 0 ? 'm' : 's' },
|
||||
};
|
||||
if (renderAs === 'block' && treeDepth > 0 && subItems) {
|
||||
// Render as a group block (bold title + list of links underneath)
|
||||
return {
|
||||
items: [...renderGroup(navNode, subItems, { spaceBefore: spaceBefore ?? null })],
|
||||
isVisible: subItems.length > 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Render as an accordion or a link (handled by EUI) depending if
|
||||
// "items" is undefined or not. If it is undefined --> a link, otherwise an
|
||||
// accordion is rendered.
|
||||
const items: Array<EuiCollapsibleNavItemProps | EuiCollapsibleNavSubItemProps> = [
|
||||
{
|
||||
id,
|
||||
title,
|
||||
isSelected,
|
||||
accordionProps,
|
||||
linkProps,
|
||||
onClick,
|
||||
href,
|
||||
items: subItems,
|
||||
['data-test-subj']: dataTestSubj,
|
||||
icon,
|
||||
iconProps: { size: treeDepth === 0 ? 'm' : 's' },
|
||||
},
|
||||
];
|
||||
|
||||
const hasVisibleChildren = (items?.length ?? 0) > 0;
|
||||
return { props, isVisible: isItem || hasVisibleChildren };
|
||||
const isVisible = isItem || hasVisibleChildren;
|
||||
|
||||
if (isVisible && spaceBefore) {
|
||||
items.unshift({
|
||||
renderItem: () => <EuiSpacer size={spaceBefore!} />,
|
||||
});
|
||||
}
|
||||
|
||||
return { items, isVisible };
|
||||
};
|
||||
|
||||
interface Props {
|
||||
|
@ -216,7 +298,7 @@ export const NavigationSectionUI: FC<Props> = ({ navNode }) => {
|
|||
const { navigateToUrl, isSideNavCollapsed } = useServices();
|
||||
const { open: openPanel, close: closePanel } = usePanel();
|
||||
|
||||
const { props, isVisible } = nodeToEuiCollapsibleNavProps(navNode, {
|
||||
const { items, isVisible } = nodeToEuiCollapsibleNavProps(navNode, {
|
||||
navigateToUrl,
|
||||
openPanel,
|
||||
closePanel,
|
||||
|
@ -224,6 +306,8 @@ export const NavigationSectionUI: FC<Props> = ({ navNode }) => {
|
|||
treeDepth: 0,
|
||||
});
|
||||
|
||||
const [props] = items;
|
||||
|
||||
if (!isEuiCollapsibleNavItemProps(props)) {
|
||||
throw new Error(`Invalid EuiCollapsibleNavItem props for node ${props.id}`);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
|
||||
import { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
|
||||
import React, { Fragment, type FC } from 'react';
|
||||
|
||||
|
@ -69,13 +69,10 @@ export const DefaultContent: FC<Props> = ({ selectedNode }) => {
|
|||
(child) => child.sideNavStatus !== 'hidden'
|
||||
);
|
||||
const serializedChildren = serializeChildren({ ...selectedNode, children: filteredChildren });
|
||||
const totalChildren = serializedChildren?.length ?? 0;
|
||||
const firstChildIsGroup = !!serializedChildren?.[0]?.children;
|
||||
const firstGroupTitle = firstChildIsGroup && serializedChildren?.[0]?.title;
|
||||
const firstGroupHasTitle = !!firstGroupTitle;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="m" alignItems="flexStart">
|
||||
{/* Panel title */}
|
||||
<EuiFlexItem>
|
||||
{typeof selectedNode.title === 'string' ? (
|
||||
<EuiTitle size="xxs">
|
||||
|
@ -86,10 +83,9 @@ export const DefaultContent: FC<Props> = ({ selectedNode }) => {
|
|||
)}
|
||||
</EuiFlexItem>
|
||||
|
||||
{/* Panel navigation */}
|
||||
<EuiFlexItem style={{ width: '100%' }}>
|
||||
<>
|
||||
{firstGroupHasTitle && <EuiSpacer size="l" />}
|
||||
|
||||
{serializedChildren && (
|
||||
<>
|
||||
{serializedChildren.map((child, i) => {
|
||||
|
@ -103,9 +99,6 @@ export const DefaultContent: FC<Props> = ({ selectedNode }) => {
|
|||
isFirstInList={i === 0}
|
||||
hasHorizontalRuleBefore={hasHorizontalRuleBefore}
|
||||
/>
|
||||
{i < totalChildren - 1 && (
|
||||
<EuiSpacer size={child.appendHorizontalRule ? 'm' : 'l'} />
|
||||
)}
|
||||
</Fragment>
|
||||
) : (
|
||||
<PanelNavItem key={child.id} item={child} />
|
||||
|
|
|
@ -22,7 +22,7 @@ import { getNavPanelStyles, getPanelWrapperStyles } from './styles';
|
|||
|
||||
export const NavigationPanel: FC = () => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { isOpen, close, getContent } = usePanel();
|
||||
const { isOpen, close, getContent, selectedNode } = usePanel();
|
||||
|
||||
// ESC key closes PanelNav
|
||||
const onKeyDown = useCallback(
|
||||
|
@ -34,9 +34,15 @@ export const NavigationPanel: FC = () => {
|
|||
[close]
|
||||
);
|
||||
|
||||
const onOutsideClick = useCallback(() => {
|
||||
close();
|
||||
}, [close]);
|
||||
const onOutsideClick = useCallback(
|
||||
({ target }: Event) => {
|
||||
// Only close if we are not clicking on the currently selected nav node
|
||||
if ((target as HTMLButtonElement).dataset.testSubj !== `panelOpener-${selectedNode?.id}`) {
|
||||
close();
|
||||
}
|
||||
},
|
||||
[close, selectedNode]
|
||||
);
|
||||
|
||||
const panelWrapperClasses = getPanelWrapperStyles();
|
||||
const sideNavPanelStyles = getNavPanelStyles(euiTheme);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import React, { FC, Fragment, useCallback } from 'react';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import {
|
||||
EuiListGroup,
|
||||
EuiTitle,
|
||||
|
@ -19,7 +19,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
|
||||
import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
|
||||
import { PanelNavItem } from './panel_nav_item';
|
||||
|
||||
const accordionButtonClassName = 'sideNavPanelAccordion__button';
|
||||
|
@ -42,6 +42,16 @@ const getClassnames = (euiTheme: EuiThemeComputed<{}>) => ({
|
|||
`,
|
||||
});
|
||||
|
||||
const someChildIsVisible = (children: ChromeProjectNavigationNode[]) => {
|
||||
return children.some((child) => {
|
||||
if (child.renderAs === 'item') return true;
|
||||
if (child.children) {
|
||||
return child.children.every(({ sideNavStatus }) => sideNavStatus !== 'hidden');
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
interface Props {
|
||||
navNode: ChromeProjectNavigationNode;
|
||||
/** Flag to indicate if the group is the first in the list of groups when looping */
|
||||
|
@ -52,15 +62,24 @@ interface Props {
|
|||
|
||||
export const PanelGroup: FC<Props> = ({ navNode, isFirstInList, hasHorizontalRuleBefore }) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { id, title, appendHorizontalRule } = navNode;
|
||||
const { id, title, appendHorizontalRule, spaceBefore: _spaceBefore } = navNode;
|
||||
const filteredChildren = navNode.children?.filter((child) => child.sideNavStatus !== 'hidden');
|
||||
const totalChildren = filteredChildren?.length ?? 0;
|
||||
const classNames = getClassnames(euiTheme);
|
||||
const hasTitle = !!title && title !== '';
|
||||
const removePaddingTop = !hasTitle && !isFirstInList;
|
||||
const someChildIsGroup = filteredChildren?.some((child) => !!child.children);
|
||||
const firstChildIsGroup = !!filteredChildren?.[0]?.children;
|
||||
|
||||
let spaceBefore = _spaceBefore;
|
||||
if (spaceBefore === undefined) {
|
||||
if (!hasTitle && isFirstInList) {
|
||||
// If the first group has no title, we don't add any space.
|
||||
spaceBefore = null;
|
||||
} else {
|
||||
spaceBefore = hasHorizontalRuleBefore ? 'm' : 'l';
|
||||
}
|
||||
}
|
||||
|
||||
const renderChildren = useCallback(() => {
|
||||
if (!filteredChildren) return null;
|
||||
|
||||
|
@ -69,21 +88,19 @@ export const PanelGroup: FC<Props> = ({ navNode, isFirstInList, hasHorizontalRul
|
|||
return isItem ? (
|
||||
<PanelNavItem key={item.id} item={item} />
|
||||
) : (
|
||||
<Fragment key={item.id}>
|
||||
<PanelGroup navNode={item} />
|
||||
{i < totalChildren - 1 && <EuiSpacer />}
|
||||
</Fragment>
|
||||
<PanelGroup navNode={item} key={item.id} />
|
||||
);
|
||||
});
|
||||
}, [filteredChildren, totalChildren]);
|
||||
}, [filteredChildren]);
|
||||
|
||||
if (!filteredChildren?.length) {
|
||||
if (!filteredChildren?.length || !someChildIsVisible(filteredChildren)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (navNode.renderAs === 'accordion') {
|
||||
return (
|
||||
<>
|
||||
{spaceBefore !== null && <EuiSpacer size={spaceBefore} />}
|
||||
<EuiAccordion
|
||||
id={id}
|
||||
buttonContent={title}
|
||||
|
@ -91,7 +108,7 @@ export const PanelGroup: FC<Props> = ({ navNode, isFirstInList, hasHorizontalRul
|
|||
buttonClassName={accordionButtonClassName}
|
||||
>
|
||||
<>
|
||||
<EuiSpacer size={firstChildIsGroup ? 'l' : 's'} />
|
||||
{!firstChildIsGroup && <EuiSpacer size="s" />}
|
||||
{renderChildren()}
|
||||
</>
|
||||
</EuiAccordion>
|
||||
|
@ -102,9 +119,10 @@ export const PanelGroup: FC<Props> = ({ navNode, isFirstInList, hasHorizontalRul
|
|||
|
||||
return (
|
||||
<>
|
||||
{spaceBefore !== null && <EuiSpacer size={spaceBefore} />}
|
||||
{hasTitle && (
|
||||
<EuiTitle size="xxxs" className={classNames.title}>
|
||||
<h2>{navNode.title}</h2>
|
||||
<h2>{title}</h2>
|
||||
</EuiTitle>
|
||||
)}
|
||||
<EuiListGroup
|
||||
|
|
|
@ -14,6 +14,7 @@ import type {
|
|||
ChromeProjectNavigation,
|
||||
ChromeProjectNavigationNode,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
import { EuiThemeProvider } from '@elastic/eui';
|
||||
|
||||
import { getServicesMock } from '../../mocks/src/jest';
|
||||
import { NavigationProvider } from '../services';
|
||||
|
@ -81,9 +82,11 @@ describe('<DefaultNavigation />', () => {
|
|||
];
|
||||
|
||||
const { findAllByTestId } = render(
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<DefaultNavigation navigationTree={{ body: navigationBody }} />
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<DefaultNavigation navigationTree={{ body: navigationBody }} />
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
|
@ -252,13 +255,15 @@ describe('<DefaultNavigation />', () => {
|
|||
];
|
||||
|
||||
render(
|
||||
<NavigationProvider
|
||||
{...services}
|
||||
navLinks$={navLinks$}
|
||||
onProjectNavigationChange={onProjectNavigationChange}
|
||||
>
|
||||
<DefaultNavigation navigationTree={{ body: navigationBody }} />
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider
|
||||
{...services}
|
||||
navLinks$={navLinks$}
|
||||
onProjectNavigationChange={onProjectNavigationChange}
|
||||
>
|
||||
<DefaultNavigation navigationTree={{ body: navigationBody }} />
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
|
@ -494,9 +499,11 @@ describe('<DefaultNavigation />', () => {
|
|||
];
|
||||
|
||||
render(
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<DefaultNavigation navigationTree={{ body: navigationBody }} />
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<DefaultNavigation navigationTree={{ body: navigationBody }} />
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
|
@ -590,9 +597,11 @@ describe('<DefaultNavigation />', () => {
|
|||
|
||||
const expectToThrow = () => {
|
||||
render(
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<DefaultNavigation navigationTree={{ body: navigationBody }} />
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider {...services} onProjectNavigationChange={onProjectNavigationChange}>
|
||||
<DefaultNavigation navigationTree={{ body: navigationBody }} />
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -615,9 +624,11 @@ describe('<DefaultNavigation />', () => {
|
|||
];
|
||||
|
||||
const { findByTestId } = render(
|
||||
<NavigationProvider {...services} recentlyAccessed$={recentlyAccessed$}>
|
||||
<DefaultNavigation navigationTree={{ body: navigationBody }} />
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider {...services} recentlyAccessed$={recentlyAccessed$}>
|
||||
<DefaultNavigation navigationTree={{ body: navigationBody }} />
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
|
@ -683,9 +694,11 @@ describe('<DefaultNavigation />', () => {
|
|||
const getActiveNodes$ = () => activeNodes$;
|
||||
|
||||
const { findByTestId } = render(
|
||||
<NavigationProvider {...services} navLinks$={navLinks$} activeNodes$={getActiveNodes$()}>
|
||||
<DefaultNavigation navigationTree={{ body: navigationBody }} />
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider {...services} navLinks$={navLinks$} activeNodes$={getActiveNodes$()}>
|
||||
<DefaultNavigation navigationTree={{ body: navigationBody }} />
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
|
@ -741,14 +754,16 @@ describe('<DefaultNavigation />', () => {
|
|||
};
|
||||
|
||||
const { findByTestId } = render(
|
||||
<NavigationProvider
|
||||
{...services}
|
||||
navLinks$={navLinks$}
|
||||
activeNodes$={getActiveNodes$()}
|
||||
onProjectNavigationChange={onProjectNavigationChange}
|
||||
>
|
||||
<DefaultNavigation navigationTree={{ body: navigationBody }} />
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider
|
||||
{...services}
|
||||
navLinks$={navLinks$}
|
||||
activeNodes$={getActiveNodes$()}
|
||||
onProjectNavigationChange={onProjectNavigationChange}
|
||||
>
|
||||
<DefaultNavigation navigationTree={{ body: navigationBody }} />
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
|
@ -804,13 +819,15 @@ describe('<DefaultNavigation />', () => {
|
|||
];
|
||||
|
||||
render(
|
||||
<NavigationProvider
|
||||
{...services}
|
||||
navLinks$={navLinks$}
|
||||
onProjectNavigationChange={onProjectNavigationChange}
|
||||
>
|
||||
<DefaultNavigation projectNavigationTree={projectNavigationTree} />
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider
|
||||
{...services}
|
||||
navLinks$={navLinks$}
|
||||
onProjectNavigationChange={onProjectNavigationChange}
|
||||
>
|
||||
<DefaultNavigation projectNavigationTree={projectNavigationTree} />
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
|
@ -833,9 +850,11 @@ describe('<DefaultNavigation />', () => {
|
|||
describe('cloud links', () => {
|
||||
test('render the cloud link', async () => {
|
||||
const { findByTestId } = render(
|
||||
<NavigationProvider {...services}>
|
||||
<DefaultNavigation projectNavigationTree={[]} />
|
||||
</NavigationProvider>
|
||||
<EuiThemeProvider>
|
||||
<NavigationProvider {...services}>
|
||||
<DefaultNavigation projectNavigationTree={[]} />
|
||||
</NavigationProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
|
|
@ -201,6 +201,154 @@ export const SimpleObjectDefinition = (args: NavigationServices) => {
|
|||
);
|
||||
};
|
||||
|
||||
const groupExamplesDefinition: ProjectNavigationDefinition<any> = {
|
||||
navigationTree: {
|
||||
body: [
|
||||
// My custom project
|
||||
{
|
||||
type: 'navGroup',
|
||||
id: 'example_projet',
|
||||
title: 'Example project',
|
||||
icon: 'logoObservability',
|
||||
defaultIsCollapsed: false,
|
||||
children: [
|
||||
{
|
||||
title: 'Block group',
|
||||
children: [
|
||||
{
|
||||
id: 'item1',
|
||||
link: 'item1',
|
||||
title: 'Item 1',
|
||||
},
|
||||
{
|
||||
id: 'item2',
|
||||
link: 'item1',
|
||||
title: 'Item 2',
|
||||
},
|
||||
{
|
||||
id: 'item3',
|
||||
link: 'item1',
|
||||
title: 'Item 3',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Accordion group',
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
id: 'item1',
|
||||
link: 'item1',
|
||||
title: 'Item 1',
|
||||
},
|
||||
{
|
||||
id: 'item2',
|
||||
link: 'item1',
|
||||
title: 'Item 2',
|
||||
},
|
||||
{
|
||||
id: 'item3',
|
||||
link: 'item1',
|
||||
title: 'Item 3',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
id: 'item1',
|
||||
link: 'item1',
|
||||
title: 'Block group',
|
||||
},
|
||||
{
|
||||
id: 'item2',
|
||||
link: 'item1',
|
||||
title: 'without',
|
||||
},
|
||||
{
|
||||
id: 'item3',
|
||||
link: 'item1',
|
||||
title: 'title',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'group:settings',
|
||||
link: 'item1',
|
||||
title: 'Panel group',
|
||||
renderAs: 'panelOpener',
|
||||
children: [
|
||||
{
|
||||
title: 'Group 1',
|
||||
children: [
|
||||
{
|
||||
link: 'group:settings.logs',
|
||||
title: 'Logs',
|
||||
},
|
||||
{
|
||||
link: 'group:settings.signals',
|
||||
title: 'Signals',
|
||||
},
|
||||
{
|
||||
id: 'group:settings.signals-2',
|
||||
link: 'group:settings.signals',
|
||||
title: 'Signals - should NOT appear',
|
||||
sideNavStatus: 'hidden', // Should not appear
|
||||
},
|
||||
{
|
||||
link: 'group:settings.tracing',
|
||||
title: 'Tracing',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'group.nestedGroup',
|
||||
link: 'group:settings.tracing',
|
||||
title: 'Group 2',
|
||||
children: [
|
||||
{
|
||||
id: 'item1',
|
||||
link: 'group:settings.signals',
|
||||
title: 'Some link title',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
footer: [
|
||||
{
|
||||
type: 'navGroup',
|
||||
...getPresets('devtools'),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const GroupsExamples = (args: NavigationServices) => {
|
||||
const services = storybookMock.getServices({
|
||||
...args,
|
||||
navLinks$: of([...navLinksMock, ...deepLinks]),
|
||||
onProjectNavigationChange: (updated) => {
|
||||
action('Update chrome navigation')(JSON.stringify(updated, null, 2));
|
||||
},
|
||||
recentlyAccessed$: of([
|
||||
{ label: 'This is an example', link: '/app/example/39859', id: '39850' },
|
||||
{ label: 'Another example', link: '/app/example/5235', id: '5235' },
|
||||
]),
|
||||
});
|
||||
|
||||
return (
|
||||
<NavigationWrapper>
|
||||
<NavigationProvider {...services}>
|
||||
<DefaultNavigation {...groupExamplesDefinition} />
|
||||
</NavigationProvider>
|
||||
</NavigationWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const navigationDefinition: ProjectNavigationDefinition<any> = {
|
||||
navigationTree: {
|
||||
body: [
|
||||
|
@ -216,6 +364,26 @@ const navigationDefinition: ProjectNavigationDefinition<any> = {
|
|||
link: 'item1',
|
||||
title: 'Get started',
|
||||
},
|
||||
{
|
||||
title: 'Group 1',
|
||||
children: [
|
||||
{
|
||||
id: 'item1',
|
||||
link: 'item1',
|
||||
title: 'Item 1',
|
||||
},
|
||||
{
|
||||
id: 'item2',
|
||||
link: 'item1',
|
||||
title: 'Item 2',
|
||||
},
|
||||
{
|
||||
id: 'item3',
|
||||
link: 'item1',
|
||||
title: 'Item 3',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
link: 'item2',
|
||||
title: 'Alerts',
|
||||
|
@ -683,7 +851,7 @@ const navigationDefinitionWithPanel: ProjectNavigationDefinition<any> = {
|
|||
id: 'root',
|
||||
children: [
|
||||
{
|
||||
title: 'Should act as item 1',
|
||||
title: 'Group renders as "item" (1)',
|
||||
link: 'item1',
|
||||
renderAs: 'item',
|
||||
children: [
|
||||
|
@ -699,7 +867,7 @@ const navigationDefinitionWithPanel: ProjectNavigationDefinition<any> = {
|
|||
},
|
||||
{
|
||||
link: 'group:settings.logs',
|
||||
title: 'Normal item',
|
||||
title: 'Item 2',
|
||||
},
|
||||
{
|
||||
link: 'group:settings.logs2',
|
||||
|
@ -723,25 +891,7 @@ const navigationDefinitionWithPanel: ProjectNavigationDefinition<any> = {
|
|||
],
|
||||
},
|
||||
{
|
||||
title: 'Should act as item 2',
|
||||
renderAs: 'item', // This group renders as a normal item
|
||||
children: [
|
||||
{
|
||||
link: 'group:settings.logs',
|
||||
title: 'Logs',
|
||||
},
|
||||
{
|
||||
link: 'group:settings.signals',
|
||||
title: 'Signals',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
title: 'Another group as Item',
|
||||
title: 'Group renders as "item" (2)',
|
||||
id: 'group2.renderAsItem',
|
||||
renderAs: 'item',
|
||||
children: [
|
||||
|
@ -797,7 +947,7 @@ const navigationDefinitionWithPanel: ProjectNavigationDefinition<any> = {
|
|||
children: [
|
||||
{
|
||||
id: 'group2-B',
|
||||
title: 'Group 2 (render as Item)',
|
||||
title: 'Group renders as "item" (3)',
|
||||
renderAs: 'item', // This group renders as a normal item
|
||||
children: [
|
||||
{
|
||||
|
@ -995,6 +1145,19 @@ export const WithUIComponents = (args: NavigationServices) => {
|
|||
</Navigation.Item>
|
||||
<Navigation.Item id="item4" title="External link" href="https://elastic.co" />
|
||||
|
||||
<Navigation.Group<any> id="group:block" title="This is a block group">
|
||||
<Navigation.Group id="group1">
|
||||
<Navigation.Item<any> link="group:settings.logs" title="Logs" />
|
||||
<Navigation.Item<any> link="group:settings.signals" title="Signals" withBadge />
|
||||
<Navigation.Item<any> link="group:settings.tracing" title="Tracing" />
|
||||
</Navigation.Group>
|
||||
<Navigation.Group id="group2" title="Nested group" renderAs="accordion">
|
||||
<Navigation.Item<any> link="group:settings.logs" title="Logs" />
|
||||
<Navigation.Item<any> link="group:settings.signals" title="Signals" />
|
||||
<Navigation.Item<any> link="group:settings.tracing" title="Tracing" />
|
||||
</Navigation.Group>
|
||||
</Navigation.Group>
|
||||
|
||||
<Navigation.Group<any>
|
||||
id="group:openPanel"
|
||||
link="item1"
|
||||
|
|
|
@ -79,9 +79,11 @@ const navigationTree: NavigationTreeDefinition = {
|
|||
{
|
||||
id: 'aiops',
|
||||
title: 'AIOps',
|
||||
renderAs: 'accordion',
|
||||
accordionProps: {
|
||||
arrowProps: { css: { display: 'none' } },
|
||||
},
|
||||
spaceBefore: null,
|
||||
children: [
|
||||
{
|
||||
title: i18n.translate('xpack.serverlessObservability.nav.ml.jobs', {
|
||||
|
@ -115,15 +117,12 @@ const navigationTree: NavigationTreeDefinition = {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'groups-spacer-1',
|
||||
isGroupTitle: true,
|
||||
},
|
||||
{
|
||||
id: 'apm',
|
||||
title: i18n.translate('xpack.serverlessObservability.nav.applications', {
|
||||
defaultMessage: 'Applications',
|
||||
}),
|
||||
renderAs: 'accordion',
|
||||
accordionProps: {
|
||||
arrowProps: { css: { display: 'none' } },
|
||||
},
|
||||
|
@ -154,6 +153,7 @@ const navigationTree: NavigationTreeDefinition = {
|
|||
title: i18n.translate('xpack.serverlessObservability.nav.infrastructure', {
|
||||
defaultMessage: 'Infrastructure',
|
||||
}),
|
||||
renderAs: 'accordion',
|
||||
accordionProps: {
|
||||
arrowProps: { css: { display: 'none' } },
|
||||
},
|
||||
|
@ -172,10 +172,6 @@ const navigationTree: NavigationTreeDefinition = {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'groups-spacer-2',
|
||||
isGroupTitle: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -186,7 +182,6 @@ const navigationTree: NavigationTreeDefinition = {
|
|||
defaultMessage: 'Get Started',
|
||||
}),
|
||||
link: 'observabilityOnboarding',
|
||||
isGroupTitle: true,
|
||||
icon: 'launch',
|
||||
},
|
||||
{
|
||||
|
|
|
@ -45,35 +45,36 @@ const navigationTree: NavigationTreeDefinition = {
|
|||
title: i18n.translate('xpack.serverlessSearch.nav.explore', {
|
||||
defaultMessage: 'Explore',
|
||||
}),
|
||||
isGroupTitle: true,
|
||||
},
|
||||
{
|
||||
link: 'discover',
|
||||
},
|
||||
{
|
||||
link: 'dashboards',
|
||||
getIsActive: ({ pathNameSerialized, prepend }) => {
|
||||
return pathNameSerialized.startsWith(prepend('/app/dashboards'));
|
||||
},
|
||||
},
|
||||
{
|
||||
link: 'visualize',
|
||||
title: i18n.translate('xpack.serverlessSearch.nav.visualize', {
|
||||
defaultMessage: 'Visualizations',
|
||||
}),
|
||||
getIsActive: ({ pathNameSerialized, prepend }) => {
|
||||
return (
|
||||
pathNameSerialized.startsWith(prepend('/app/visualize')) ||
|
||||
pathNameSerialized.startsWith(prepend('/app/lens')) ||
|
||||
pathNameSerialized.startsWith(prepend('/app/maps'))
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
link: 'management:triggersActions',
|
||||
title: i18n.translate('xpack.serverlessSearch.nav.alerts', {
|
||||
defaultMessage: 'Alerts',
|
||||
}),
|
||||
children: [
|
||||
{
|
||||
link: 'discover',
|
||||
},
|
||||
{
|
||||
link: 'dashboards',
|
||||
getIsActive: ({ pathNameSerialized, prepend }) => {
|
||||
return pathNameSerialized.startsWith(prepend('/app/dashboards'));
|
||||
},
|
||||
},
|
||||
{
|
||||
link: 'visualize',
|
||||
title: i18n.translate('xpack.serverlessSearch.nav.visualize', {
|
||||
defaultMessage: 'Visualizations',
|
||||
}),
|
||||
getIsActive: ({ pathNameSerialized, prepend }) => {
|
||||
return (
|
||||
pathNameSerialized.startsWith(prepend('/app/visualize')) ||
|
||||
pathNameSerialized.startsWith(prepend('/app/lens')) ||
|
||||
pathNameSerialized.startsWith(prepend('/app/maps'))
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
link: 'management:triggersActions',
|
||||
title: i18n.translate('xpack.serverlessSearch.nav.alerts', {
|
||||
defaultMessage: 'Alerts',
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -81,33 +82,37 @@ const navigationTree: NavigationTreeDefinition = {
|
|||
title: i18n.translate('xpack.serverlessSearch.nav.content', {
|
||||
defaultMessage: 'Content',
|
||||
}),
|
||||
isGroupTitle: true,
|
||||
children: [
|
||||
{
|
||||
title: i18n.translate('xpack.serverlessSearch.nav.content.indices', {
|
||||
defaultMessage: 'Index Management',
|
||||
}),
|
||||
link: 'management:index_management',
|
||||
breadcrumbStatus:
|
||||
'hidden' /* management sub-pages set their breadcrumbs themselves */,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.serverlessSearch.nav.content.pipelines', {
|
||||
defaultMessage: 'Pipelines',
|
||||
}),
|
||||
link: 'management:ingest_pipelines',
|
||||
breadcrumbStatus:
|
||||
'hidden' /* management sub-pages set their breadcrumbs themselves */,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.serverlessSearch.nav.content.indices', {
|
||||
defaultMessage: 'Index Management',
|
||||
}),
|
||||
link: 'management:index_management',
|
||||
breadcrumbStatus: 'hidden' /* management sub-pages set their breadcrumbs themselves */,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.serverlessSearch.nav.content.pipelines', {
|
||||
defaultMessage: 'Pipelines',
|
||||
}),
|
||||
link: 'management:ingest_pipelines',
|
||||
breadcrumbStatus: 'hidden' /* management sub-pages set their breadcrumbs themselves */,
|
||||
},
|
||||
|
||||
{
|
||||
id: 'security',
|
||||
title: i18n.translate('xpack.serverlessSearch.nav.security', {
|
||||
defaultMessage: 'Security',
|
||||
}),
|
||||
isGroupTitle: true,
|
||||
},
|
||||
{
|
||||
link: 'management:api_keys',
|
||||
breadcrumbStatus: 'hidden' /* management sub-pages set their breadcrumbs themselves */,
|
||||
children: [
|
||||
{
|
||||
link: 'management:api_keys',
|
||||
breadcrumbStatus:
|
||||
'hidden' /* management sub-pages set their breadcrumbs themselves */,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue