mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Improve ML serverless breadcrumbs for oblt and security (#169513)
## Summary close https://github.com/elastic/kibana/issues/167337 It introduces a new way to automatically set the deeper context breadcrumbs in serverless navigation. Instead of using the `serverless.setBreadcrumbs` for setting deeper context breadcrumbs in serverless, the project navigation service merges navigational breadcrumbs with regular chrome's breadcrumbs by deepLinkId
This commit is contained in:
parent
443cf43442
commit
ce9a765d8e
13 changed files with 266 additions and 56 deletions
|
@ -223,7 +223,12 @@ export class ChromeService {
|
|||
|
||||
const navControls = this.navControls.start();
|
||||
const navLinks = this.navLinks.start({ application, http });
|
||||
const projectNavigation = this.projectNavigation.start({ application, navLinks, http });
|
||||
const projectNavigation = this.projectNavigation.start({
|
||||
application,
|
||||
navLinks,
|
||||
http,
|
||||
chromeBreadcrumbs$: breadcrumbs$,
|
||||
});
|
||||
const recentlyAccessed = await this.recentlyAccessed.start({ http });
|
||||
const docTitle = this.docTitle.start();
|
||||
const { customBranding$ } = customBranding;
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
AppDeepLinkId,
|
||||
ChromeProjectBreadcrumb,
|
||||
ChromeProjectNavigationNode,
|
||||
ChromeSetProjectBreadcrumbsParams,
|
||||
ChromeBreadcrumb,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
import { createHomeBreadcrumb } from './home_breadcrumbs';
|
||||
|
||||
export function buildBreadcrumbs({
|
||||
homeHref,
|
||||
projectBreadcrumbs,
|
||||
activeNodes,
|
||||
chromeBreadcrumbs,
|
||||
}: {
|
||||
homeHref: string;
|
||||
projectBreadcrumbs: {
|
||||
breadcrumbs: ChromeProjectBreadcrumb[];
|
||||
params: ChromeSetProjectBreadcrumbsParams;
|
||||
};
|
||||
chromeBreadcrumbs: ChromeBreadcrumb[];
|
||||
activeNodes: ChromeProjectNavigationNode[][];
|
||||
}): ChromeProjectBreadcrumb[] {
|
||||
const homeBreadcrumb = createHomeBreadcrumb({
|
||||
homeHref,
|
||||
});
|
||||
|
||||
if (projectBreadcrumbs.params.absolute) {
|
||||
return [homeBreadcrumb, ...projectBreadcrumbs.breadcrumbs];
|
||||
}
|
||||
|
||||
// breadcrumbs take the first active path
|
||||
const activePath: ChromeProjectNavigationNode[] = activeNodes[0] ?? [];
|
||||
const navBreadcrumbPath = activePath.filter(
|
||||
(n) => Boolean(n.title) && n.breadcrumbStatus !== 'hidden'
|
||||
);
|
||||
const navBreadcrumbs = navBreadcrumbPath.map(
|
||||
(node): ChromeProjectBreadcrumb => ({
|
||||
href: node.deepLink?.url ?? node.href,
|
||||
deepLinkId: node.deepLink?.id as AppDeepLinkId,
|
||||
text: node.title,
|
||||
})
|
||||
);
|
||||
|
||||
// if there are project breadcrumbs set, use them
|
||||
if (projectBreadcrumbs.breadcrumbs.length !== 0) {
|
||||
return [homeBreadcrumb, ...navBreadcrumbs, ...projectBreadcrumbs.breadcrumbs];
|
||||
}
|
||||
|
||||
// otherwise try to merge legacy breadcrumbs with navigational project breadcrumbs using deeplinkid
|
||||
let chromeBreadcrumbStartIndex = -1;
|
||||
let navBreadcrumbEndIndex = -1;
|
||||
navBreadcrumbsLoop: for (let i = navBreadcrumbs.length - 1; i >= 0; i--) {
|
||||
if (!navBreadcrumbs[i].deepLinkId) continue;
|
||||
for (let j = 0; j < chromeBreadcrumbs.length; j++) {
|
||||
if (chromeBreadcrumbs[j].deepLinkId === navBreadcrumbs[i].deepLinkId) {
|
||||
chromeBreadcrumbStartIndex = j;
|
||||
navBreadcrumbEndIndex = i;
|
||||
break navBreadcrumbsLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chromeBreadcrumbStartIndex === -1) {
|
||||
return [homeBreadcrumb, ...navBreadcrumbs];
|
||||
} else {
|
||||
return [
|
||||
homeBreadcrumb,
|
||||
...navBreadcrumbs.slice(0, navBreadcrumbEndIndex),
|
||||
...chromeBreadcrumbs.slice(chromeBreadcrumbStartIndex),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -7,15 +7,17 @@
|
|||
*/
|
||||
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { firstValueFrom, lastValueFrom, take } from 'rxjs';
|
||||
import { firstValueFrom, lastValueFrom, take, BehaviorSubject } from 'rxjs';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
import { applicationServiceMock } from '@kbn/core-application-browser-mocks';
|
||||
import type { ChromeNavLinks } from '@kbn/core-chrome-browser';
|
||||
import type { ChromeNavLinks, ChromeBreadcrumb, AppDeepLinkId } from '@kbn/core-chrome-browser';
|
||||
import { ProjectNavigationService } from './project_navigation_service';
|
||||
|
||||
const setup = ({ locationPathName = '/' }: { locationPathName?: string } = {}) => {
|
||||
const projectNavigationService = new ProjectNavigationService();
|
||||
const history = createMemoryHistory();
|
||||
const chromeBreadcrumbs$ = new BehaviorSubject<ChromeBreadcrumb[]>([]);
|
||||
|
||||
history.replace(locationPathName);
|
||||
const projectNavigation = projectNavigationService.start({
|
||||
application: {
|
||||
|
@ -24,15 +26,18 @@ const setup = ({ locationPathName = '/' }: { locationPathName?: string } = {}) =
|
|||
},
|
||||
navLinks: {} as unknown as ChromeNavLinks,
|
||||
http: httpServiceMock.createStartContract(),
|
||||
chromeBreadcrumbs$,
|
||||
});
|
||||
|
||||
return { projectNavigation, history };
|
||||
return { projectNavigation, history, chromeBreadcrumbs$ };
|
||||
};
|
||||
|
||||
describe('breadcrumbs', () => {
|
||||
const setupWithNavTree = () => {
|
||||
const currentLocationPathName = '/foo/item1';
|
||||
const { projectNavigation, history } = setup({ locationPathName: currentLocationPathName });
|
||||
const { projectNavigation, chromeBreadcrumbs$, history } = setup({
|
||||
locationPathName: currentLocationPathName,
|
||||
});
|
||||
|
||||
const mockNavigation = {
|
||||
navigationTree: [
|
||||
|
@ -66,7 +71,7 @@ describe('breadcrumbs', () => {
|
|||
],
|
||||
};
|
||||
projectNavigation.setProjectNavigation(mockNavigation);
|
||||
return { projectNavigation, history, mockNavigation };
|
||||
return { projectNavigation, history, mockNavigation, chromeBreadcrumbs$ };
|
||||
};
|
||||
|
||||
test('should set breadcrumbs home / nav / custom', async () => {
|
||||
|
@ -89,7 +94,7 @@ describe('breadcrumbs', () => {
|
|||
"title": "Home",
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "breadcrumb-deepLinkId-navItem1",
|
||||
"deepLinkId": "navItem1",
|
||||
"href": "/foo/item1",
|
||||
"text": "Nav Item 1",
|
||||
},
|
||||
|
@ -139,6 +144,38 @@ describe('breadcrumbs', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
test('should merge nav breadcrumbs and chrome breadcrumbs', async () => {
|
||||
const { projectNavigation, chromeBreadcrumbs$ } = setupWithNavTree();
|
||||
|
||||
projectNavigation.setProjectBreadcrumbs([]);
|
||||
chromeBreadcrumbs$.next([
|
||||
{ text: 'Kibana' },
|
||||
{ deepLinkId: 'navItem1' as AppDeepLinkId, text: 'Nav Item 1 from Chrome' },
|
||||
{ text: 'Deep context from Chrome' },
|
||||
]);
|
||||
|
||||
const breadcrumbs = await firstValueFrom(projectNavigation.getProjectBreadcrumbs$());
|
||||
expect(breadcrumbs).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "breadcrumb-home",
|
||||
"href": "/",
|
||||
"text": <EuiIcon
|
||||
type="home"
|
||||
/>,
|
||||
"title": "Home",
|
||||
},
|
||||
Object {
|
||||
"deepLinkId": "navItem1",
|
||||
"text": "Nav Item 1 from Chrome",
|
||||
},
|
||||
Object {
|
||||
"text": "Deep context from Chrome",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('should reset custom breadcrumbs when active path changes', async () => {
|
||||
const { projectNavigation, history } = setupWithNavTree();
|
||||
projectNavigation.setProjectBreadcrumbs([
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
ChromeProjectNavigation,
|
||||
SideNavComponent,
|
||||
ChromeProjectBreadcrumb,
|
||||
ChromeBreadcrumb,
|
||||
ChromeSetProjectBreadcrumbsParams,
|
||||
ChromeProjectNavigationNode,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
|
@ -29,15 +30,15 @@ import {
|
|||
} from 'rxjs';
|
||||
import type { Location } from 'history';
|
||||
import deepEqual from 'react-fast-compare';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { createHomeBreadcrumb } from './home_breadcrumbs';
|
||||
import { findActiveNodes, flattenNav, stripQueryParams } from './utils';
|
||||
import { buildBreadcrumbs } from './breadcrumbs';
|
||||
|
||||
interface StartDeps {
|
||||
application: InternalApplicationStart;
|
||||
navLinks: ChromeNavLinks;
|
||||
http: HttpStart;
|
||||
chromeBreadcrumbs$: Observable<ChromeBreadcrumb[]>;
|
||||
}
|
||||
|
||||
export class ProjectNavigationService {
|
||||
|
@ -60,7 +61,7 @@ export class ProjectNavigationService {
|
|||
private http?: HttpStart;
|
||||
private unlistenHistory?: () => void;
|
||||
|
||||
public start({ application, navLinks, http }: StartDeps) {
|
||||
public start({ application, navLinks, http, chromeBreadcrumbs$ }: StartDeps) {
|
||||
this.application = application;
|
||||
this.http = http;
|
||||
this.onHistoryLocationChange(application.history.location);
|
||||
|
@ -136,33 +137,15 @@ export class ProjectNavigationService {
|
|||
this.projectBreadcrumbs$,
|
||||
this.activeNodes$,
|
||||
this.projectHome$.pipe(map((homeHref) => homeHref ?? '/')),
|
||||
chromeBreadcrumbs$,
|
||||
]).pipe(
|
||||
map(([breadcrumbs, activeNodes, homeHref]) => {
|
||||
const homeBreadcrumb = createHomeBreadcrumb({
|
||||
map(([projectBreadcrumbs, activeNodes, homeHref, chromeBreadcrumbs]) => {
|
||||
return buildBreadcrumbs({
|
||||
homeHref: this.http?.basePath.prepend?.(homeHref) ?? homeHref,
|
||||
projectBreadcrumbs,
|
||||
activeNodes,
|
||||
chromeBreadcrumbs,
|
||||
});
|
||||
|
||||
if (breadcrumbs.params.absolute) {
|
||||
return [homeBreadcrumb, ...breadcrumbs.breadcrumbs];
|
||||
} else {
|
||||
// breadcrumbs take the first active path
|
||||
const activePath: ChromeProjectNavigationNode[] = activeNodes[0] ?? [];
|
||||
const navBreadcrumbs = activePath
|
||||
.filter((n) => Boolean(n.title) && n.breadcrumbStatus !== 'hidden')
|
||||
.map(
|
||||
(node): ChromeProjectBreadcrumb => ({
|
||||
href: node.deepLink?.url ?? node.href,
|
||||
text: node.title,
|
||||
'data-test-subj': classnames({
|
||||
[`breadcrumb-deepLinkId-${node.deepLink?.id}`]: !!node.deepLink,
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const result = [homeBreadcrumb, ...navBreadcrumbs, ...breadcrumbs.breadcrumbs];
|
||||
|
||||
return result;
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
|
|
|
@ -27,13 +27,15 @@ export function HeaderBreadcrumbs({ breadcrumbs$ }: Props) {
|
|||
|
||||
crumbs = crumbs.map((breadcrumb, i) => {
|
||||
const isLast = i === breadcrumbs.length - 1;
|
||||
const { deepLinkId, ...rest } = breadcrumb;
|
||||
|
||||
return {
|
||||
...breadcrumb,
|
||||
...rest,
|
||||
href: isLast ? undefined : breadcrumb.href,
|
||||
onClick: isLast ? undefined : breadcrumb.onClick,
|
||||
'data-test-subj': classNames(
|
||||
'breadcrumb',
|
||||
deepLinkId && `breadcrumb-deepLinkId-${deepLinkId}`,
|
||||
breadcrumb['data-test-subj'],
|
||||
i === 0 && 'first',
|
||||
isLast && 'last'
|
||||
|
|
|
@ -8,9 +8,16 @@
|
|||
|
||||
import type { EuiBreadcrumb } from '@elastic/eui';
|
||||
import type { MountPoint } from '@kbn/core-mount-utils-browser';
|
||||
import type { AppDeepLinkId } from './project_navigation';
|
||||
|
||||
/** @public */
|
||||
export type ChromeBreadcrumb = EuiBreadcrumb;
|
||||
export interface ChromeBreadcrumb extends EuiBreadcrumb {
|
||||
/**
|
||||
* The deepLinkId can be used to merge the navigational breadcrumbs set via project navigation
|
||||
* with the deeper context breadcrumbs set via the `chrome.setBreadcrumbs` API.
|
||||
*/
|
||||
deepLinkId?: AppDeepLinkId;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface ChromeBreadcrumbsAppendExtension {
|
||||
|
|
|
@ -33,11 +33,14 @@ export const defaultNavigation: MlNodeDefinition = {
|
|||
{
|
||||
link: 'ml:notifications',
|
||||
},
|
||||
{
|
||||
link: 'ml:memoryUsage',
|
||||
},
|
||||
{
|
||||
title: i18n.translate('defaultNavigation.ml.anomalyDetection', {
|
||||
defaultMessage: 'Anomaly Detection',
|
||||
}),
|
||||
id: 'anomaly_detection',
|
||||
link: 'ml:anomalyDetection',
|
||||
renderAs: 'accordion',
|
||||
children: [
|
||||
{
|
||||
|
@ -45,6 +48,7 @@ export const defaultNavigation: MlNodeDefinition = {
|
|||
defaultMessage: 'Jobs',
|
||||
}),
|
||||
link: 'ml:anomalyDetection',
|
||||
breadcrumbStatus: 'hidden',
|
||||
},
|
||||
{
|
||||
link: 'ml:anomalyExplorer',
|
||||
|
@ -58,7 +62,7 @@ export const defaultNavigation: MlNodeDefinition = {
|
|||
],
|
||||
},
|
||||
{
|
||||
id: 'data_frame_analytics',
|
||||
link: 'ml:dataFrameAnalytics',
|
||||
title: i18n.translate('defaultNavigation.ml.dataFrameAnalytics', {
|
||||
defaultMessage: 'Data Frame Analytics',
|
||||
}),
|
||||
|
@ -67,6 +71,7 @@ export const defaultNavigation: MlNodeDefinition = {
|
|||
{
|
||||
title: 'Jobs',
|
||||
link: 'ml:dataFrameAnalytics',
|
||||
breadcrumbStatus: 'hidden',
|
||||
},
|
||||
{
|
||||
link: 'ml:resultExplorer',
|
||||
|
@ -109,12 +114,21 @@ export const defaultNavigation: MlNodeDefinition = {
|
|||
defaultMessage: 'Data view',
|
||||
}),
|
||||
link: 'ml:indexDataVisualizer',
|
||||
getIsActive: ({ pathNameSerialized, prepend }) => {
|
||||
return (
|
||||
pathNameSerialized.includes(prepend('/app/ml/datavisualizer')) ||
|
||||
pathNameSerialized.includes(prepend('/app/ml/jobs/new_job/datavisualizer'))
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18n.translate('defaultNavigation.ml.dataComparison', {
|
||||
defaultMessage: 'Data drift',
|
||||
}),
|
||||
link: 'ml:dataDrift',
|
||||
getIsActive: ({ pathNameSerialized, prepend }) => {
|
||||
return pathNameSerialized.includes(prepend('/app/ml/data_drift'));
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -127,12 +141,21 @@ export const defaultNavigation: MlNodeDefinition = {
|
|||
children: [
|
||||
{
|
||||
link: 'ml:logRateAnalysis',
|
||||
getIsActive: ({ pathNameSerialized, prepend }) => {
|
||||
return pathNameSerialized.includes(prepend('/app/ml/aiops/log_rate_analysis'));
|
||||
},
|
||||
},
|
||||
{
|
||||
link: 'ml:logPatternAnalysis',
|
||||
getIsActive: ({ pathNameSerialized, prepend }) => {
|
||||
return pathNameSerialized.includes(prepend('/app/ml/aiops/log_categorization'));
|
||||
},
|
||||
},
|
||||
{
|
||||
link: 'ml:changePointDetections',
|
||||
getIsActive: ({ pathNameSerialized, prepend }) => {
|
||||
return pathNameSerialized.includes(prepend('/app/ml/aiops/change_point_detection'));
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -190,9 +190,30 @@ Array [
|
|||
"sideNavStatus": "visible",
|
||||
"title": "Deeplink ml:notifications",
|
||||
},
|
||||
Object {
|
||||
"children": undefined,
|
||||
"deepLink": Object {
|
||||
"baseUrl": "/mocked",
|
||||
"href": "http://mocked/ml:memoryUsage",
|
||||
"id": "ml:memoryUsage",
|
||||
"title": "Deeplink ml:memoryUsage",
|
||||
"url": "/mocked/ml:memoryUsage",
|
||||
},
|
||||
"href": undefined,
|
||||
"id": "ml:memoryUsage",
|
||||
"isActive": false,
|
||||
"isGroup": false,
|
||||
"path": Array [
|
||||
"rootNav:ml",
|
||||
"ml:memoryUsage",
|
||||
],
|
||||
"sideNavStatus": "visible",
|
||||
"title": "Deeplink ml:memoryUsage",
|
||||
},
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"breadcrumbStatus": "hidden",
|
||||
"children": undefined,
|
||||
"deepLink": Object {
|
||||
"baseUrl": "/mocked",
|
||||
|
@ -207,7 +228,7 @@ Array [
|
|||
"isGroup": false,
|
||||
"path": Array [
|
||||
"rootNav:ml",
|
||||
"anomaly_detection",
|
||||
"ml:anomalyDetection",
|
||||
"ml:anomalyDetection",
|
||||
],
|
||||
"sideNavStatus": "visible",
|
||||
|
@ -228,7 +249,7 @@ Array [
|
|||
"isGroup": false,
|
||||
"path": Array [
|
||||
"rootNav:ml",
|
||||
"anomaly_detection",
|
||||
"ml:anomalyDetection",
|
||||
"ml:anomalyExplorer",
|
||||
],
|
||||
"sideNavStatus": "visible",
|
||||
|
@ -249,7 +270,7 @@ Array [
|
|||
"isGroup": false,
|
||||
"path": Array [
|
||||
"rootNav:ml",
|
||||
"anomaly_detection",
|
||||
"ml:anomalyDetection",
|
||||
"ml:singleMetricViewer",
|
||||
],
|
||||
"sideNavStatus": "visible",
|
||||
|
@ -270,21 +291,27 @@ Array [
|
|||
"isGroup": false,
|
||||
"path": Array [
|
||||
"rootNav:ml",
|
||||
"anomaly_detection",
|
||||
"ml:anomalyDetection",
|
||||
"ml:settings",
|
||||
],
|
||||
"sideNavStatus": "visible",
|
||||
"title": "Deeplink ml:settings",
|
||||
},
|
||||
],
|
||||
"deepLink": undefined,
|
||||
"deepLink": Object {
|
||||
"baseUrl": "/mocked",
|
||||
"href": "http://mocked/ml:anomalyDetection",
|
||||
"id": "ml:anomalyDetection",
|
||||
"title": "Deeplink ml:anomalyDetection",
|
||||
"url": "/mocked/ml:anomalyDetection",
|
||||
},
|
||||
"href": undefined,
|
||||
"id": "anomaly_detection",
|
||||
"id": "ml:anomalyDetection",
|
||||
"isActive": false,
|
||||
"isGroup": true,
|
||||
"path": Array [
|
||||
"rootNav:ml",
|
||||
"anomaly_detection",
|
||||
"ml:anomalyDetection",
|
||||
],
|
||||
"renderAs": "accordion",
|
||||
"sideNavStatus": "visible",
|
||||
|
@ -293,6 +320,7 @@ Array [
|
|||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"breadcrumbStatus": "hidden",
|
||||
"children": undefined,
|
||||
"deepLink": Object {
|
||||
"baseUrl": "/mocked",
|
||||
|
@ -307,7 +335,7 @@ Array [
|
|||
"isGroup": false,
|
||||
"path": Array [
|
||||
"rootNav:ml",
|
||||
"data_frame_analytics",
|
||||
"ml:dataFrameAnalytics",
|
||||
"ml:dataFrameAnalytics",
|
||||
],
|
||||
"sideNavStatus": "visible",
|
||||
|
@ -328,7 +356,7 @@ Array [
|
|||
"isGroup": false,
|
||||
"path": Array [
|
||||
"rootNav:ml",
|
||||
"data_frame_analytics",
|
||||
"ml:dataFrameAnalytics",
|
||||
"ml:resultExplorer",
|
||||
],
|
||||
"sideNavStatus": "visible",
|
||||
|
@ -349,21 +377,27 @@ Array [
|
|||
"isGroup": false,
|
||||
"path": Array [
|
||||
"rootNav:ml",
|
||||
"data_frame_analytics",
|
||||
"ml:dataFrameAnalytics",
|
||||
"ml:analyticsMap",
|
||||
],
|
||||
"sideNavStatus": "visible",
|
||||
"title": "Deeplink ml:analyticsMap",
|
||||
},
|
||||
],
|
||||
"deepLink": undefined,
|
||||
"deepLink": Object {
|
||||
"baseUrl": "/mocked",
|
||||
"href": "http://mocked/ml:dataFrameAnalytics",
|
||||
"id": "ml:dataFrameAnalytics",
|
||||
"title": "Deeplink ml:dataFrameAnalytics",
|
||||
"url": "/mocked/ml:dataFrameAnalytics",
|
||||
},
|
||||
"href": undefined,
|
||||
"id": "data_frame_analytics",
|
||||
"id": "ml:dataFrameAnalytics",
|
||||
"isActive": false,
|
||||
"isGroup": true,
|
||||
"path": Array [
|
||||
"rootNav:ml",
|
||||
"data_frame_analytics",
|
||||
"ml:dataFrameAnalytics",
|
||||
],
|
||||
"renderAs": "accordion",
|
||||
"sideNavStatus": "visible",
|
||||
|
@ -459,6 +493,7 @@ Array [
|
|||
"title": "Deeplink ml:indexDataVisualizer",
|
||||
"url": "/mocked/ml:indexDataVisualizer",
|
||||
},
|
||||
"getIsActive": [Function],
|
||||
"href": undefined,
|
||||
"id": "ml:indexDataVisualizer",
|
||||
"isActive": false,
|
||||
|
@ -480,6 +515,7 @@ Array [
|
|||
"title": "Deeplink ml:dataDrift",
|
||||
"url": "/mocked/ml:dataDrift",
|
||||
},
|
||||
"getIsActive": [Function],
|
||||
"href": undefined,
|
||||
"id": "ml:dataDrift",
|
||||
"isActive": false,
|
||||
|
@ -517,6 +553,7 @@ Array [
|
|||
"title": "Deeplink ml:logRateAnalysis",
|
||||
"url": "/mocked/ml:logRateAnalysis",
|
||||
},
|
||||
"getIsActive": [Function],
|
||||
"href": undefined,
|
||||
"id": "ml:logRateAnalysis",
|
||||
"isActive": false,
|
||||
|
@ -538,6 +575,7 @@ Array [
|
|||
"title": "Deeplink ml:logPatternAnalysis",
|
||||
"url": "/mocked/ml:logPatternAnalysis",
|
||||
},
|
||||
"getIsActive": [Function],
|
||||
"href": undefined,
|
||||
"id": "ml:logPatternAnalysis",
|
||||
"isActive": false,
|
||||
|
@ -559,6 +597,7 @@ Array [
|
|||
"title": "Deeplink ml:changePointDetections",
|
||||
"url": "/mocked/ml:changePointDetections",
|
||||
},
|
||||
"getIsActive": [Function],
|
||||
"href": undefined,
|
||||
"id": "ml:changePointDetections",
|
||||
"isActive": false,
|
||||
|
|
|
@ -172,7 +172,7 @@ const nodeToEuiCollapsibleNavProps = (
|
|||
spaceBefore: _spaceBefore,
|
||||
} = navNode;
|
||||
const isExternal = Boolean(href) && isAbsoluteLink(href!);
|
||||
const isSelected = hasChildren ? false : isActive;
|
||||
const isSelected = hasChildren && !isItem ? false : isActive;
|
||||
const dataTestSubj = classnames(`nav-item`, `nav-item-${id}`, {
|
||||
[`nav-item-deepLinkId-${deepLink?.id}`]: !!deepLink,
|
||||
[`nav-item-id-${id}`]: id,
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiBreadcrumb } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ChromeBreadcrumb } from '@kbn/core/public';
|
||||
|
@ -25,6 +23,7 @@ export const SETTINGS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
|
|||
defaultMessage: 'Settings',
|
||||
}),
|
||||
href: '/settings',
|
||||
deepLinkId: 'ml:settings',
|
||||
});
|
||||
|
||||
export const ANOMALY_DETECTION_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
|
||||
|
@ -32,6 +31,7 @@ export const ANOMALY_DETECTION_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
|
|||
defaultMessage: 'Anomaly Detection',
|
||||
}),
|
||||
href: '/jobs',
|
||||
deepLinkId: 'ml:anomalyDetection',
|
||||
});
|
||||
|
||||
export const DATA_FRAME_ANALYTICS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
|
||||
|
@ -39,6 +39,7 @@ export const DATA_FRAME_ANALYTICS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
|
|||
defaultMessage: 'Data Frame Analytics',
|
||||
}),
|
||||
href: '/data_frame_analytics',
|
||||
deepLinkId: 'ml:dataFrameAnalytics',
|
||||
});
|
||||
|
||||
export const TRAINED_MODELS: ChromeBreadcrumb = Object.freeze({
|
||||
|
@ -46,6 +47,7 @@ export const TRAINED_MODELS: ChromeBreadcrumb = Object.freeze({
|
|||
defaultMessage: 'Model Management',
|
||||
}),
|
||||
href: '/trained_models',
|
||||
deepLinkId: 'ml:modelManagement',
|
||||
});
|
||||
|
||||
export const DATA_VISUALIZER_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
|
||||
|
@ -53,6 +55,7 @@ export const DATA_VISUALIZER_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
|
|||
defaultMessage: 'Data Visualizer',
|
||||
}),
|
||||
href: '/datavisualizer',
|
||||
deepLinkId: 'ml:dataVisualizer',
|
||||
});
|
||||
|
||||
// we need multiple AIOPS_BREADCRUMB breadcrumb items as they each need to link
|
||||
|
@ -83,6 +86,7 @@ export const LOG_RATE_ANALYSIS: ChromeBreadcrumb = Object.freeze({
|
|||
defaultMessage: 'Log Rate Analysis',
|
||||
}),
|
||||
href: '/aiops/log_rate_analysis_index_select',
|
||||
deepLinkId: 'ml:logRateAnalysis',
|
||||
});
|
||||
|
||||
export const LOG_PATTERN_ANALYSIS: ChromeBreadcrumb = Object.freeze({
|
||||
|
@ -90,6 +94,7 @@ export const LOG_PATTERN_ANALYSIS: ChromeBreadcrumb = Object.freeze({
|
|||
defaultMessage: 'Log Pattern Analysis',
|
||||
}),
|
||||
href: '/aiops/log_categorization_index_select',
|
||||
deepLinkId: 'ml:logPatternAnalysis',
|
||||
});
|
||||
|
||||
export const CHANGE_POINT_DETECTION: ChromeBreadcrumb = Object.freeze({
|
||||
|
@ -97,6 +102,7 @@ export const CHANGE_POINT_DETECTION: ChromeBreadcrumb = Object.freeze({
|
|||
defaultMessage: 'Change Point Detection',
|
||||
}),
|
||||
href: '/aiops/change_point_detection_index_select',
|
||||
deepLinkId: 'ml:changePointDetections',
|
||||
});
|
||||
|
||||
export const CREATE_JOB_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
|
||||
|
@ -111,6 +117,7 @@ export const CALENDAR_MANAGEMENT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
|
|||
defaultMessage: 'Calendar management',
|
||||
}),
|
||||
href: '/settings/calendars_list',
|
||||
deepLinkId: 'ml:calendarSettings',
|
||||
});
|
||||
|
||||
export const FILTER_LISTS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
|
||||
|
@ -118,6 +125,7 @@ export const FILTER_LISTS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
|
|||
defaultMessage: 'Filter lists',
|
||||
}),
|
||||
href: '/settings/filter_lists',
|
||||
deepLinkId: 'ml:filterListsSettings',
|
||||
});
|
||||
|
||||
export const DATA_DRIFT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
|
||||
|
@ -125,6 +133,7 @@ export const DATA_DRIFT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
|
|||
defaultMessage: 'Data drift',
|
||||
}),
|
||||
href: '/data_drift_index_select',
|
||||
deepLinkId: 'ml:dataDrift',
|
||||
});
|
||||
|
||||
const breadcrumbs = {
|
||||
|
@ -150,7 +159,7 @@ type Breadcrumb = keyof typeof breadcrumbs;
|
|||
export const breadcrumbOnClickFactory = (
|
||||
path: string | undefined,
|
||||
navigateToPath: NavigateToPath
|
||||
): EuiBreadcrumb['onClick'] => {
|
||||
): ChromeBreadcrumb['onClick'] => {
|
||||
return (e) => {
|
||||
e.preventDefault();
|
||||
navigateToPath(path);
|
||||
|
@ -161,12 +170,13 @@ export const getBreadcrumbWithUrlForApp = (
|
|||
breadcrumbName: Breadcrumb,
|
||||
navigateToPath?: NavigateToPath,
|
||||
basePath?: string
|
||||
): EuiBreadcrumb => {
|
||||
): ChromeBreadcrumb => {
|
||||
return {
|
||||
text: breadcrumbs[breadcrumbName].text,
|
||||
...(navigateToPath
|
||||
? {
|
||||
href: `${basePath}/app/ml${breadcrumbs[breadcrumbName].href}`,
|
||||
deepLinkId: breadcrumbs[breadcrumbName].deepLinkId,
|
||||
onClick: breadcrumbOnClickFactory(breadcrumbs[breadcrumbName].href, navigateToPath),
|
||||
}
|
||||
: {}),
|
||||
|
|
|
@ -81,6 +81,8 @@ export const getFormatChromeProjectNavNodes = (services: Services) => {
|
|||
id,
|
||||
title: node.title || '',
|
||||
path: [...path, id],
|
||||
breadcrumbStatus: node.breadcrumbStatus,
|
||||
getIsActive: node.getIsActive,
|
||||
};
|
||||
if (chrome.navLinks.has(id)) {
|
||||
const deepLink = chrome.navLinks.get(id);
|
||||
|
|
|
@ -90,6 +90,18 @@ const navigationTree: NavigationTreeDefinition = {
|
|||
defaultMessage: 'Anomaly detection',
|
||||
}),
|
||||
link: 'ml:anomalyDetection',
|
||||
renderAs: 'item',
|
||||
children: [
|
||||
{
|
||||
link: 'ml:singleMetricViewer',
|
||||
},
|
||||
{
|
||||
link: 'ml:anomalyExplorer',
|
||||
},
|
||||
{
|
||||
link: 'ml:settings',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.serverlessObservability.ml.logRateAnalysis', {
|
||||
|
|
|
@ -59,6 +59,15 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
|
|||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({
|
||||
deepLinkId: 'ml:anomalyDetection',
|
||||
});
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({
|
||||
text: 'Jobs',
|
||||
});
|
||||
await testSubjects.click('mlCreateNewJobButton');
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts([
|
||||
'AIOps',
|
||||
'Anomaly Detection',
|
||||
'Create job',
|
||||
]);
|
||||
|
||||
// navigate to a different section
|
||||
await svlCommonNavigation.sidenav.openSection('project_settings_project_nav');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue