diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx index 22f32f7960a37..ede90c7098b5f 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { BehaviorSubject, combineLatest, merge, type Observable, of, ReplaySubject } from 'rxjs'; -import { flatMap, map, takeUntil } from 'rxjs/operators'; +import { mergeMap, map, takeUntil } from 'rxjs/operators'; import { parse } from 'url'; import { EuiLink } from '@elastic/eui'; import useObservable from 'react-use/lib/useObservable'; @@ -89,7 +89,7 @@ export class ChromeService { // in the sense that the chrome UI should not be displayed until a non-chromeless app is mounting or mounted of(true), application.currentAppId$.pipe( - flatMap((appId) => + mergeMap((appId) => application.applications$.pipe( map((applications) => { return !!appId && applications.has(appId) && !!applications.get(appId)!.chromeless; @@ -177,25 +177,23 @@ export class ChromeService { chromeStyle$.next(style); }; - const setProjectSideNavComponent = (component: ISideNavComponent | null) => { + const validateChromeStyle = () => { const chromeStyle = chromeStyle$.getValue(); if (chromeStyle !== 'project') { // Helps ensure callers go through the serverless plugin to get here. throw new Error( - `Invalid ChromeStyle value of "${chromeStyle}". setProjectSideNavComponent requires ChromeStyle set to "project".` + `Invalid ChromeStyle value of "${chromeStyle}". This method requires ChromeStyle set to "project".` ); } + }; + + const setProjectSideNavComponent = (component: ISideNavComponent | null) => { + validateChromeStyle(); projectNavigation.setProjectSideNavComponent(component); }; const setProjectNavigation = (config: ChromeProjectNavigation) => { - const chromeStyle = chromeStyle$.getValue(); - if (chromeStyle !== 'project') { - // Helps ensure callers go through the serverless plugin to get here. - throw new Error( - `Invalid ChromeStyle value of "${chromeStyle}". setProjectNavigation requires ChromeStyle set to "project".` - ); - } + validateChromeStyle(); projectNavigation.setProjectNavigation(config); }; @@ -206,6 +204,11 @@ export class ChromeService { projectNavigation.setProjectBreadcrumbs(breadcrumbs, params); }; + const setProjectHome = (homeHref: string) => { + validateChromeStyle(); + projectNavigation.setProjectHome(homeHref); + }; + const isIE = () => { const ua = window.navigator.userAgent; const msie = ua.indexOf('MSIE '); // IE 10 or older @@ -288,9 +291,14 @@ export class ChromeService { breadcrumbs$={projectBreadcrumbs$.pipe(takeUntil(this.stop$))} helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))} helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))} + navControlsLeft$={navControls.getLeft$()} + navControlsCenter$={navControls.getCenter$()} navControlsRight$={navControls.getRight$()} + loadingCount$={http.getLoadingCount$()} + homeHref$={projectNavigation.getProjectHome$()} kibanaDocLink={docLinks.links.kibana.guide} kibanaVersion={injectedMetadata.getKibanaVersion()} + prependBasePath={http.basePath.prepend} > {/* TODO: pass down the SideNavCompProps once they are defined */} @@ -405,6 +413,7 @@ export class ChromeService { setChromeStyle, getChromeStyle$: () => chromeStyle$.pipe(takeUntil(this.stop$)), project: { + setHome: setProjectHome, setNavigation: setProjectNavigation, setSideNavComponent: setProjectSideNavComponent, setBreadcrumbs: setProjectBreadcrumbs, diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts index 5398d09d132f3..ac6bf79e0ead9 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts @@ -26,6 +26,7 @@ export class ProjectNavigationService { private customProjectSideNavComponent$ = new BehaviorSubject<{ current: SideNavComponent | null; }>({ current: null }); + private projectHome$ = new BehaviorSubject(undefined); private projectNavigation$ = new BehaviorSubject(undefined); private projectBreadcrumbs$ = new BehaviorSubject<{ @@ -40,6 +41,12 @@ export class ProjectNavigationService { // 3. keep track of currently active link / path (path will be used to highlight the link in the sidenav and display part of the breadcrumbs) return { + setProjectHome: (homeHref: string) => { + this.projectHome$.next(homeHref); + }, + getProjectHome$: () => { + return this.projectHome$.asObservable(); + }, setProjectNavigation: (projectNavigation: ChromeProjectNavigation) => { this.projectNavigation$.next(projectNavigation); }, diff --git a/packages/core/chrome/core-chrome-browser-internal/src/types.ts b/packages/core/chrome/core-chrome-browser-internal/src/types.ts index 98f5ab672df94..427fb2c90d7e0 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/types.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/types.ts @@ -32,34 +32,42 @@ export interface InternalChromeStart extends ChromeStart { /** * Used only by the serverless plugin to customize project-style chrome. - * Use {@link ServerlessPluginStart.setSideNavComponent} to set serverless navigation. + * @internal */ project: { + /** + * Sets the project home href string. + * @param homeHref + * + * Use {@link ServerlessPluginStart.setProjectHome} to set project home. + */ + setHome(homeHref: string): void; + /** * Sets the project navigation config to be used for rendering project navigation. * It is used for default project sidenav, project breadcrumbs, tracking active deep link. * @param projectNavigation The project navigation config * - * @remarks Has no effect if the chrome style is not `project`. + * Use {@link ServerlessPluginStart.setNavigation} to set project navigation config. */ setNavigation(projectNavigation: ChromeProjectNavigation): void; /** * Set custom project sidenav component to be used instead of the default project sidenav. - * @param getter A function returning a CustomNavigationComponent. - * This component will receive Chrome navigation state as props (not yet implemented) + * @param component A getter function returning a CustomNavigationComponent. + * + * @remarks This component will receive Chrome navigation state as props (not yet implemented) * - * @remarks Has no effect if the chrome style is not `project`. + * Use {@link ServerlessPluginStart.setSideNavComponent} to set custom project navigation. */ setSideNavComponent(component: SideNavComponent | null): void; /** * Set project breadcrumbs - * * @param breadcrumbs * @param params.absolute If true, If true, the breadcrumbs will replace the defaults, otherwise they will be appended to the default ones. false by default. * - * @remarks Has no effect if the chrome style is not `project` or if setNavigation was not called + * Use {@link ServerlessPluginStart.setBreadcrumbs} to set project breadcrumbs. */ setBreadcrumbs( breadcrumbs: ChromeProjectBreadcrumb[] | ChromeProjectBreadcrumb, diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx index 9b7ad144055e7..195222cfbcbd6 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx @@ -6,76 +6,170 @@ * Side Public License, v 1. */ -import React, { createRef, useState } from 'react'; -import { Router } from 'react-router-dom'; import { EuiHeader, + EuiHeaderLink, EuiHeaderLogo, EuiHeaderSection, EuiHeaderSectionItem, EuiHeaderSectionItemButton, EuiIcon, + EuiLoadingSpinner, htmlIdGenerator, } from '@elastic/eui'; +import { css } from '@emotion/react'; +import type { InternalApplicationStart } from '@kbn/core-application-browser-internal'; import { ChromeBreadcrumb, ChromeGlobalHelpExtensionMenuLink, ChromeHelpExtension, ChromeNavControl, } from '@kbn/core-chrome-browser/src'; -import useLocalStorage from 'react-use/lib/useLocalStorage'; -import { i18n } from '@kbn/i18n'; -import { Observable } from 'rxjs'; +import type { HttpStart } from '@kbn/core-http-browser'; import { MountPoint } from '@kbn/core-mount-utils-browser'; -import { InternalApplicationStart } from '@kbn/core-application-browser-internal'; -import { HeaderBreadcrumbs } from '../header/header_breadcrumbs'; +import { i18n } from '@kbn/i18n'; +import React, { createRef, useState } from 'react'; +import { Router } from 'react-router-dom'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; +import useObservable from 'react-use/lib/useObservable'; +import { Observable, debounceTime } from 'rxjs'; import { HeaderActionMenu, useHeaderActionMenuMounter } from '../header/header_action_menu'; +import { HeaderBreadcrumbs } from '../header/header_breadcrumbs'; import { HeaderHelpMenu } from '../header/header_help_menu'; import { HeaderNavControls } from '../header/header_nav_controls'; import { ProjectNavigation } from './navigation'; +const headerCss = { + logo: { + container: css` + display: inline-block; + min-width: 56px; /* 56 = 40 + 8 + 8 */ + padding: 0 8px; + cursor: pointer; + `, + logo: css` + min-width: 0; /* overrides min-width: 40px */ + padding: 0; + `, + spinner: css` + position: relative; + left: 4px; + top: 2px; + `, + }, + nav: { + toggleNavButton: css` + border-right: 1px solid #d3dae6; + margin-left: -1px; + `, + }, +}; + +const headerStrings = { + logo: { + ariaLabel: i18n.translate('core.ui.primaryNav.goToHome.ariaLabel', { + defaultMessage: 'Go to home page', + }), + }, + cloud: { + linkToDeployments: i18n.translate('core.ui.primaryNav.cloud.linkToDeployments', { + defaultMessage: 'My deployments', + }), + }, + nav: { + closeNavAriaLabel: i18n.translate('core.ui.primaryNav.toggleNavAriaLabel', { + defaultMessage: 'Toggle primary navigation', + }), + }, +}; + interface Props { breadcrumbs$: Observable; actionMenu$: Observable; kibanaDocLink: string; + children: React.ReactNode; globalHelpExtensionMenuLinks$: Observable; helpExtension$: Observable; helpSupportUrl$: Observable; + homeHref$: Observable; kibanaVersion: string; application: InternalApplicationStart; + loadingCount$: ReturnType; + navControlsLeft$: Observable; + navControlsCenter$: Observable; navControlsRight$: Observable; - children: React.ReactNode; + prependBasePath: (url: string) => string; } const LOCAL_STORAGE_IS_OPEN_KEY = 'PROJECT_NAVIGATION_OPEN' as const; +const LOADING_DEBOUNCE_TIME = 80; + +const Logo = ( + props: Pick +) => { + const loadingCount = useObservable( + props.loadingCount$.pipe(debounceTime(LOADING_DEBOUNCE_TIME)), + 0 + ); + + const homeHref = useObservable(props.homeHref$, '/app/home'); + const { logo } = headerCss; + + let fullHref: string | undefined; + if (homeHref) { + fullHref = props.prependBasePath(homeHref); + } + + const navigateHome = (event: React.MouseEvent) => { + if (fullHref) { + props.application.navigateToUrl(fullHref); + } + event.preventDefault(); + }; + + return ( + + {loadingCount === 0 ? ( + + ) : ( + + + + )} + + ); +}; export const ProjectHeader = ({ application, kibanaDocLink, kibanaVersion, children, + prependBasePath, ...observables }: Props) => { const [navId] = useState(htmlIdGenerator()()); const [isOpen, setIsOpen] = useLocalStorage(LOCAL_STORAGE_IS_OPEN_KEY, true); const toggleCollapsibleNavRef = createRef void }>(); - - const renderLogo = () => ( - e.preventDefault()} - aria-label="Go to home page" - /> - ); - const headerActionMenuMounter = useHeaderActionMenuMounter(observables.actionMenu$); return ( <> - + setIsOpen(!isOpen)} aria-expanded={isOpen!} aria-pressed={isOpen!} @@ -105,12 +197,37 @@ export const ProjectHeader = ({ - {renderLogo()} + + + + + + + + + + + + {headerStrings.cloud.linkToDeployments} + + + + + + + + + - - - - + {headerActionMenuMounter.mount && ( - {/* TODO: This puts a group of nav menu items on the right edge of the screen, - but it should be possible for apps customize the layout in a grid and use spacers between items. - https://github.com/elastic/kibana/issues/158034 */} diff --git a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts index 4c2461510c669..68554c646cd99 100644 --- a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts +++ b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts @@ -64,6 +64,7 @@ const createStartContractMock = () => { getChromeStyle$: jest.fn(), setChromeStyle: jest.fn(), project: { + setHome: jest.fn(), setNavigation: jest.fn(), setSideNavComponent: jest.fn(), setBreadcrumbs: jest.fn(), diff --git a/packages/core/chrome/core-chrome-browser/src/project_navigation.ts b/packages/core/chrome/core-chrome-browser/src/project_navigation.ts index 64a95f565431c..00bdc67629b74 100644 --- a/packages/core/chrome/core-chrome-browser/src/project_navigation.ts +++ b/packages/core/chrome/core-chrome-browser/src/project_navigation.ts @@ -67,10 +67,6 @@ export interface ChromeProjectNavigationNode { /** @public */ export interface ChromeProjectNavigation { - /** - * The URL href for the home link - */ - homeRef: string; /** * The navigation tree representation of the side bar navigation. */ diff --git a/packages/shared-ux/chrome/navigation/index.ts b/packages/shared-ux/chrome/navigation/index.ts index 83c6a19688029..1bbb6c33a1482 100644 --- a/packages/shared-ux/chrome/navigation/index.ts +++ b/packages/shared-ux/chrome/navigation/index.ts @@ -11,7 +11,6 @@ export { NavigationKibanaProvider, NavigationProvider } from './src/services'; export { DefaultNavigation, getPresets, Navigation } from './src/ui'; export type { - CloudLinkDefinition, GroupDefinition, NavigationGroupPreset, NavigationTreeDefinition, diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index afaacdaeda62e..b41d324ab5b46 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -16,13 +16,11 @@ export const getServicesMock = ({ }: { navLinks?: ChromeNavLink[] } = {}): NavigationServices => { const navigateToUrl = jest.fn().mockResolvedValue(undefined); const basePath = { prepend: jest.fn((path: string) => `/base${path}`) }; - const loadingCount$ = new BehaviorSubject(0); const recentlyAccessed$ = new BehaviorSubject([]); const navLinks$ = new BehaviorSubject(navLinks); return { basePath, - loadingCount$, recentlyAccessed$, navLinks$, navIsOpen: true, diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index 9d660d923033b..28be60ca54538 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -15,7 +15,6 @@ type Arguments = ChromeNavigationViewModel & NavigationServices; export type Params = Pick< Arguments, | 'activeNavItemId' - | 'loadingCount$' | 'navIsOpen' | 'navigationTree' | 'platformConfig' @@ -51,7 +50,6 @@ export class StorybookMock extends AbstractStorybookMock< ...params, basePath: { prepend: (suffix: string) => `/basepath${suffix}` }, navigateToUrl, - loadingCount$: params.loadingCount$ ?? new BehaviorSubject(0), recentlyAccessed$: params.recentlyAccessed$ ?? new BehaviorSubject([]), navLinks$: params.navLinks$ ?? new BehaviorSubject([]), onProjectNavigationChange: params.onProjectNavigationChange ?? (() => undefined), @@ -61,8 +59,6 @@ export class StorybookMock extends AbstractStorybookMock< getProps(params: Params): ChromeNavigationViewModel { return { ...params, - homeHref: '#', - linkToCloud: 'projects', recentlyAccessedFilter: params.recentlyAccessedFilter, }; } diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index cd20965f6e3c8..655d37fc63c2c 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -32,7 +32,6 @@ export const NavigationKibanaProvider: FC = ({ const value: NavigationServices = { basePath, - loadingCount$: http.getLoadingCount$(), recentlyAccessed$: chrome.recentlyAccessed.get$(), navLinks$: chrome.navLinks.getNavLinks$(), navigateToUrl, diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/cloud_link.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/cloud_link.tsx deleted file mode 100644 index b71b949e47603..0000000000000 --- a/packages/shared-ux/chrome/navigation/src/ui/components/cloud_link.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 { EuiCollapsibleNavGroup, EuiLink } from '@elastic/eui'; -import React, { FC } from 'react'; -import { getI18nStrings } from '../i18n_strings'; - -const i18nTexts = getI18nStrings(); - -const presets = { - projects: { - href: 'https://cloud.elastic.co/projects', - icon: 'spaces', - title: i18nTexts.linkToCloudProjects, - dataTestSubj: 'nav-header-link-to-projects', - }, - deployments: { - href: 'https://cloud.elastic.co/deployments', - icon: 'spaces', - title: i18nTexts.linkToCloudDeployments, - dataTestSubj: 'nav-header-link-to-deployments', - }, -}; - -export interface Props { - /** Use one of the cloud link presets */ - preset?: 'projects' | 'deployments' | null; - /** Optional. If "preset" is not provided it is required */ - href?: string; - /** Optional. If "preset" is not provided it is required */ - icon?: string; - /** Optional. If "preset" is not provided it is required */ - title?: string; -} - -export const CloudLink: FC = ({ preset, href: _href, icon: _icon, title: _title }) => { - if (preset === null) { - return null; - } - - if (!preset && (!_href || !_icon || !_title)) { - throw new Error(`Navigation.CloudLink requires href, icon, and title`); - } - - const { href, icon, title, dataTestSubj } = - preset && presets[preset] - ? presets[preset]! - : { - href: _href, - icon: _icon, - title: _title, - dataTestSubj: 'nav-header-link-to-cloud', - }; - - return ( - - - - ); -}; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/index.ts b/packages/shared-ux/chrome/navigation/src/ui/components/index.ts index bb7aeacdfabb7..2174b35603525 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/index.ts +++ b/packages/shared-ux/chrome/navigation/src/ui/components/index.ts @@ -6,6 +6,5 @@ * Side Public License, v 1. */ -export type { Props as CloudLinkProps } from './cloud_link'; export { Navigation } from './navigation'; export type { Props as RecentlyAccessedProps } from './recently_accessed'; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation.test.tsx index 46a56dc4cc363..9860b8b231289 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation.test.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation.test.tsx @@ -6,11 +6,10 @@ * Side Public License, v 1. */ -import React from 'react'; -import { render } from '@testing-library/react'; -import { type Observable, of } from 'rxjs'; import type { ChromeNavLink } from '@kbn/core-chrome-browser'; - +import { render } from '@testing-library/react'; +import React from 'react'; +import { of, type Observable } from 'rxjs'; import { defaultAnalyticsNavGroup, defaultDevtoolsNavGroup, @@ -30,7 +29,7 @@ describe('', () => { const { findByTestId } = render( - + @@ -62,7 +61,6 @@ describe('', () => { const [navTree] = lastCall; expect(navTree).toEqual({ - homeRef: 'https://elastic.co', navigationTree: [ { id: 'group1', @@ -132,7 +130,7 @@ describe('', () => { navLinks$={navLinks$} onProjectNavigationChange={onProjectNavigationChange} > - + {/* Title from deeplink */} @@ -152,7 +150,6 @@ describe('', () => { const [navTree] = lastCall; expect(navTree).toEqual({ - homeRef: 'https://elastic.co', navigationTree: [ { id: 'root', @@ -225,7 +222,7 @@ describe('', () => { navLinks$={navLinks$} onProjectNavigationChange={onProjectNavigationChange} > - + {/* Title from deeplink */} @@ -247,7 +244,6 @@ describe('', () => { const [navTree] = lastCall; expect(navTree).toEqual({ - homeRef: 'https://elastic.co', navigationTree: [ { id: 'root', @@ -297,7 +293,7 @@ describe('', () => { navLinks$={navLinks$} onProjectNavigationChange={onProjectNavigationChange} > - + id="item1" link="notRegistered" /> @@ -310,8 +306,8 @@ describe('', () => { ); - expect(await queryByTestId('nav-group-root.group1')).toBeNull(); - expect(await queryByTestId('nav-item-root.group2.item1')).toBeVisible(); + expect(queryByTestId('nav-group-root.group1')).toBeNull(); + expect(queryByTestId('nav-item-root.group2.item1')).toBeVisible(); expect(onProjectNavigationChange).toHaveBeenCalled(); const lastCall = @@ -319,7 +315,6 @@ describe('', () => { const [navTree] = lastCall; expect(navTree).toEqual({ - homeRef: 'https://elastic.co', navigationTree: [ { id: 'root', @@ -375,7 +370,7 @@ describe('', () => { navLinks$={navLinks$} onProjectNavigationChange={onProjectNavigationChange} > - + link="item1"> @@ -392,9 +387,7 @@ describe('', () => { expect(await findByTestId('my-custom-element')).toBeVisible(); expect(await findByTestId('my-other-custom-element')).toBeVisible(); - expect(await (await findByTestId('my-other-custom-element')).textContent).toBe( - 'Children prop' - ); + expect((await findByTestId('my-other-custom-element')).textContent).toBe('Children prop'); expect(onProjectNavigationChange).toHaveBeenCalled(); const lastCall = @@ -402,7 +395,6 @@ describe('', () => { const [navTree] = lastCall; expect(navTree).toEqual({ - homeRef: 'https://elastic.co', navigationTree: [ { id: 'root', @@ -447,7 +439,7 @@ describe('', () => { render( - + @@ -462,7 +454,6 @@ describe('', () => { const [navTreeGenerated] = lastCall; expect(navTreeGenerated).toEqual({ - homeRef: 'https://elastic.co', navigationTree: expect.any(Array), }); @@ -479,31 +470,6 @@ describe('', () => { expect(navTreeGenerated.navigationTree[3]).toEqual(defaultManagementNavGroup); }); - test('should render cloud link', async () => { - const onProjectNavigationChange = jest.fn(); - - const { findByTestId } = render( - - - - - - - - - - - - ); - - expect(await findByTestId('nav-header-link-to-projects')).toBeVisible(); - expect(await findByTestId('nav-header-link-to-deployments')).toBeVisible(); - expect(await findByTestId('nav-header-link-to-cloud')).toBeVisible(); - expect(await (await findByTestId('nav-header-link-to-cloud')).textContent).toBe( - 'Custom link' - ); - }); - test('should render recently accessed items', async () => { const recentlyAccessed$ = of([ { label: 'This is an example', link: '/app/example/39859', id: '39850' }, @@ -512,7 +478,7 @@ describe('', () => { const { findByTestId } = render( - + @@ -523,7 +489,7 @@ describe('', () => { ); expect(await findByTestId('nav-bucket-recentlyAccessed')).toBeVisible(); - expect(await (await findByTestId('nav-bucket-recentlyAccessed')).textContent).toBe( + expect((await findByTestId('nav-bucket-recentlyAccessed')).textContent).toBe( 'RecentThis is an exampleAnother example' ); }); @@ -533,7 +499,7 @@ describe('', () => { render( - + @@ -547,7 +513,6 @@ describe('', () => { const [navTreeGenerated] = lastCall; expect(navTreeGenerated).toEqual({ - homeRef: 'https://elastic.co', navigationTree: [ { id: 'group1', @@ -579,7 +544,7 @@ describe('', () => { const expectToThrow = () => { render( - + diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation.tsx index 6825c8a8084bf..073318df6f838 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation.tsx @@ -20,7 +20,6 @@ import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser'; import { useNavigation as useNavigationServices } from '../../services'; import { RegisterFunction, UnRegisterFunction } from '../types'; -import { CloudLink } from './cloud_link'; import { NavigationFooter } from './navigation_footer'; import { NavigationGroup } from './navigation_group'; import { NavigationItem } from './navigation_item'; @@ -44,10 +43,6 @@ const NavigationContext = createContext({ interface Props { children: ReactNode; - /** - * Href to the home page - */ - homeRef: string; /** * Flag to indicate if the Navigation should not be styled with EUI components. * If set to true, the children will be rendered as is. @@ -56,7 +51,7 @@ interface Props { dataTestSubj?: string; } -export function Navigation({ children, homeRef, unstyled = false, dataTestSubj }: Props) { +export function Navigation({ children, unstyled = false, dataTestSubj }: Props) { const { onProjectNavigationChange } = useNavigationServices(); // We keep a reference of the order of the children that register themselves when mounting. @@ -109,23 +104,17 @@ export function Navigation({ children, homeRef, unstyled = false, dataTestSubj } useEffect(() => { // This will update the navigation tree in the Chrome service (calling the serverless.setNavigation()) onProjectNavigationChange({ - homeRef, navigationTree: Object.values(navigationItems).sort((a, b) => { const aOrder = orderChildrenRef.current[a.id]; const bOrder = orderChildrenRef.current[b.id]; return aOrder - bOrder; }), }); - }, [navigationItems, onProjectNavigationChange, homeRef]); + }, [navigationItems, onProjectNavigationChange]); return ( - + {children} @@ -143,5 +132,4 @@ export function useNavigation() { Navigation.Group = NavigationGroup; Navigation.Item = NavigationItem; Navigation.Footer = NavigationFooter; -Navigation.CloudLink = CloudLink; Navigation.RecentlyAccessed = RecentlyAccessed; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_header.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_header.tsx deleted file mode 100644 index 7895ca62e7a4b..0000000000000 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_header.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 React, { FC } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiHeaderLogo, EuiLoadingSpinner } from '@elastic/eui'; -import useObservable from 'react-use/lib/useObservable'; -import { useNavigation as useServices } from '../../services'; -import { ElasticMark } from '../elastic_mark'; -import { getI18nStrings } from '../i18n_strings'; - -import '../header_logo.scss'; - -interface Props { - homeHref: string; -} - -export const NavHeader: FC = ({ homeHref }) => { - const strings = getI18nStrings(); - const { basePath, navigateToUrl, loadingCount$ } = useServices(); - const loadingCount = useObservable(loadingCount$, 0); - const homeUrl = basePath.prepend(homeHref); - - const navigateHome = (event: React.MouseEvent) => { - event.preventDefault(); - navigateToUrl(homeUrl); - }; - - const logo = - loadingCount === 0 ? ( - - ) : ( - - - - ); - - return ( - - {logo} - - - - - ); -}; diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_ui.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_ui.tsx index a0f48aef8a112..898a3b6829821 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_ui.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_ui.tsx @@ -6,32 +6,18 @@ * Side Public License, v 1. */ -import { EuiCollapsibleNavGroup, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { FC } from 'react'; -import { NavHeader } from './navigation_header'; interface Props { - homeRef: string; unstyled?: boolean; footerChildren?: React.ReactNode; dataTestSubj?: string; } -export const NavigationUI: FC = ({ - children, - unstyled, - footerChildren, - homeRef, - dataTestSubj, -}) => { - const { euiTheme } = useEuiTheme(); - +export const NavigationUI: FC = ({ children, unstyled, footerChildren, dataTestSubj }) => { return ( <> - - - - {unstyled ? ( <>{children} ) : ( diff --git a/packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.tsx index 91e3ed5ca28e3..4ad3dc75a9d93 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.tsx @@ -23,10 +23,6 @@ import { } from '../../mocks/src/default_navigation.test.helpers'; import { navLinksMock } from '../../mocks/src/navlinks'; -const defaultProps = { - homeRef: 'https://elastic.co', -}; - describe('', () => { const services = getServicesMock(); @@ -77,12 +73,7 @@ describe('', () => { const { findByTestId } = render( - + ); @@ -103,7 +94,6 @@ describe('', () => { const [navTreeGenerated] = lastCall; expect(navTreeGenerated).toEqual({ - homeRef: 'https://elastic.co', navigationTree: [ { id: 'group1', @@ -202,12 +192,7 @@ describe('', () => { navLinks$={navLinks$} onProjectNavigationChange={onProjectNavigationChange} > - + ); @@ -217,7 +202,6 @@ describe('', () => { const [navTreeGenerated] = lastCall; expect(navTreeGenerated).toEqual({ - homeRef: 'https://elastic.co', navigationTree: [ { id: 'root', @@ -285,12 +269,7 @@ describe('', () => { render( - + ); @@ -300,7 +279,6 @@ describe('', () => { const [navTreeGenerated] = lastCall; expect(navTreeGenerated).toEqual({ - homeRef: 'https://elastic.co', navigationTree: [ { id: 'root', @@ -358,12 +336,7 @@ describe('', () => { const expectToThrow = () => { render( - + ); }; @@ -374,43 +347,6 @@ describe('', () => { console.error.mockRestore(); }); - test('should render cloud link', async () => { - const navigationBody: RootNavigationItemDefinition[] = [ - { - type: 'cloudLink', - preset: 'deployments', - }, - { - type: 'cloudLink', - preset: 'projects', - }, - { - type: 'cloudLink', - href: 'https://foo.com', - icon: 'myIcon', - title: 'Custom link', - }, - ]; - - const { findByTestId } = render( - - - - ); - - expect(await findByTestId('nav-header-link-to-projects')).toBeVisible(); - expect(await findByTestId('nav-header-link-to-deployments')).toBeVisible(); - expect(await findByTestId('nav-header-link-to-cloud')).toBeVisible(); - expect(await (await findByTestId('nav-header-link-to-cloud')).textContent).toBe( - 'Custom link' - ); - }); - test('should render recently accessed items', async () => { const recentlyAccessed$ = of([ { label: 'This is an example', link: '/app/example/39859', id: '39850' }, @@ -425,17 +361,12 @@ describe('', () => { const { findByTestId } = render( - + ); expect(await findByTestId('nav-bucket-recentlyAccessed')).toBeVisible(); - expect(await (await findByTestId('nav-bucket-recentlyAccessed')).textContent).toBe( + expect((await findByTestId('nav-bucket-recentlyAccessed')).textContent).toBe( 'RecentThis is an exampleAnother example' ); }); @@ -489,7 +420,7 @@ describe('', () => { navLinks$={navLinks$} onProjectNavigationChange={onProjectNavigationChange} > - + ); @@ -499,7 +430,6 @@ describe('', () => { const [navTreeGenerated] = lastCall; expect(navTreeGenerated).toEqual({ - homeRef: 'https://elastic.co', navigationTree: expect.any(Array), }); diff --git a/packages/shared-ux/chrome/navigation/src/ui/default_navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/default_navigation.tsx index 6f179c9ff821d..a42ef5b802e6c 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/default_navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/default_navigation.tsx @@ -18,7 +18,6 @@ import type { ProjectNavigationTreeDefinition, RootNavigationItemDefinition, } from './types'; -import { CloudLink } from './components/cloud_link'; import { RecentlyAccessed } from './components/recently_accessed'; import { NavigationFooter } from './components/navigation_footer'; import { getPresets } from './nav_tree_presets'; @@ -39,10 +38,6 @@ const getDefaultNavigationTree = ( ): NavigationTreeDefinition => { return { body: [ - { - type: 'cloudLink', - preset: 'deployments', - }, { type: 'recentlyAccessed', }, @@ -72,7 +67,6 @@ const getDefaultNavigationTree = ( let idCounter = 0; export const DefaultNavigation: FC = ({ - homeRef, projectNavigationTree, navigationTree, dataTestSubj, @@ -92,14 +86,8 @@ export const DefaultNavigation: FC { return items.map((item) => { const isRootNavigationItem = isRootNavigationItemDefinition(item); - if (isRootNavigationItem) { - if (item.type === 'cloudLink') { - return ; - } - - if (item.type === 'recentlyAccessed') { - return ; - } + if (isRootNavigationItem && item.type === 'recentlyAccessed') { + return ; } if (item.preset) { @@ -134,7 +122,7 @@ export const DefaultNavigation: FC + <> {renderItems(navigationDefinition.body)} {navigationDefinition.footer && ( diff --git a/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts b/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts index c268e7a42de10..0167ffd0ea7ee 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts +++ b/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts @@ -9,24 +9,6 @@ import { i18n } from '@kbn/i18n'; export const getI18nStrings = () => ({ - headerLogoAriaLabel: i18n.translate( - 'sharedUXPackages.chrome.sideNavigation.headerLogo.ariaLabel', - { - defaultMessage: 'Go to home page', - } - ), - linkToCloudProjects: i18n.translate( - 'sharedUXPackages.chrome.sideNavigation.linkToCloud.projects', - { - defaultMessage: 'My projects', - } - ), - linkToCloudDeployments: i18n.translate( - 'sharedUXPackages.chrome.sideNavigation.linkToCloud.deployments', - { - defaultMessage: 'My deployments', - } - ), recentlyAccessed: i18n.translate( 'sharedUXPackages.chrome.sideNavigation.recentlyAccessed.title', { diff --git a/packages/shared-ux/chrome/navigation/src/ui/index.ts b/packages/shared-ux/chrome/navigation/src/ui/index.ts index 7bb534a32a8c0..1335a1844f45c 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/index.ts +++ b/packages/shared-ux/chrome/navigation/src/ui/index.ts @@ -13,7 +13,6 @@ export { DefaultNavigation } from './default_navigation'; export { getPresets } from './nav_tree_presets'; export type { - CloudLinkDefinition, GroupDefinition, NavigationGroupPreset, NavigationTreeDefinition, diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx index 21b59cf7521f6..1de6bb828c138 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx @@ -115,7 +115,6 @@ const deepLinks: ChromeNavLink[] = [ ]; const simpleNavigationDefinition: ProjectNavigationDefinition = { - homeRef: 'https://elastic.co', projectNavigationTree: [ { id: 'example_projet', @@ -195,13 +194,8 @@ export const SimpleObjectDefinition = (args: ChromeNavigationViewModel & Navigat }; const navigationDefinition: ProjectNavigationDefinition = { - homeRef: 'https://elastic.co', navigationTree: { body: [ - { - type: 'cloudLink', - preset: 'deployments', - }, // My custom project { type: 'navGroup', @@ -330,9 +324,7 @@ export const WithUIComponents = (args: ChromeNavigationViewModel & NavigationSer return ( - - - + { +export const MinimalUI = (args: ChromeNavigationViewModel & NavigationServices) => { const services = storybookMock.getServices({ ...args, navLinks$: of([...navLinksMock, ...deepLinks]), @@ -398,13 +388,7 @@ export const MinimalUIAndCustomCloudLink = ( return ( - - - + - + - - diff --git a/packages/shared-ux/chrome/navigation/src/ui/types.ts b/packages/shared-ux/chrome/navigation/src/ui/types.ts index 1d479bfafd43d..91f37e8d3c63d 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/types.ts +++ b/packages/shared-ux/chrome/navigation/src/ui/types.ts @@ -13,7 +13,7 @@ import type { NodeDefinition, } from '@kbn/core-chrome-browser'; -import type { CloudLinkProps, RecentlyAccessedProps } from './components'; +import type { RecentlyAccessedProps } from './components'; export type NonEmptyArray = [T, ...T[]]; @@ -75,15 +75,6 @@ export interface RecentlyAccessedDefinition extends RecentlyAccessedProps { type: 'recentlyAccessed'; } -/** - * @public - * - * A cloud link root item definition. Use it to add one or more links to the Cloud console - */ -export interface CloudLinkDefinition extends CloudLinkProps { - type: 'cloudLink'; -} - /** * @public * @@ -109,7 +100,7 @@ export type RootNavigationItemDefinition< LinkId extends AppDeepLinkId = AppDeepLinkId, Id extends string = string, ChildrenId extends string = Id -> = RecentlyAccessedDefinition | CloudLinkDefinition | GroupDefinition; +> = RecentlyAccessedDefinition | GroupDefinition; export type ProjectNavigationTreeDefinition< LinkId extends AppDeepLinkId = AppDeepLinkId, @@ -142,10 +133,6 @@ export interface NavigationTreeDefinition { * or when calling `setNavigation()` on the serverless plugin. */ export interface ProjectNavigationDefinition { - /** - * The URL href for the home link - */ - homeRef: string; /** * A navigation tree structure with object items containing labels, links, and sub-items * for a project. Use it if you only need to configure your project navigation and leave diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index 0a850121430d0..e262b61d13622 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -19,7 +19,6 @@ import type { BasePathService, NavigateToUrlFn, RecentItem } from './internal'; export interface NavigationServices { activeNavItemId?: string; basePath: BasePathService; - loadingCount$: Observable; recentlyAccessed$: Observable; navLinks$: Observable>; navIsOpen: boolean; @@ -55,7 +54,7 @@ export interface NavigationKibanaDependencies { export type ChromeNavigationLink = string; /** - * Chrome navigatioin node definition. + * Chrome navigation node definition. * * @public */ @@ -97,14 +96,6 @@ export interface ChromeNavigationNodeViewModel extends Omit { - /** - * Target for the logo icon - */ - homeHref: string; + extends Pick { /** * The navigation tree definition */ diff --git a/x-pack/plugins/serverless/public/mocks.ts b/x-pack/plugins/serverless/public/mocks.ts index d23bc823c0182..ffe61c833e6dc 100644 --- a/x-pack/plugins/serverless/public/mocks.ts +++ b/x-pack/plugins/serverless/public/mocks.ts @@ -8,9 +8,10 @@ import { ServerlessPluginStart } from './types'; const startMock = (): ServerlessPluginStart => ({ - setSideNavComponent: jest.fn(), setNavigation: jest.fn(), setBreadcrumbs: jest.fn(), + setProjectHome: jest.fn(), + setSideNavComponent: jest.fn(), }); export const serverlessMock = { diff --git a/x-pack/plugins/serverless/public/plugin.tsx b/x-pack/plugins/serverless/public/plugin.tsx index e015633a2f99e..73f199eb3c468 100644 --- a/x-pack/plugins/serverless/public/plugin.tsx +++ b/x-pack/plugins/serverless/public/plugin.tsx @@ -7,6 +7,7 @@ import { InternalChromeStart } from '@kbn/core-chrome-browser-internal'; import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; +import { I18nProvider } from '@kbn/i18n-react'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { ProjectSwitcher, ProjectSwitcherKibanaProvider } from '@kbn/serverless-project-switcher'; import { ProjectType } from '@kbn/serverless-types'; @@ -61,14 +62,15 @@ export class ServerlessPlugin core.chrome.setChromeStyle('project'); management.setIsSidebarEnabled(false); + // Casting the "chrome.projects" service to an "internal" type: this is intentional to obscure the property from Typescript. + const { project } = core.chrome as InternalChromeStart; + return { - // Casting the "chrome.projects" service to an "internal" type: this is intentional to obscure the property from Typescript. setSideNavComponent: (sideNavigationComponent) => - (core.chrome as InternalChromeStart).project.setSideNavComponent(sideNavigationComponent), - setNavigation: (projectNavigation) => - (core.chrome as InternalChromeStart).project.setNavigation(projectNavigation), - setBreadcrumbs: (breadcrumbs, params) => - (core.chrome as InternalChromeStart).project.setBreadcrumbs(breadcrumbs, params), + project.setSideNavComponent(sideNavigationComponent), + setNavigation: (projectNavigation) => project.setNavigation(projectNavigation), + setBreadcrumbs: (breadcrumbs, params) => project.setBreadcrumbs(breadcrumbs, params), + setProjectHome: (homeHref: string) => project.setHome(homeHref), }; } @@ -80,11 +82,13 @@ export class ServerlessPlugin currentProjectType: ProjectType ) { ReactDOM.render( - - - - - , + + + + + + + , targetDomElement ); diff --git a/x-pack/plugins/serverless/public/types.ts b/x-pack/plugins/serverless/public/types.ts index 8efad28215d49..8e9e8672f7e69 100644 --- a/x-pack/plugins/serverless/public/types.ts +++ b/x-pack/plugins/serverless/public/types.ts @@ -5,24 +5,25 @@ * 2.0. */ -import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public'; import type { - SideNavComponent, - ChromeProjectNavigation, ChromeProjectBreadcrumb, + ChromeProjectNavigation, ChromeSetProjectBreadcrumbsParams, + SideNavComponent, } from '@kbn/core-chrome-browser'; +import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ServerlessPluginSetup {} export interface ServerlessPluginStart { - setSideNavComponent: (navigation: SideNavComponent) => void; - setNavigation(projectNavigation: ChromeProjectNavigation): void; setBreadcrumbs: ( breadcrumbs: ChromeProjectBreadcrumb | ChromeProjectBreadcrumb[], params?: Partial ) => void; + setNavigation(projectNavigation: ChromeProjectNavigation): void; + setProjectHome(homeHref: string): void; + setSideNavComponent: (navigation: SideNavComponent) => void; } export interface ServerlessPluginSetupDependencies { diff --git a/x-pack/plugins/serverless/tsconfig.json b/x-pack/plugins/serverless/tsconfig.json index 52919a26a652f..c4cf602f928c7 100644 --- a/x-pack/plugins/serverless/tsconfig.json +++ b/x-pack/plugins/serverless/tsconfig.json @@ -23,5 +23,6 @@ "@kbn/utils", "@kbn/core-chrome-browser", "@kbn/core-chrome-browser-internal", + "@kbn/i18n-react", ] } diff --git a/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx b/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx index 6f577fa904e7a..ea24b44cbd960 100644 --- a/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx +++ b/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx @@ -18,7 +18,6 @@ import { i18n } from '@kbn/i18n'; const navigationTree: NavigationTreeDefinition = { body: [ - { type: 'cloudLink', preset: 'projects' }, { type: 'recentlyAccessed' }, { type: 'navGroup', @@ -119,11 +118,7 @@ export const getObservabilitySideNavComponent = () => { return ( - + ); }; diff --git a/x-pack/plugins/serverless_observability/public/plugin.ts b/x-pack/plugins/serverless_observability/public/plugin.ts index 56304cfdb623d..ee774980f7c29 100644 --- a/x-pack/plugins/serverless_observability/public/plugin.ts +++ b/x-pack/plugins/serverless_observability/public/plugin.ts @@ -30,6 +30,7 @@ export class ServerlessObservabilityPlugin ): ServerlessObservabilityPluginStart { const { observabilityShared, serverless } = setupDeps; observabilityShared.setIsSidebarEnabled(false); + serverless.setProjectHome('/app/observability/landing'); serverless.setSideNavComponent(getObservabilitySideNavComponent(core, { serverless })); return {}; } diff --git a/x-pack/plugins/serverless_search/public/layout/nav.tsx b/x-pack/plugins/serverless_search/public/layout/nav.tsx index adbb0e0e5a414..aa114e609152e 100644 --- a/x-pack/plugins/serverless_search/public/layout/nav.tsx +++ b/x-pack/plugins/serverless_search/public/layout/nav.tsx @@ -18,7 +18,6 @@ import { ServerlessPluginStart } from '@kbn/serverless/public'; const navigationTree: NavigationTreeDefinition = { body: [ - { type: 'cloudLink', preset: 'projects' }, { type: 'recentlyAccessed' }, { type: 'navGroup', @@ -109,11 +108,7 @@ export const createServerlessSearchSideNavComponent = () => { return ( - + ); }; diff --git a/x-pack/plugins/serverless_search/public/plugin.ts b/x-pack/plugins/serverless_search/public/plugin.ts index 18644b72f426c..69de4a01ecbdd 100644 --- a/x-pack/plugins/serverless_search/public/plugin.ts +++ b/x-pack/plugins/serverless_search/public/plugin.ts @@ -50,6 +50,7 @@ export class ServerlessSearchPlugin core: CoreStart, { serverless }: ServerlessSearchPluginStartDependencies ): ServerlessSearchPluginStart { + serverless.setProjectHome('/app/elasticsearch'); serverless.setSideNavComponent(createComponent(core, { serverless })); return {}; } diff --git a/x-pack/plugins/serverless_security/public/plugin.ts b/x-pack/plugins/serverless_security/public/plugin.ts index 2c181a2fe3354..f0729330de4af 100644 --- a/x-pack/plugins/serverless_security/public/plugin.ts +++ b/x-pack/plugins/serverless_security/public/plugin.ts @@ -49,6 +49,7 @@ export class ServerlessSecurityPlugin securitySolution.setIsSidebarEnabled(false); securitySolution.setGetStartedPage(getSecurityGetStartedComponent(core, startDeps)); + serverless.setProjectHome('/app/security'); serverless.setSideNavComponent(getSecuritySideNavComponent(core, startDeps)); return {};