diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index 166dc73b6290c..f3be43958509a 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -6,18 +6,21 @@ * Side Public License, v 1. */ +import { BehaviorSubject } from 'rxjs'; import { NavigationServices, SolutionProperties } from '../../types'; export const getServicesMock = (): NavigationServices => { const navigateToUrl = jest.fn().mockResolvedValue(undefined); const basePath = { prepend: jest.fn((path: string) => `/base${path}`) }; - const loadingCount = 0; + const loadingCount$ = new BehaviorSubject(0); + const recentlyAccessed$ = new BehaviorSubject([]); return { basePath, - loadingCount, navIsOpen: true, navigateToUrl, + loadingCount$, + recentlyAccessed$, }; }; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index 27e27393dc5bb..16ae574fc0787 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -13,7 +13,12 @@ import { NavigationProps, NavigationServices } from '../../types'; type Arguments = NavigationProps & NavigationServices; export type Params = Pick< Arguments, - 'activeNavItemId' | 'loadingCount' | 'navIsOpen' | 'platformConfig' | 'solutions' + | 'activeNavItemId' + | 'loadingCount$' + | 'recentlyAccessed$' + | 'navIsOpen' + | 'platformConfig' + | 'solutions' >; export class StorybookMock extends AbstractStorybookMock { diff --git a/packages/shared-ux/chrome/navigation/src/model/index.ts b/packages/shared-ux/chrome/navigation/src/model/index.ts index 8e8d94e995019..db3e5a29951ac 100644 --- a/packages/shared-ux/chrome/navigation/src/model/index.ts +++ b/packages/shared-ux/chrome/navigation/src/model/index.ts @@ -21,7 +21,6 @@ export interface NavigationModelDeps { * @public */ export enum Platform { - Recents = 'recents', Analytics = 'analytics', MachineLearning = 'ml', DevTools = 'devTools', diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index 8235963c18681..6b880a7c2729b 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -7,7 +7,6 @@ */ import React, { FC, useContext } from 'react'; -import useObservable from 'react-use/lib/useObservable'; import { NavigationKibanaDependencies, NavigationServices } from '../types'; const Context = React.createContext(null); @@ -27,17 +26,16 @@ export const NavigationKibanaProvider: FC = ({ ...dependencies }) => { const { core } = dependencies; - const { http } = core; + const { chrome, http } = core; const { basePath } = http; const { navigateToUrl } = core.application; - const loadingCount = useObservable(http.getLoadingCount$(), 0); - const value: NavigationServices = { basePath, - loadingCount, navigateToUrl, navIsOpen: true, + loadingCount$: http.getLoadingCount$(), + recentlyAccessed$: chrome.recentlyAccessed.get$(), }; return ( 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 9f6f3fbadca30..c268e7a42de10 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts +++ b/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts @@ -27,4 +27,10 @@ export const getI18nStrings = () => ({ defaultMessage: 'My deployments', } ), + recentlyAccessed: i18n.translate( + 'sharedUXPackages.chrome.sideNavigation.recentlyAccessed.title', + { + defaultMessage: 'Recent', + } + ), }); 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 20c0c2201acfb..4c4886c5f269d 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx @@ -13,9 +13,10 @@ import { EuiPopover, EuiThemeProvider, } from '@elastic/eui'; +import { css } from '@emotion/react'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import React, { useCallback, useState } from 'react'; -import { css } from '@emotion/react'; +import { BehaviorSubject } from 'rxjs'; import { getSolutionPropertiesMock, NavigationStorybookMock } from '../../mocks'; import mdx from '../../README.mdx'; import { NavigationProps, NavigationServices } from '../../types'; @@ -132,7 +133,7 @@ ReducedPlatformLinks.argTypes = storybookMock.getArgumentTypes(); export const WithRequestsLoading: ComponentStory = Template.bind({}); WithRequestsLoading.args = { activeNavItemId: 'example_project.root.get_started', - loadingCount: 1, + loadingCount$: new BehaviorSubject(1), solutions: [getSolutionPropertiesMock()], }; WithRequestsLoading.argTypes = storybookMock.getArgumentTypes(); diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx index 075925f05de6a..a85b8eb81b10b 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx @@ -8,6 +8,7 @@ import { render } from '@testing-library/react'; import React from 'react'; +import { BehaviorSubject } from 'rxjs'; import { getServicesMock } from '../../mocks/src/jest'; import { PlatformConfigSet, SolutionProperties } from '../../types'; import { Platform } from '../model'; @@ -114,7 +115,7 @@ describe('', () => { }); test('shows loading state', async () => { - services.loadingCount = 5; + services.loadingCount$ = new BehaviorSubject(5); const { findByTestId } = render( diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index 60f3af10a6b54..48bccd614fdb9 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import useObservable from 'react-use/lib/useObservable'; import { EuiCollapsibleNavGroup, EuiFlexGroup, @@ -13,6 +14,8 @@ import { EuiHeaderLogo, EuiLink, EuiLoadingSpinner, + EuiSideNav, + EuiSideNavItemType, EuiSpacer, useEuiTheme, } from '@elastic/eui'; @@ -21,17 +24,23 @@ import { getI18nStrings } from './i18n_strings'; import { NavigationBucketProps, NavigationProps } from '../../types'; import { NavigationModel } from '../model'; import { useNavigation } from '../services'; +import { navigationStyles as styles } from '../styles'; import { ElasticMark } from './elastic_mark'; import './header_logo.scss'; import { NavigationBucket } from './navigation_bucket'; export const Navigation = (props: NavigationProps) => { - const { loadingCount, activeNavItemId, ...services } = useNavigation(); + const { activeNavItemId, basePath, navIsOpen, navigateToUrl, ...observables } = useNavigation(); const { euiTheme } = useEuiTheme(); const activeNav = activeNavItemId ?? props.activeNavItemId; - const nav = new NavigationModel(services, props.platformConfig, props.solutions, activeNav); + const nav = new NavigationModel( + { basePath, navigateToUrl }, + props.platformConfig, + props.solutions, + activeNav + ); const solutions = nav.getSolutions(); const { analytics, ml, devTools, management } = nav.getPlatform(); @@ -39,10 +48,11 @@ export const Navigation = (props: NavigationProps) => { const strings = getI18nStrings(); const NavHeader = () => { - const homeUrl = services.basePath.prepend(props.homeHref); + const loadingCount = useObservable(observables.loadingCount$, 0); + const homeUrl = basePath.prepend(props.homeHref); const navigateHome = (event: React.MouseEvent) => { event.preventDefault(); - services.navigateToUrl(homeUrl); + navigateToUrl(homeUrl); }; const logo = loadingCount === 0 ? ( @@ -66,9 +76,7 @@ export const Navigation = (props: NavigationProps) => { return ( <> {logo} - {services.navIsOpen ? ( - - ) : null} + {navIsOpen ? : null} ); }; @@ -100,6 +108,34 @@ export const Navigation = (props: NavigationProps) => { } }; + const RecentlyAccessed = () => { + const recentlyAccessed = useObservable(observables.recentlyAccessed$, []); + + const navItems: Array> = [ + { + name: '', + id: 'recents_root', + items: recentlyAccessed.map((item) => ({ + ...item, + name: item.label, + href: item.link, + })), + }, + ]; + + return ( + 0} + data-test-subj={`nav-bucket-recentlyAccessed`} + > + + + ); + }; + // higher-order-component to keep the common props DRY const NavigationBucketHoc = (outerProps: Omit) => ( @@ -114,6 +150,8 @@ export const Navigation = (props: NavigationProps) => { + + {solutions.map((solutionBucket, idx) => { return ; })} diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index bd7aaddcb67c6..a42aee4bc3cc3 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -17,7 +17,8 @@ import { BasePathService, NavigateToUrlFn, RecentItem } from './internal'; export interface NavigationServices { activeNavItemId?: string; basePath: BasePathService; - loadingCount: number; + loadingCount$: Observable; + recentlyAccessed$: Observable; navIsOpen: boolean; navigateToUrl: NavigateToUrlFn; }