From 1c67d67f97ec6ee493e27dd72323edc13ff2df3a Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Thu, 14 May 2020 21:02:20 -0500 Subject: [PATCH 01/11] first draft refactoring nav link created --- src/core/public/chrome/chrome_service.tsx | 50 ++- src/core/public/chrome/nav_links/nav_link.ts | 14 +- .../public/chrome/nav_links/to_nav_link.ts | 27 +- .../recently_accessed_service.ts | 2 +- src/core/public/chrome/ui/header/_index.scss | 2 - ...lapsible_nav.scss => collapsible_nav.scss} | 0 .../chrome/ui/header/collapsible_nav.test.tsx | 344 ++++++++++-------- .../chrome/ui/header/collapsible_nav.tsx | 115 +++--- .../public/chrome/ui/header/header.test.tsx | 54 +++ src/core/public/chrome/ui/header/header.tsx | 271 +++++--------- .../chrome/ui/header/header_breadcrumbs.tsx | 89 +---- .../public/chrome/ui/header/header_logo.tsx | 17 +- .../chrome/ui/header/header_nav_controls.tsx | 39 +- .../public/chrome/ui/header/nav_drawer.tsx | 38 +- src/core/public/chrome/ui/header/nav_link.tsx | 122 ++----- .../server/legacy/plugins/get_nav_links.ts | 2 + 16 files changed, 562 insertions(+), 624 deletions(-) rename src/core/public/chrome/ui/header/{_collapsible_nav.scss => collapsible_nav.scss} (100%) create mode 100644 src/core/public/chrome/ui/header/header.test.tsx diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index bf1a764e85882..5c3ac4c92a800 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -34,7 +34,7 @@ import { ChromeDocTitle, DocTitleService } from './doc_title'; import { ChromeNavControls, NavControlsService } from './nav_controls'; import { ChromeNavLinks, NavLinksService } from './nav_links'; import { ChromeRecentlyAccessed, RecentlyAccessedService } from './recently_accessed'; -import { Header, LoadingIndicator } from './ui'; +import { Header } from './ui'; import { NavType } from './ui/header'; import { ChromeHelpExtensionMenuLink } from './ui/header/header_help_menu'; export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle }; @@ -180,31 +180,29 @@ export class ChromeService { docTitle, getHeaderComponent: () => ( - - -
- +
), setAppTitle: (appTitle: string) => appTitle$.next(appTitle), diff --git a/src/core/public/chrome/nav_links/nav_link.ts b/src/core/public/chrome/nav_links/nav_link.ts index fb2972735c2b7..55b5c80526bab 100644 --- a/src/core/public/chrome/nav_links/nav_link.ts +++ b/src/core/public/chrome/nav_links/nav_link.ts @@ -62,7 +62,7 @@ export interface ChromeNavLink { /** * A EUI iconType that will be used for the app's icon. This icon - * takes precendence over the `icon` property. + * takes precedence over the `icon` property. */ readonly euiIconType?: string; @@ -72,6 +72,14 @@ export interface ChromeNavLink { */ readonly icon?: string; + /** + * Settled state between `url`, `baseUrl`, and `active` + * + * @internalRemarks + * This should be required once legacy apps are gone. + */ + readonly href?: string; + /** LEGACY FIELDS */ /** @@ -144,7 +152,7 @@ export interface ChromeNavLink { /** @public */ export type ChromeNavLinkUpdateableFields = Partial< - Pick + Pick >; export class NavLinkWrapper { @@ -162,7 +170,7 @@ export class NavLinkWrapper { public update(newProps: ChromeNavLinkUpdateableFields) { // Enforce limited properties at runtime for JS code - newProps = pick(newProps, ['active', 'disabled', 'hidden', 'url', 'subUrlBase']); + newProps = pick(newProps, ['active', 'disabled', 'hidden', 'url', 'subUrlBase', 'href']); return new NavLinkWrapper({ ...this.properties, ...newProps }); } } diff --git a/src/core/public/chrome/nav_links/to_nav_link.ts b/src/core/public/chrome/nav_links/to_nav_link.ts index f79b1df77f8e1..24744fe53c82c 100644 --- a/src/core/public/chrome/nav_links/to_nav_link.ts +++ b/src/core/public/chrome/nav_links/to_nav_link.ts @@ -24,7 +24,12 @@ import { appendAppPath } from '../../application/utils'; export function toNavLink(app: App | LegacyApp, basePath: IBasePath): NavLinkWrapper { const useAppStatus = app.navLinkStatus === AppNavLinkStatus.default; - const baseUrl = isLegacyApp(app) ? basePath.prepend(app.appUrl) : basePath.prepend(app.appRoute!); + const relativeBaseUrl = isLegacyApp(app) + ? basePath.prepend(app.appUrl) + : basePath.prepend(app.appRoute!); + const url = relativeToAbsolute(appendAppPath(relativeBaseUrl, app.defaultPath)); + const baseUrl = relativeToAbsolute(relativeBaseUrl); + return new NavLinkWrapper({ ...app, hidden: useAppStatus @@ -32,17 +37,27 @@ export function toNavLink(app: App | LegacyApp, basePath: IBasePath): NavLinkWra : app.navLinkStatus === AppNavLinkStatus.hidden, disabled: useAppStatus ? false : app.navLinkStatus === AppNavLinkStatus.disabled, legacy: isLegacyApp(app), - baseUrl: relativeToAbsolute(baseUrl), + baseUrl, ...(isLegacyApp(app) - ? {} + ? { + href: url && !url.startsWith(app.subUrlBase!) ? url : baseUrl, + } : { - url: relativeToAbsolute(appendAppPath(baseUrl, app.defaultPath)), + href: url, + url, }), }); } -function relativeToAbsolute(url: string) { - // convert all link urls to absolute urls +/** + * @param {string} url - a relative or root relative url. If a relative path is given then the + * absolute url returned will depend on the current page where this function is called from. For example + * if you are on page "http://www.mysite.com/shopping/kids" and you pass this function "adults", you would get + * back "http://www.mysite.com/shopping/adults". If you passed this function a root relative path, or one that + * starts with a "/", for example "/account/cart", you would get back "http://www.mysite.com/account/cart". + * @return {string} the relative url transformed into an absolute url + */ +export function relativeToAbsolute(url: string) { const a = document.createElement('a'); a.setAttribute('href', url); return a.href; diff --git a/src/core/public/chrome/recently_accessed/recently_accessed_service.ts b/src/core/public/chrome/recently_accessed/recently_accessed_service.ts index 27dbc288d18cb..86c7f3a1ef765 100644 --- a/src/core/public/chrome/recently_accessed/recently_accessed_service.ts +++ b/src/core/public/chrome/recently_accessed/recently_accessed_service.ts @@ -76,7 +76,7 @@ export interface ChromeRecentlyAccessed { * * @param link a relative URL to the resource (not including the {@link HttpStart.basePath | `http.basePath`}) * @param label the label to display in the UI - * @param id a unique string used to de-duplicate the recently accessed llist. + * @param id a unique string used to de-duplicate the recently accessed list. */ add(link: string, label: string, id: string): void; diff --git a/src/core/public/chrome/ui/header/_index.scss b/src/core/public/chrome/ui/header/_index.scss index 1b0438d748ff0..5c5e7f18b60a4 100644 --- a/src/core/public/chrome/ui/header/_index.scss +++ b/src/core/public/chrome/ui/header/_index.scss @@ -1,5 +1,3 @@ -@import './collapsible_nav'; - // TODO #64541 // Delete this block .chrHeaderWrapper:not(.headerWrapper) { diff --git a/src/core/public/chrome/ui/header/_collapsible_nav.scss b/src/core/public/chrome/ui/header/collapsible_nav.scss similarity index 100% rename from src/core/public/chrome/ui/header/_collapsible_nav.scss rename to src/core/public/chrome/ui/header/collapsible_nav.scss diff --git a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx index 527f0df598c7c..cbc998b3965c5 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx @@ -17,164 +17,186 @@ * under the License. */ -import { mount, ReactWrapper } from 'enzyme'; -import React from 'react'; -import sinon from 'sinon'; -import { CollapsibleNav } from './collapsible_nav'; -import { DEFAULT_APP_CATEGORIES } from '../../..'; -import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; -import { NavLink, RecentNavLink } from './nav_link'; - -jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ - htmlIdGenerator: () => () => 'mockId', -})); - -const { kibana, observability, security, management } = DEFAULT_APP_CATEGORIES; - -function mockLink({ label = 'discover', category, onClick }: Partial) { - return { - key: label, - label, - href: label, - isActive: true, - onClick: onClick || (() => {}), - category, - 'data-test-subj': label, - }; -} - -function mockRecentNavLink({ label = 'recent', onClick }: Partial) { - return { - href: label, - label, - title: label, - 'aria-label': label, - onClick, - }; -} - -function mockProps() { - return { - id: 'collapsible-nav', - homeHref: '/', - isLocked: false, - isOpen: false, - navLinks: [], - recentNavLinks: [], - storage: new StubBrowserStorage(), - onIsOpenUpdate: () => {}, - onIsLockedUpdate: () => {}, - navigateToApp: () => {}, - }; -} - -function expectShownNavLinksCount(component: ReactWrapper, count: number) { - expect( - component.find('.euiAccordion-isOpen a[data-test-subj^="collapsibleNavAppLink"]').length - ).toEqual(count); -} - -function expectNavIsClosed(component: ReactWrapper) { - expectShownNavLinksCount(component, 0); -} - -function clickGroup(component: ReactWrapper, group: string) { - component.find(`[data-test-subj="collapsibleNavGroup-${group}"] button`).simulate('click'); -} - -describe('CollapsibleNav', () => { - // this test is mostly an "EUI works as expected" sanity check - it('renders the default nav', () => { - const onLock = sinon.spy(); - const component = mount(); - expect(component).toMatchSnapshot(); - - component.setProps({ isOpen: true }); - expect(component).toMatchSnapshot(); - - component.setProps({ isLocked: true }); - expect(component).toMatchSnapshot(); - - // limit the find to buttons because jest also renders data-test-subj on a JSX wrapper element - component.find('button[data-test-subj="collapsible-nav-lock"]').simulate('click'); - expect(onLock.callCount).toEqual(1); - }); - - it('renders links grouped by category', () => { - // just a test of category functionality, categories are not accurate - const navLinks = [ - mockLink({ label: 'discover', category: kibana }), - mockLink({ label: 'siem', category: security }), - mockLink({ label: 'metrics', category: observability }), - mockLink({ label: 'monitoring', category: management }), - mockLink({ label: 'visualize', category: kibana }), - mockLink({ label: 'dashboard', category: kibana }), - mockLink({ label: 'canvas' }), // links should be able to be rendered top level as well - mockLink({ label: 'logs', category: observability }), - ]; - const recentNavLinks = [ - mockRecentNavLink({ label: 'recent 1' }), - mockRecentNavLink({ label: 'recent 2' }), - ]; - const component = mount( - - ); - expect(component).toMatchSnapshot(); - }); - - it('remembers collapsible section state', () => { - const navLinks = [mockLink({ category: kibana }), mockLink({ category: observability })]; - const recentNavLinks = [mockRecentNavLink({})]; - const component = mount( - - ); - expectShownNavLinksCount(component, 3); - clickGroup(component, 'kibana'); - clickGroup(component, 'recentlyViewed'); - expectShownNavLinksCount(component, 1); - component.setProps({ isOpen: false }); - expectNavIsClosed(component); - component.setProps({ isOpen: true }); - expectShownNavLinksCount(component, 1); - }); - - it('closes the nav after clicking a link', () => { - const onClick = sinon.spy(); - const onIsOpenUpdate = sinon.spy(); - const navLinks = [mockLink({ category: kibana, onClick })]; - const recentNavLinks = [mockRecentNavLink({ onClick })]; - const component = mount( - - ); - component.setProps({ - onIsOpenUpdate: (isOpen: boolean) => { - component.setProps({ isOpen }); - onIsOpenUpdate(); - }, - }); - - component.find('[data-test-subj="collapsibleNavGroup-recentlyViewed"] a').simulate('click'); - expect(onClick.callCount).toEqual(1); - expect(onIsOpenUpdate.callCount).toEqual(1); - expectNavIsClosed(component); - component.setProps({ isOpen: true }); - component.find('[data-test-subj="collapsibleNavGroup-kibana"] a').simulate('click'); - expect(onClick.callCount).toEqual(2); - expect(onIsOpenUpdate.callCount).toEqual(2); - }); -}); +// /* +// * Licensed to Elasticsearch B.V. under one or more contributor +// * license agreements. See the NOTICE file distributed with +// * this work for additional information regarding copyright +// * ownership. Elasticsearch B.V. licenses this file to you under +// * the Apache License, Version 2.0 (the "License"); you may +// * not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, +// * software distributed under the License is distributed on an +// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// * KIND, either express or implied. See the License for the +// * specific language governing permissions and limitations +// * under the License. +// */ + +// import { mount, ReactWrapper } from 'enzyme'; +// import React from 'react'; +// import sinon from 'sinon'; +// import { CollapsibleNav } from './collapsible_nav'; +// import { DEFAULT_APP_CATEGORIES, ChromeNavLink } from '../../..'; +// import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; +// import { ChromeRecentlyAccessedHistoryItem } from '../../recently_accessed'; + +// jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ +// htmlIdGenerator: () => () => 'mockId', +// })); + +// const { kibana, observability, security, management } = DEFAULT_APP_CATEGORIES; + +// function mockLink({ label = 'discover', category, onClick }: Partial) { +// return { +// key: label, +// label, +// href: label, +// isActive: true, +// onClick: onClick || (() => {}), +// category, +// 'data-test-subj': label, +// }; +// } + +// function mockRecentNavLink({ +// label = 'recent', +// onClick, +// }: Partial) { +// return { +// href: label, +// label, +// title: label, +// 'aria-label': label, +// onClick, +// }; +// } + +// function mockProps() { +// return { +// id: 'collapsible-nav', +// homeHref: '/', +// isLocked: false, +// isOpen: false, +// navLinks: [], +// recentNavLinks: [], +// storage: new StubBrowserStorage(), +// onIsOpenUpdate: () => {}, +// onIsLockedUpdate: () => {}, +// navigateToApp: () => {}, +// }; +// } + +// function expectShownNavLinksCount(component: ReactWrapper, count: number) { +// expect( +// component.find('.euiAccordion-isOpen a[data-test-subj^="collapsibleNavAppLink"]').length +// ).toEqual(count); +// } + +// function expectNavIsClosed(component: ReactWrapper) { +// expectShownNavLinksCount(component, 0); +// } + +// function clickGroup(component: ReactWrapper, group: string) { +// component.find(`[data-test-subj="collapsibleNavGroup-${group}"] button`).simulate('click'); +// } + +// describe('CollapsibleNav', () => { +// // this test is mostly an "EUI works as expected" sanity check +// it('renders the default nav', () => { +// const onLock = sinon.spy(); +// const component = mount(); +// expect(component).toMatchSnapshot(); + +// component.setProps({ isOpen: true }); +// expect(component).toMatchSnapshot(); + +// component.setProps({ isLocked: true }); +// expect(component).toMatchSnapshot(); + +// // limit the find to buttons because jest also renders data-test-subj on a JSX wrapper element +// component.find('button[data-test-subj="collapsible-nav-lock"]').simulate('click'); +// expect(onLock.callCount).toEqual(1); +// }); + +// it('renders links grouped by category', () => { +// // just a test of category functionality, categories are not accurate +// const navLinks = [ +// mockLink({ label: 'discover', category: kibana }), +// mockLink({ label: 'siem', category: security }), +// mockLink({ label: 'metrics', category: observability }), +// mockLink({ label: 'monitoring', category: management }), +// mockLink({ label: 'visualize', category: kibana }), +// mockLink({ label: 'dashboard', category: kibana }), +// mockLink({ label: 'canvas' }), // links should be able to be rendered top level as well +// mockLink({ label: 'logs', category: observability }), +// ]; +// const recentNavLinks = [ +// mockRecentNavLink({ label: 'recent 1' }), +// mockRecentNavLink({ label: 'recent 2' }), +// ]; +// const component = mount( +// +// ); +// expect(component).toMatchSnapshot(); +// }); + +// it('remembers collapsible section state', () => { +// const navLinks = [mockLink({ category: kibana }), mockLink({ category: observability })]; +// const recentNavLinks = [mockRecentNavLink({})]; +// const component = mount( +// +// ); +// expectShownNavLinksCount(component, 3); +// clickGroup(component, 'kibana'); +// clickGroup(component, 'recentlyViewed'); +// expectShownNavLinksCount(component, 1); +// component.setProps({ isOpen: false }); +// expectNavIsClosed(component); +// component.setProps({ isOpen: true }); +// expectShownNavLinksCount(component, 1); +// }); + +// it('closes the nav after clicking a link', () => { +// const onClick = sinon.spy(); +// const onIsOpenUpdate = sinon.spy(); +// const navLinks = [mockLink({ category: kibana, onClick })]; +// const recentNavLinks = [mockRecentNavLink({ onClick })]; +// const component = mount( +// +// ); +// component.setProps({ +// onIsOpenUpdate: (isOpen: boolean) => { +// component.setProps({ isOpen }); +// onIsOpenUpdate(); +// }, +// }); + +// component.find('[data-test-subj="collapsibleNavGroup-recentlyViewed"] a').simulate('click'); +// expect(onClick.callCount).toEqual(1); +// expect(onIsOpenUpdate.callCount).toEqual(1); +// expectNavIsClosed(component); +// component.setProps({ isOpen: true }); +// component.find('[data-test-subj="collapsibleNavGroup-kibana"] a').simulate('click'); +// expect(onClick.callCount).toEqual(2); +// expect(onIsOpenUpdate.callCount).toEqual(2); +// }); +// }); diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index 60463d8dccc9b..6cb90ff7f77ca 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -30,11 +30,17 @@ import { import { i18n } from '@kbn/i18n'; import { groupBy, sortBy } from 'lodash'; import React, { useRef } from 'react'; +import * as Rx from 'rxjs'; +import { useObservable } from 'react-use'; import { AppCategory } from '../../../../types'; import { OnIsLockedUpdate } from './'; -import { NavLink, RecentNavLink } from './nav_link'; +import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem } from '../..'; +import { createRecentNavLink, createEuiListItem } from './nav_link'; +import { HttpStart } from '../../../http'; +import { InternalApplicationStart } from '../../../application/types'; +import './collapsible_nav.scss'; -function getAllCategories(allCategorizedLinks: Record) { +function getAllCategories(allCategorizedLinks: Record) { const allCategories = {} as Record; for (const [key, value] of Object.entries(allCategorizedLinks)) { @@ -45,7 +51,7 @@ function getAllCategories(allCategorizedLinks: Record) { } function getOrderedCategories( - mainCategories: Record, + mainCategories: Record, categoryDictionary: ReturnType ) { return sortBy( @@ -69,35 +75,53 @@ function setIsCategoryOpen(id: string, isOpen: boolean, storage: Storage) { } interface Props { + appId$: InternalApplicationStart['currentAppId$']; + basePath: HttpStart['basePath']; + id: string; isLocked: boolean; isOpen: boolean; - navLinks: NavLink[]; - recentNavLinks: RecentNavLink[]; homeHref: string; - id: string; + legacyMode: boolean; + navLinks$: Rx.Observable; + recentlyAccessed$: Rx.Observable; storage?: Storage; onIsLockedUpdate: OnIsLockedUpdate; - onIsOpenUpdate: (isOpen?: boolean) => void; - navigateToApp: (appId: string) => void; + closeNav: () => void; + navigateToApp: InternalApplicationStart['navigateToApp']; } export function CollapsibleNav({ + basePath, + id, isLocked, isOpen, - navLinks, - recentNavLinks, - onIsLockedUpdate, - onIsOpenUpdate, homeHref, - id, - navigateToApp, + legacyMode, storage = window.localStorage, + onIsLockedUpdate, + closeNav, + navigateToApp, + ...observables }: Props) { + const navLinks = useObservable(observables.navLinks$, []).filter(link => !link.hidden); + const recentlyAccessed = useObservable(observables.recentlyAccessed$, []); + const appId = useObservable(observables.appId$, ''); const lockRef = useRef(null); const groupedNavLinks = groupBy(navLinks, link => link?.category?.id); const { undefined: unknowns = [], ...allCategorizedLinks } = groupedNavLinks; const categoryDictionary = getAllCategories(allCategorizedLinks); const orderedCategories = getOrderedCategories(allCategorizedLinks, categoryDictionary); + const readyForEUI = (link: ChromeNavLink, needsIcon: boolean = false) => { + return createEuiListItem({ + link, + legacyMode, + appId, + dataTestSubj: 'collapsibleNavAppLink', + navigateToApp, + onClick: closeNav, + ...(needsIcon && { basePath }), + }); + }; return ( {/* Pinned items */} @@ -127,7 +151,7 @@ export function CollapsibleNav({ iconType: 'home', href: homeHref, onClick: (event: React.MouseEvent) => { - onIsOpenUpdate(false); + closeNav(); if ( event.isDefaultPrevented() || event.altKey || @@ -159,21 +183,24 @@ export function CollapsibleNav({ onToggle={isCategoryOpen => setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)} data-test-subj="collapsibleNavGroup-recentlyViewed" > - {recentNavLinks.length > 0 ? ( + {recentlyAccessed.length > 0 ? ( {}, ...link }) => ({ - 'data-test-subj': 'collapsibleNavAppLink--recent', - onClick: (e: React.MouseEvent) => { - onIsOpenUpdate(false); - onClick(e); - }, - ...link, - }))} + listItems={recentlyAccessed.map(link => { + // TODO #64541 + // Can remove icon from recent links completely + const { iconType, ...hydratedLink } = createRecentNavLink(link, navLinks, basePath); + + return { + ...hydratedLink, + 'data-test-subj': 'collapsibleNavAppLink--recent', + onClick: (e: React.MouseEvent) => { + closeNav(); + }, + }; + })} maxWidth="none" color="subdued" gutterSize="none" @@ -195,21 +222,8 @@ export function CollapsibleNav({ {/* Kibana, Observability, Security, and Management sections */} - {orderedCategories.map((categoryName, i) => { + {orderedCategories.map(categoryName => { const category = categoryDictionary[categoryName]!; - const links = allCategorizedLinks[categoryName].map( - ({ label, href, isActive, isDisabled, onClick }) => ({ - label, - href, - isActive, - isDisabled, - 'data-test-subj': 'collapsibleNavAppLink', - onClick: (e: React.MouseEvent) => { - onIsOpenUpdate(false); - onClick(e); - }, - }) - ); return ( readyForEUI(link))} maxWidth="none" color="subdued" gutterSize="none" @@ -237,23 +251,10 @@ export function CollapsibleNav({ })} {/* Things with no category (largely for custom plugins) */} - {unknowns.map(({ label, href, icon, isActive, isDisabled, onClick }, i) => ( + {unknowns.map((link, i) => ( - ) => { - onIsOpenUpdate(false); - onClick(e); - }} - /> + ))} diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx new file mode 100644 index 0000000000000..7784a790dc0ee --- /dev/null +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// import { mount } from 'enzyme'; +// import React from 'react'; +// import { Header } from './header'; + +// function mockProps() { +// return { +// kibanaVersion: '1.0.0', +// // application: InternalApplicationStart, +// // appTitle$: Rx.Observable, +// // badge$: Rx.Observable, +// // breadcrumbs$: Rx.Observable, +// // homeHref: string, +// // isVisible$: Rx.Observable, +// // kibanaDocLink: string, +// // navLinks$: Rx.Observable, +// // recentlyAccessed$: Rx.Observable, +// // forceAppSwitcherNavigation$: Rx.Observable, +// // helpExtension$: Rx.Observable, +// // helpSupportUrl$: Rx.Observable, +// // legacyMode: boolean, +// // navControlsLeft$: Rx.Observable, +// // navControlsRight$: Rx.Observable, +// // basePath: HttpStart['basePath'], +// // isLocked$: Rx.Observable, +// // navType$: Rx.Observable, +// // onIsLockedUpdate: OnIsLockedUpdate, +// }; +// } + +// describe('Header', () => { +// it('renders an empty header', () => { +// const component = mount(
); +// expect(component).toMatchSnapshot(); +// }); +// }); diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index 09bc3972e0e40..f7ee1edebbf2a 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -29,8 +29,9 @@ import { htmlIdGenerator, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { Component, createRef } from 'react'; import classnames from 'classnames'; +import React, { createRef, useState } from 'react'; +import { useObservable } from 'react-use'; import * as Rx from 'rxjs'; import { ChromeBadge, @@ -42,15 +43,15 @@ import { import { InternalApplicationStart } from '../../../application/types'; import { HttpStart } from '../../../http'; import { ChromeHelpExtension } from '../../chrome_service'; -import { HeaderBadge } from './header_badge'; import { NavType, OnIsLockedUpdate } from './'; +import { CollapsibleNav } from './collapsible_nav'; +import { HeaderBadge } from './header_badge'; import { HeaderBreadcrumbs } from './header_breadcrumbs'; import { HeaderHelpMenu } from './header_help_menu'; -import { HeaderNavControls } from './header_nav_controls'; -import { createNavLink, createRecentNavLink } from './nav_link'; import { HeaderLogo } from './header_logo'; +import { HeaderNavControls } from './header_nav_controls'; import { NavDrawer } from './nav_drawer'; -import { CollapsibleNav } from './collapsible_nav'; +import { LoadingIndicator } from '../'; export interface HeaderProps { kibanaVersion: string; @@ -72,162 +73,72 @@ export interface HeaderProps { basePath: HttpStart['basePath']; isLocked$: Rx.Observable; navType$: Rx.Observable; + loadingCount$: ReturnType; onIsLockedUpdate: OnIsLockedUpdate; } -interface State { - appTitle: string; - isVisible: boolean; - navLinks: ChromeNavLink[]; - recentlyAccessed: ChromeRecentlyAccessedHistoryItem[]; - forceNavigation: boolean; - navControlsLeft: readonly ChromeNavControl[]; - navControlsRight: readonly ChromeNavControl[]; - currentAppId: string | undefined; - isLocked: boolean; - navType: NavType; - isOpen: boolean; +function renderMenuTrigger(toggleOpen: () => void) { + return ( + + + + ); } -export class Header extends Component { - private subscription?: Rx.Subscription; - private navDrawerRef = createRef(); - private toggleCollapsibleNavRef = createRef(); - - constructor(props: HeaderProps) { - super(props); - - let isLocked = false; - props.isLocked$.subscribe(initialIsLocked => (isLocked = initialIsLocked)); - - this.state = { - appTitle: 'Kibana', - isVisible: true, - navLinks: [], - recentlyAccessed: [], - forceNavigation: false, - navControlsLeft: [], - navControlsRight: [], - currentAppId: '', - isLocked, - navType: 'modern', - isOpen: false, - }; +export function Header({ + kibanaVersion, + kibanaDocLink, + legacyMode, + application, + basePath, + onIsLockedUpdate, + homeHref, + ...observables +}: HeaderProps) { + const isVisible = useObservable(observables.isVisible$, true); + const navType = useObservable(observables.navType$, 'modern'); + const isLocked = useObservable(observables.isLocked$, false); + const [isOpen, setIsOpen] = useState(false); + + if (!isVisible) { + return ; } - public componentDidMount() { - this.subscription = Rx.combineLatest( - this.props.appTitle$, - this.props.isVisible$, - this.props.forceAppSwitcherNavigation$, - this.props.navLinks$, - this.props.recentlyAccessed$, - // Types for combineLatest only handle up to 6 inferred types so we combine these separately. - Rx.combineLatest( - this.props.navControlsLeft$, - this.props.navControlsRight$, - this.props.application.currentAppId$, - this.props.isLocked$, - this.props.navType$ - ) - ).subscribe({ - next: ([ - appTitle, - isVisible, - forceNavigation, - navLinks, - recentlyAccessed, - [navControlsLeft, navControlsRight, currentAppId, isLocked, navType], - ]) => { - this.setState({ - appTitle, - isVisible, - forceNavigation, - navLinks: navLinks.filter(navLink => !navLink.hidden), - recentlyAccessed, - navControlsLeft, - navControlsRight, - currentAppId, - isLocked, - navType, - }); - }, - }); - } - - public componentWillUnmount() { - if (this.subscription) { - this.subscription.unsubscribe(); + const navDrawerRef = createRef(); + const toggleCollapsibleNavRef = createRef(); + const navId = htmlIdGenerator()(); + const className = classnames( + 'chrHeaderWrapper', // TODO #64541 - delete this + 'hide-for-sharing', + { + 'chrHeaderWrapper--navIsLocked': isLocked, + headerWrapper: navType === 'modern', } - } - - public renderMenuTrigger() { - return ( - this.navDrawerRef.current.toggleOpen()} - > - - - ); - } - - public render() { - const { appTitle, isVisible, navControlsLeft, navControlsRight } = this.state; - const { - badge$, - breadcrumbs$, - helpExtension$, - helpSupportUrl$, - kibanaDocLink, - kibanaVersion, - } = this.props; - const navLinks = this.state.navLinks.map(link => - createNavLink( - link, - this.props.legacyMode, - this.state.currentAppId, - this.props.basePath, - this.props.application.navigateToApp - ) - ); - const recentNavLinks = this.state.recentlyAccessed.map(link => - createRecentNavLink(link, this.state.navLinks, this.props.basePath) - ); + ); - if (!isVisible) { - return null; - } - - const className = classnames( - 'chrHeaderWrapper', // TODO #64541 - delete this - 'hide-for-sharing', - { - 'chrHeaderWrapper--navIsLocked': this.state.isLocked, - headerWrapper: this.state.navType === 'modern', - } - ); - const navId = htmlIdGenerator()(); - return ( + return ( + <> +
- {this.state.navType === 'modern' ? ( + {navType === 'modern' ? ( { - this.setState({ isOpen: !this.state.isOpen }); - }} - aria-expanded={this.state.isOpen} - aria-pressed={this.state.isOpen} + onClick={() => setIsOpen(!isOpen)} + aria-expanded={isOpen} + aria-pressed={isOpen} aria-controls={navId} - ref={this.toggleCollapsibleNavRef} + ref={toggleCollapsibleNavRef} > @@ -237,71 +148,79 @@ export class Header extends Component { // Delete this block - {this.renderMenuTrigger()} + {renderMenuTrigger(() => navDrawerRef.current.toggleOpen())} )} - + - + - + - + - {this.state.navType === 'modern' ? ( + {navType === 'modern' ? ( { - this.setState({ isOpen }); - if (this.toggleCollapsibleNavRef.current) { - this.toggleCollapsibleNavRef.current.focus(); + isLocked={isLocked} + navLinks$={observables.navLinks$} + recentlyAccessed$={observables.recentlyAccessed$} + isOpen={isOpen} + homeHref={homeHref} + basePath={basePath} + legacyMode={legacyMode} + navigateToApp={application.navigateToApp} + onIsLockedUpdate={onIsLockedUpdate} + closeNav={() => { + setIsOpen(false); + if (toggleCollapsibleNavRef.current) { + toggleCollapsibleNavRef.current.focus(); } }} - navigateToApp={this.props.application.navigateToApp} /> ) : ( // TODO #64541 // Delete this block )}
- ); - } + + ); } diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx index 83840cff45d03..ad7b53f03daf5 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx @@ -18,87 +18,36 @@ */ import classNames from 'classnames'; -import React, { Component } from 'react'; +import React from 'react'; import * as Rx from 'rxjs'; import { EuiHeaderBreadcrumbs } from '@elastic/eui'; +import { useObservable } from 'react-use'; import { ChromeBreadcrumb } from '../../chrome_service'; interface Props { - appTitle?: string; + appTitle$: Rx.Observable; breadcrumbs$: Rx.Observable; } -interface State { - breadcrumbs: ChromeBreadcrumb[]; -} - -export class HeaderBreadcrumbs extends Component { - private subscription?: Rx.Subscription; - - constructor(props: Props) { - super(props); - - this.state = { breadcrumbs: [] }; - } - - public componentDidMount() { - this.subscribe(); - } - - public componentDidUpdate(prevProps: Props) { - if (prevProps.breadcrumbs$ === this.props.breadcrumbs$) { - return; - } - - this.unsubscribe(); - this.subscribe(); - } +export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$ }: Props) { + const appTitle = useObservable(appTitle$, 'Kibana'); + const breadcrumbs = useObservable(breadcrumbs$, []); + let crumbs = breadcrumbs; - public componentWillUnmount() { - this.unsubscribe(); + if (breadcrumbs.length === 0 && appTitle) { + crumbs = [{ text: appTitle }]; } - public render() { - return ( - - ); - } + crumbs = crumbs.map((breadcrumb, i) => ({ + ...breadcrumb, + 'data-test-subj': classNames( + 'breadcrumb', + breadcrumb['data-test-subj'], + i === 0 && 'first', + i === breadcrumbs.length - 1 && 'last' + ), + })); - private subscribe() { - this.subscription = this.props.breadcrumbs$.subscribe(breadcrumbs => { - this.setState({ - breadcrumbs, - }); - }); - } - - private unsubscribe() { - if (this.subscription) { - this.subscription.unsubscribe(); - delete this.subscription; - } - } - - private getBreadcrumbs() { - let breadcrumbs = this.state.breadcrumbs; - - if (breadcrumbs.length === 0 && this.props.appTitle) { - breadcrumbs = [{ text: this.props.appTitle }]; - } - - return breadcrumbs.map((breadcrumb, i) => ({ - ...breadcrumb, - 'data-test-subj': classNames( - 'breadcrumb', - breadcrumb['data-test-subj'], - i === 0 && 'first', - i === breadcrumbs.length - 1 && 'last' - ), - })); - } + return ; } diff --git a/src/core/public/chrome/ui/header/header_logo.tsx b/src/core/public/chrome/ui/header/header_logo.tsx index 4296064945455..518ea476728e5 100644 --- a/src/core/public/chrome/ui/header/header_logo.tsx +++ b/src/core/public/chrome/ui/header/header_logo.tsx @@ -21,7 +21,9 @@ import Url from 'url'; import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiHeaderLogo } from '@elastic/eui'; -import { NavLink } from './nav_link'; +import { useObservable } from 'react-use'; +import * as Rx from 'rxjs'; +import { ChromeNavLink } from '../..'; function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { let current = element; @@ -41,7 +43,7 @@ function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { function onClick( event: React.MouseEvent, forceNavigation: boolean, - navLinks: NavLink[], + navLinks: ChromeNavLink[], navigateToApp: (appId: string) => void ) { const anchor = findClosestAnchor((event as any).nativeEvent.target); @@ -50,7 +52,7 @@ function onClick( } const navLink = navLinks.find(item => item.href === anchor.href); - if (navLink && navLink.isDisabled) { + if (navLink && navLink.disabled) { event.preventDefault(); return; } @@ -85,12 +87,15 @@ function onClick( interface Props { href: string; - navLinks: NavLink[]; - forceNavigation: boolean; + navLinks$: Rx.Observable; + forceNavigation$: Rx.Observable; navigateToApp: (appId: string) => void; } -export function HeaderLogo({ href, forceNavigation, navLinks, navigateToApp }: Props) { +export function HeaderLogo({ href, navigateToApp, ...observables }: Props) { + const forceNavigation = useObservable(observables.forceNavigation$, false); + const navLinks = useObservable(observables.navLinks$, []); + return ( ; side: 'left' | 'right'; } -export class HeaderNavControls extends Component { - public render() { - const { navControls } = this.props; - - if (!navControls) { - return null; - } +export function HeaderNavControls({ navControls$, side }: Props) { + const navControls = useObservable(navControls$, []); - return navControls.map(this.renderNavControl); + if (!navControls) { + return null; } // It should be performant to use the index as the key since these are unlikely // to change while Kibana is running. - private renderNavControl = (navControl: ChromeNavControl, index: number) => ( - - - + return ( + <> + {navControls.map((navControl: ChromeNavControl, index: number) => ( + + + + ))} + ); } diff --git a/src/core/public/chrome/ui/header/nav_drawer.tsx b/src/core/public/chrome/ui/header/nav_drawer.tsx index 7faee8edea43b..cb2d1dbef5c90 100644 --- a/src/core/public/chrome/ui/header/nav_drawer.tsx +++ b/src/core/public/chrome/ui/header/nav_drawer.tsx @@ -21,21 +21,36 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; // @ts-ignore import { EuiNavDrawer, EuiHorizontalRule, EuiNavDrawerGroup } from '@elastic/eui'; +import * as Rx from 'rxjs'; +import { useObservable } from 'react-use'; import { OnIsLockedUpdate } from './'; -import { NavLink, RecentNavLink } from './nav_link'; import { RecentLinks } from './recent_links'; +import { HttpStart } from '../../../http'; +import { InternalApplicationStart } from '../../../application/types'; +import { createRecentNavLink, createEuiListItem } from './nav_link'; +import { ChromeNavLink, CoreStart, ChromeRecentlyAccessedHistoryItem } from '../../..'; export interface Props { + appId$: InternalApplicationStart['currentAppId$']; + basePath: HttpStart['basePath']; isLocked?: boolean; + legacyMode: boolean; + navLinks$: Rx.Observable; + recentlyAccessed$: Rx.Observable; + navigateToApp: CoreStart['application']['navigateToApp']; onIsLockedUpdate?: OnIsLockedUpdate; - navLinks: NavLink[]; - recentNavLinks: RecentNavLink[]; } -function navDrawerRenderer( - { isLocked, onIsLockedUpdate, navLinks, recentNavLinks }: Props, +function NavDrawerRenderer( + { isLocked, onIsLockedUpdate, basePath, legacyMode, navigateToApp, ...observables }: Props, ref: React.Ref ) { + const appId = useObservable(observables.appId$, ''); + const navLinks = useObservable(observables.navLinks$, []).filter(link => !link.hidden); + const recentNavLinks = useObservable(observables.recentlyAccessed$, []).map(link => + createRecentNavLink(link, navLinks, basePath) + ); + return ( + createEuiListItem({ + link, + legacyMode, + appId, + basePath, + navigateToApp, + dataTestSubj: 'navDrawerAppsMenuLink', + }) + )} aria-label={i18n.translate('core.ui.primaryNavList.screenReaderLabel', { defaultMessage: 'Primary navigation links', })} @@ -59,4 +83,4 @@ function navDrawerRenderer( ); } -export const NavDrawer = React.forwardRef(navDrawerRenderer); +export const NavDrawer = React.forwardRef(NavDrawerRenderer); diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index 8003c22b99a36..468e33bd9d954 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -20,9 +20,9 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiImage } from '@elastic/eui'; -import { AppCategory } from 'src/core/types'; -import { ChromeNavLink, CoreStart, ChromeRecentlyAccessedHistoryItem } from '../../../'; +import { ChromeNavLink, CoreStart, ChromeRecentlyAccessedHistoryItem } from '../../..'; import { HttpStart } from '../../../http'; +import { relativeToAbsolute } from '../../nav_links/to_nav_link'; function isModifiedEvent(event: React.MouseEvent) { return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); @@ -32,62 +32,32 @@ function LinkIcon({ url }: { url: string }) { return ; } -export interface NavLink { - key: string; - label: string; - href: string; - isActive: boolean; - onClick(event: React.MouseEvent): void; - category?: AppCategory; - isDisabled?: boolean; - iconType?: string; - icon?: JSX.Element; - order?: number; - 'data-test-subj': string; +interface Props { + link: ChromeNavLink; + legacyMode: boolean; + appId: string | undefined; + basePath?: HttpStart['basePath']; + dataTestSubj: string; + onClick?: Function; + navigateToApp: CoreStart['application']['navigateToApp']; } -/** - * Create a link that's actually ready to be passed into EUI - * - * @param navLink - * @param legacyMode - * @param currentAppId - * @param basePath - * @param navigateToApp - */ -export function createNavLink( - navLink: ChromeNavLink, - legacyMode: boolean, - currentAppId: string | undefined, - basePath: HttpStart['basePath'], - navigateToApp: CoreStart['application']['navigateToApp'] -): NavLink { - const { - legacy, - url, - active, - baseUrl, - id, - title, - disabled, - euiIconType, - icon, - category, - order, - tooltip, - } = navLink; - let href = navLink.url ?? navLink.baseUrl; - - if (legacy) { - href = url && !active ? url : baseUrl; - } +export function createEuiListItem({ + link, + legacyMode, + appId, + basePath, + onClick, + navigateToApp, + dataTestSubj, +}: Props) { + const { legacy, active, id, title, disabled, euiIconType, icon, tooltip, href } = link; return { - category, - key: id, label: tooltip ?? title, - href, // Use href and onClick to support "open in new tab" and SPA navigation in the same link - onClick(event) { + href, + /* Use href and onClick to support "open in new tab" and SPA navigation in the same link */ + onClick(event: React.MouseEvent) { if ( !legacyMode && // ignore when in legacy mode !legacy && // ignore links to legacy apps @@ -96,53 +66,29 @@ export function createNavLink( !isModifiedEvent(event) // ignore clicks with modifier keys ) { event.preventDefault(); - navigateToApp(navLink.id); + navigateToApp(id); + if (onClick) { + onClick(); + } } }, // Legacy apps use `active` property, NP apps should match the current app - isActive: active || currentAppId === id, + isActive: active || appId === id, isDisabled: disabled, - iconType: euiIconType, - icon: !euiIconType && icon ? : undefined, - order, - 'data-test-subj': 'navDrawerAppsMenuLink', + 'data-test-subj': dataTestSubj, + ...(basePath && { + iconType: euiIconType, + icon: !euiIconType && icon ? : undefined, + }), }; } -// Providing a buffer between the limit and the cut off index -// protects from truncating just the last couple (6) characters -const TRUNCATE_LIMIT: number = 64; -const TRUNCATE_AT: number = 58; - -function truncateRecentItemLabel(label: string): string { - if (label.length > TRUNCATE_LIMIT) { - label = `${label.substring(0, TRUNCATE_AT)}…`; - } - - return label; -} - -/** - * @param {string} url - a relative or root relative url. If a relative path is given then the - * absolute url returned will depend on the current page where this function is called from. For example - * if you are on page "http://www.mysite.com/shopping/kids" and you pass this function "adults", you would get - * back "http://www.mysite.com/shopping/adults". If you passed this function a root relative path, or one that - * starts with a "/", for example "/account/cart", you would get back "http://www.mysite.com/account/cart". - * @return {string} the relative url transformed into an absolute url - */ -function relativeToAbsolute(url: string) { - const a = document.createElement('a'); - a.setAttribute('href', url); - return a.href; -} - export interface RecentNavLink { href: string; label: string; title: string; 'aria-label': string; iconType?: string; - onClick?(event: React.MouseEvent): void; } /** @@ -176,7 +122,7 @@ export function createRecentNavLink( return { href, - label: truncateRecentItemLabel(label), + label, title: titleAndAriaLabel, 'aria-label': titleAndAriaLabel, iconType: navLink?.euiIconType, diff --git a/src/core/server/legacy/plugins/get_nav_links.ts b/src/core/server/legacy/plugins/get_nav_links.ts index 067fb204ca7f3..d8c7bbc83391b 100644 --- a/src/core/server/legacy/plugins/get_nav_links.ts +++ b/src/core/server/legacy/plugins/get_nav_links.ts @@ -38,6 +38,7 @@ function legacyAppToNavLink(spec: LegacyAppSpec): LegacyNavLink { euiIconType: spec.euiIconType, url: spec.url || `/app/${spec.id}`, linkToLastSubUrl: spec.linkToLastSubUrl ?? true, + href: undefined, }; } @@ -56,6 +57,7 @@ function legacyLinkToNavLink(spec: LegacyNavLinkSpec): LegacyNavLink { hidden: spec.hidden ?? false, disabled: spec.disabled ?? false, tooltip: spec.tooltip ?? '', + href: undefined, }; } From f63f3772ce5cffac6f6401a2ac66601c6ae714f6 Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Tue, 19 May 2020 20:15:21 -0400 Subject: [PATCH 02/11] fixing tests --- .../application/application_service.mock.ts | 1 + .../collapsible_nav.test.tsx.snap | 1010 +++++++++++++---- .../header_breadcrumbs.test.tsx.snap | 2 + .../chrome/ui/header/collapsible_nav.test.tsx | 350 +++--- .../chrome/ui/header/collapsible_nav.tsx | 6 +- .../public/chrome/ui/header/header.test.tsx | 64 +- .../ui/header/header_breadcrumbs.test.tsx | 19 +- src/core/public/chrome/ui/header/nav_link.tsx | 6 +- 8 files changed, 1035 insertions(+), 423 deletions(-) diff --git a/src/core/public/application/application_service.mock.ts b/src/core/public/application/application_service.mock.ts index d2a827d381be5..6e2af915713c9 100644 --- a/src/core/public/application/application_service.mock.ts +++ b/src/core/public/application/application_service.mock.ts @@ -34,6 +34,7 @@ const createSetupContractMock = (): jest.Mocked => ({ register: jest.fn(), registerAppUpdater: jest.fn(), registerMountContext: jest.fn(), + getComponent: jest.fn().mockImplementation(() => null), }); const createInternalSetupContractMock = (): jest.Mocked => ({ diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index 3e3d8c422b978..0b76e55435493 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -2,140 +2,295 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
  • @@ -1821,7 +1978,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` aria-label="recent 2" color="subdued" data-test-subj="collapsibleNavAppLink--recent" - href="recent 2" + href="http://localhost/recent%202" key="title-1" label="recent 2" onClick={[Function]} @@ -1837,7 +1994,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` aria-label="recent 2" className="euiListGroupItem__button" data-test-subj="collapsibleNavAppLink--recent" - href="recent 2" + href="http://localhost/recent%202" onClick={[Function]} title="recent 2" > @@ -2025,7 +2182,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` Object { "data-test-subj": "collapsibleNavAppLink", "href": "discover", - "isActive": true, + "isActive": false, "isDisabled": undefined, "label": "discover", "onClick": [Function], @@ -2033,7 +2190,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` Object { "data-test-subj": "collapsibleNavAppLink", "href": "visualize", - "isActive": true, + "isActive": false, "isDisabled": undefined, "label": "visualize", "onClick": [Function], @@ -2041,7 +2198,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` Object { "data-test-subj": "collapsibleNavAppLink", "href": "dashboard", - "isActive": true, + "isActive": false, "isDisabled": undefined, "label": "dashboard", "onClick": [Function], @@ -2064,7 +2221,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` color="subdued" data-test-subj="collapsibleNavAppLink" href="discover" - isActive={true} + isActive={false} key="title-0" label="discover" onClick={[Function]} @@ -2073,7 +2230,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` wrapText={false} >
  • First @@ -39,6 +40,7 @@ Array [ aria-current="page" className="euiBreadcrumb euiBreadcrumb--last" data-test-subj="breadcrumb last" + title="Second" > Second , diff --git a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx index cbc998b3965c5..5a734d55445a2 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx @@ -17,186 +17,170 @@ * under the License. */ -// /* -// * Licensed to Elasticsearch B.V. under one or more contributor -// * license agreements. See the NOTICE file distributed with -// * this work for additional information regarding copyright -// * ownership. Elasticsearch B.V. licenses this file to you under -// * the Apache License, Version 2.0 (the "License"); you may -// * not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, -// * software distributed under the License is distributed on an -// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// * KIND, either express or implied. See the License for the -// * specific language governing permissions and limitations -// * under the License. -// */ - -// import { mount, ReactWrapper } from 'enzyme'; -// import React from 'react'; -// import sinon from 'sinon'; -// import { CollapsibleNav } from './collapsible_nav'; -// import { DEFAULT_APP_CATEGORIES, ChromeNavLink } from '../../..'; -// import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; -// import { ChromeRecentlyAccessedHistoryItem } from '../../recently_accessed'; - -// jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ -// htmlIdGenerator: () => () => 'mockId', -// })); - -// const { kibana, observability, security, management } = DEFAULT_APP_CATEGORIES; - -// function mockLink({ label = 'discover', category, onClick }: Partial) { -// return { -// key: label, -// label, -// href: label, -// isActive: true, -// onClick: onClick || (() => {}), -// category, -// 'data-test-subj': label, -// }; -// } - -// function mockRecentNavLink({ -// label = 'recent', -// onClick, -// }: Partial) { -// return { -// href: label, -// label, -// title: label, -// 'aria-label': label, -// onClick, -// }; -// } - -// function mockProps() { -// return { -// id: 'collapsible-nav', -// homeHref: '/', -// isLocked: false, -// isOpen: false, -// navLinks: [], -// recentNavLinks: [], -// storage: new StubBrowserStorage(), -// onIsOpenUpdate: () => {}, -// onIsLockedUpdate: () => {}, -// navigateToApp: () => {}, -// }; -// } - -// function expectShownNavLinksCount(component: ReactWrapper, count: number) { -// expect( -// component.find('.euiAccordion-isOpen a[data-test-subj^="collapsibleNavAppLink"]').length -// ).toEqual(count); -// } - -// function expectNavIsClosed(component: ReactWrapper) { -// expectShownNavLinksCount(component, 0); -// } - -// function clickGroup(component: ReactWrapper, group: string) { -// component.find(`[data-test-subj="collapsibleNavGroup-${group}"] button`).simulate('click'); -// } - -// describe('CollapsibleNav', () => { -// // this test is mostly an "EUI works as expected" sanity check -// it('renders the default nav', () => { -// const onLock = sinon.spy(); -// const component = mount(); -// expect(component).toMatchSnapshot(); - -// component.setProps({ isOpen: true }); -// expect(component).toMatchSnapshot(); - -// component.setProps({ isLocked: true }); -// expect(component).toMatchSnapshot(); - -// // limit the find to buttons because jest also renders data-test-subj on a JSX wrapper element -// component.find('button[data-test-subj="collapsible-nav-lock"]').simulate('click'); -// expect(onLock.callCount).toEqual(1); -// }); - -// it('renders links grouped by category', () => { -// // just a test of category functionality, categories are not accurate -// const navLinks = [ -// mockLink({ label: 'discover', category: kibana }), -// mockLink({ label: 'siem', category: security }), -// mockLink({ label: 'metrics', category: observability }), -// mockLink({ label: 'monitoring', category: management }), -// mockLink({ label: 'visualize', category: kibana }), -// mockLink({ label: 'dashboard', category: kibana }), -// mockLink({ label: 'canvas' }), // links should be able to be rendered top level as well -// mockLink({ label: 'logs', category: observability }), -// ]; -// const recentNavLinks = [ -// mockRecentNavLink({ label: 'recent 1' }), -// mockRecentNavLink({ label: 'recent 2' }), -// ]; -// const component = mount( -// -// ); -// expect(component).toMatchSnapshot(); -// }); - -// it('remembers collapsible section state', () => { -// const navLinks = [mockLink({ category: kibana }), mockLink({ category: observability })]; -// const recentNavLinks = [mockRecentNavLink({})]; -// const component = mount( -// -// ); -// expectShownNavLinksCount(component, 3); -// clickGroup(component, 'kibana'); -// clickGroup(component, 'recentlyViewed'); -// expectShownNavLinksCount(component, 1); -// component.setProps({ isOpen: false }); -// expectNavIsClosed(component); -// component.setProps({ isOpen: true }); -// expectShownNavLinksCount(component, 1); -// }); - -// it('closes the nav after clicking a link', () => { -// const onClick = sinon.spy(); -// const onIsOpenUpdate = sinon.spy(); -// const navLinks = [mockLink({ category: kibana, onClick })]; -// const recentNavLinks = [mockRecentNavLink({ onClick })]; -// const component = mount( -// -// ); -// component.setProps({ -// onIsOpenUpdate: (isOpen: boolean) => { -// component.setProps({ isOpen }); -// onIsOpenUpdate(); -// }, -// }); - -// component.find('[data-test-subj="collapsibleNavGroup-recentlyViewed"] a').simulate('click'); -// expect(onClick.callCount).toEqual(1); -// expect(onIsOpenUpdate.callCount).toEqual(1); -// expectNavIsClosed(component); -// component.setProps({ isOpen: true }); -// component.find('[data-test-subj="collapsibleNavGroup-kibana"] a').simulate('click'); -// expect(onClick.callCount).toEqual(2); -// expect(onIsOpenUpdate.callCount).toEqual(2); -// }); -// }); +import { mount, ReactWrapper } from 'enzyme'; +import React from 'react'; +import { BehaviorSubject } from 'rxjs'; +import sinon from 'sinon'; +import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; +import { ChromeNavLink, DEFAULT_APP_CATEGORIES } from '../../..'; +import { httpServiceMock } from '../../../http/http_service.mock'; +import { ChromeRecentlyAccessedHistoryItem } from '../../recently_accessed'; +import { CollapsibleNav } from './collapsible_nav'; + +jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ + htmlIdGenerator: () => () => 'mockId', +})); + +const { kibana, observability, security, management } = DEFAULT_APP_CATEGORIES; + +function mockLink({ title = 'discover', category }: Partial) { + return { + title, + category, + id: title, + href: title, + baseUrl: '/', + legacy: false, + isActive: true, + 'data-test-subj': title, + }; +} + +function mockRecentNavLink({ label = 'recent' }: Partial) { + return { + label, + link: label, + id: label, + }; +} + +function mockProps() { + return { + appId$: new BehaviorSubject('test'), + basePath: httpServiceMock.createSetupContract({ basePath: '/test' }).basePath, + id: 'collapsibe-nav', + isLocked: false, + isOpen: false, + homeHref: '/', + legacyMode: false, + navLinks$: new BehaviorSubject([]), + recentlyAccessed$: new BehaviorSubject([]), + storage: new StubBrowserStorage(), + onIsLockedUpdate: () => {}, + closeNav: () => {}, + navigateToApp: () => Promise.resolve(), + }; +} + +function expectShownNavLinksCount(component: ReactWrapper, count: number) { + expect( + component.find('.euiAccordion-isOpen a[data-test-subj^="collapsibleNavAppLink"]').length + ).toEqual(count); +} + +function expectNavIsClosed(component: ReactWrapper) { + expectShownNavLinksCount(component, 0); +} + +function clickGroup(component: ReactWrapper, group: string) { + component.find(`[data-test-subj="collapsibleNavGroup-${group}"] button`).simulate('click'); +} + +describe('CollapsibleNav', () => { + // this test is mostly an "EUI works as expected" sanity check + it('renders the default nav', () => { + const onLock = sinon.spy(); + const component = mount(); + expect(component).toMatchSnapshot(); + + component.setProps({ isOpen: true }); + expect(component).toMatchSnapshot(); + + component.setProps({ isLocked: true }); + expect(component).toMatchSnapshot(); + + // limit the find to buttons because jest also renders data-test-subj on a JSX wrapper element + component.find('button[data-test-subj="collapsible-nav-lock"]').simulate('click'); + expect(onLock.callCount).toEqual(1); + }); + + it('renders links grouped by category', () => { + // just a test of category functionality, categories are not accurate + const navLinks = [ + mockLink({ title: 'discover', category: kibana }), + mockLink({ title: 'siem', category: security }), + mockLink({ title: 'metrics', category: observability }), + mockLink({ title: 'monitoring', category: management }), + mockLink({ title: 'visualize', category: kibana }), + mockLink({ title: 'dashboard', category: kibana }), + mockLink({ title: 'canvas' }), // links should be able to be rendered top level as well + mockLink({ title: 'logs', category: observability }), + ]; + const recentNavLinks = [ + mockRecentNavLink({ label: 'recent 1' }), + mockRecentNavLink({ label: 'recent 2' }), + ]; + const component = mount( + + ); + expect(component).toMatchSnapshot(); + }); + + it('remembers collapsible section state', () => { + const navLinks = [mockLink({ category: kibana }), mockLink({ category: observability })]; + const recentNavLinks = [mockRecentNavLink({})]; + const component = mount( + + ); + expectShownNavLinksCount(component, 3); + clickGroup(component, 'kibana'); + clickGroup(component, 'recentlyViewed'); + expectShownNavLinksCount(component, 1); + component.setProps({ isOpen: false }); + expectNavIsClosed(component); + component.setProps({ isOpen: true }); + expectShownNavLinksCount(component, 1); + }); + + it('closes the nav after clicking a link', () => { + const onClose = sinon.spy(); + const navLinks = [mockLink({ category: kibana }), mockLink({ title: 'categoryless' })]; + const recentNavLinks = [mockRecentNavLink({})]; + const component = mount( + + ); + component.setProps({ + closeNav: () => { + component.setProps({ isOpen: false }); + onClose(); + }, + }); + + component.find('[data-test-subj="collapsibleNavGroup-recentlyViewed"] a').simulate('click'); + expect(onClose.callCount).toEqual(1); + expectNavIsClosed(component); + component.setProps({ isOpen: true }); + component.find('[data-test-subj="collapsibleNavGroup-kibana"] a').simulate('click'); + expect(onClose.callCount).toEqual(2); + expectNavIsClosed(component); + component.setProps({ isOpen: true }); + component.find('[data-test-subj="collapsibleNavGroup-noCategory"] a').simulate('click'); + expect(onClose.callCount).toEqual(3); + expectNavIsClosed(component); + }); +}); diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index 6cb90ff7f77ca..e8af6160aae1a 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -196,9 +196,7 @@ export function CollapsibleNav({ return { ...hydratedLink, 'data-test-subj': 'collapsibleNavAppLink--recent', - onClick: (e: React.MouseEvent) => { - closeNav(); - }, + onClick: closeNav, }; })} maxWidth="none" @@ -252,7 +250,7 @@ export function CollapsibleNav({ {/* Things with no category (largely for custom plugins) */} {unknowns.map((link, i) => ( - + diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx index 7784a790dc0ee..9709a055e5b19 100644 --- a/src/core/public/chrome/ui/header/header.test.tsx +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -17,38 +17,46 @@ * under the License. */ -// import { mount } from 'enzyme'; -// import React from 'react'; -// import { Header } from './header'; +import React from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { NavType } from '.'; +import { httpServiceMock } from '../../../http/http_service.mock'; +import { applicationServiceMock } from '../../../mocks'; +import { Header } from './header'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; -// function mockProps() { -// return { -// kibanaVersion: '1.0.0', -// // application: InternalApplicationStart, -// // appTitle$: Rx.Observable, -// // badge$: Rx.Observable, -// // breadcrumbs$: Rx.Observable, -// // homeHref: string, -// // isVisible$: Rx.Observable, -// // kibanaDocLink: string, -// // navLinks$: Rx.Observable, -// // recentlyAccessed$: Rx.Observable, -// // forceAppSwitcherNavigation$: Rx.Observable, -// // helpExtension$: Rx.Observable, -// // helpSupportUrl$: Rx.Observable, -// // legacyMode: boolean, -// // navControlsLeft$: Rx.Observable, -// // navControlsRight$: Rx.Observable, -// // basePath: HttpStart['basePath'], -// // isLocked$: Rx.Observable, -// // navType$: Rx.Observable, -// // onIsLockedUpdate: OnIsLockedUpdate, -// }; -// } +function mockProps() { + const http = httpServiceMock.createSetupContract({ basePath: '/test' }); + const application = applicationServiceMock.createInternalStartContract(); + + return { + application, + kibanaVersion: '1.0.0', + appTitle$: new BehaviorSubject('test'), + badge$: new BehaviorSubject(undefined), + breadcrumbs$: new BehaviorSubject([]), + homeHref: '/', + isVisible$: new BehaviorSubject(true), + kibanaDocLink: '/docs', + navLinks$: new BehaviorSubject([]), + recentlyAccessed$: new BehaviorSubject([]), + forceAppSwitcherNavigation$: new BehaviorSubject(false), + helpExtension$: new BehaviorSubject(undefined), + helpSupportUrl$: new BehaviorSubject(''), + legacyMode: false, + navControlsLeft$: new BehaviorSubject([]), + navControlsRight$: new BehaviorSubject([]), + basePath: http.basePath, + isLocked$: new BehaviorSubject(false), + navType$: new BehaviorSubject('modern' as NavType), + loadingCount$: new BehaviorSubject(0), + onIsLockedUpdate: () => {}, + }; +} // describe('Header', () => { // it('renders an empty header', () => { -// const component = mount(
    ); +// const component = mountWithIntl(
    ); // expect(component).toMatchSnapshot(); // }); // }); diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx index 0398f162f9af9..f0af4d2b01a94 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx @@ -19,26 +19,23 @@ import { mount } from 'enzyme'; import React from 'react'; -import * as Rx from 'rxjs'; - -import { ChromeBreadcrumb } from '../../chrome_service'; +import { BehaviorSubject } from 'rxjs'; import { HeaderBreadcrumbs } from './header_breadcrumbs'; +import { act } from 'react-dom/test-utils'; describe('HeaderBreadcrumbs', () => { it('renders updates to the breadcrumbs$ observable', () => { - const breadcrumbs$ = new Rx.Subject(); - const wrapper = mount(); - - breadcrumbs$.next([{ text: 'First' }]); - // Unfortunately, enzyme won't update the wrapper until we call update. - wrapper.update(); + const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]); + const wrapper = mount( + + ); expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); - breadcrumbs$.next([{ text: 'First' }, { text: 'Second' }]); + act(() => breadcrumbs$.next([{ text: 'First' }, { text: 'Second' }])); wrapper.update(); expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); - breadcrumbs$.next([]); + act(() => breadcrumbs$.next([])); wrapper.update(); expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); }); diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index 468e33bd9d954..c56d799238a49 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -47,7 +47,7 @@ export function createEuiListItem({ legacyMode, appId, basePath, - onClick, + onClick = () => {}, navigateToApp, dataTestSubj, }: Props) { @@ -58,6 +58,7 @@ export function createEuiListItem({ href, /* Use href and onClick to support "open in new tab" and SPA navigation in the same link */ onClick(event: React.MouseEvent) { + onClick(); if ( !legacyMode && // ignore when in legacy mode !legacy && // ignore links to legacy apps @@ -67,9 +68,6 @@ export function createEuiListItem({ ) { event.preventDefault(); navigateToApp(id); - if (onClick) { - onClick(); - } } }, // Legacy apps use `active` property, NP apps should match the current app From a94ae19461c3555fb44a7d494af05676b8b23b0b Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Tue, 19 May 2020 20:26:46 -0400 Subject: [PATCH 03/11] api update --- .../kibana-plugin-core-public.chromenavlink.euiicontype.md | 2 +- .../core/public/kibana-plugin-core-public.chromenavlink.md | 3 ++- ...bana-plugin-core-public.chromenavlinkupdateablefields.md | 2 +- .../kibana-plugin-plugins-data-public.querystringinput.md | 2 +- .../public/kibana-plugin-plugins-data-public.searchbar.md | 4 ++-- src/core/public/public.api.md | 3 ++- src/plugins/data/public/public.api.md | 6 +++--- 7 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.euiicontype.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.euiicontype.md index fe95cb38cd97c..e30e8262f40b2 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.euiicontype.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.euiicontype.md @@ -4,7 +4,7 @@ ## ChromeNavLink.euiIconType property -A EUI iconType that will be used for the app's icon. This icon takes precendence over the `icon` property. +A EUI iconType that will be used for the app's icon. This icon takes precedence over the `icon` property. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md index a9fabb38df869..0349e865bff97 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md @@ -20,8 +20,9 @@ export interface ChromeNavLink | [category](./kibana-plugin-core-public.chromenavlink.category.md) | AppCategory | The category the app lives in | | [disabled](./kibana-plugin-core-public.chromenavlink.disabled.md) | boolean | Disables a link from being clickable. | | [disableSubUrlTracking](./kibana-plugin-core-public.chromenavlink.disablesuburltracking.md) | boolean | A flag that tells legacy chrome to ignore the link when tracking sub-urls | -| [euiIconType](./kibana-plugin-core-public.chromenavlink.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precendence over the icon property. | +| [euiIconType](./kibana-plugin-core-public.chromenavlink.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precedence over the icon property. | | [hidden](./kibana-plugin-core-public.chromenavlink.hidden.md) | boolean | Hides a link from the navigation. | +| [href](./kibana-plugin-core-public.chromenavlink.href.md) | string | Settled state between url, baseUrl, and active | | [icon](./kibana-plugin-core-public.chromenavlink.icon.md) | string | A URL to an image file used as an icon. Used as a fallback if euiIconType is not provided. | | [id](./kibana-plugin-core-public.chromenavlink.id.md) | string | A unique identifier for looking up links. | | [linkToLastSubUrl](./kibana-plugin-core-public.chromenavlink.linktolastsuburl.md) | boolean | Whether or not the subUrl feature should be enabled. | diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlinkupdateablefields.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlinkupdateablefields.md index 7f6dc7e0d5640..bd5a1399cded7 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlinkupdateablefields.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlinkupdateablefields.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type ChromeNavLinkUpdateableFields = Partial>; +export declare type ChromeNavLinkUpdateableFields = Partial>; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md index 58690300b3bd6..85eb4825bc2e3 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md @@ -7,5 +7,5 @@ Signature: ```typescript -QueryStringInput: React.FC> +QueryStringInput: React.FC> ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md index 0d5e0e42af27f..a32acae73f5ed 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md @@ -7,7 +7,7 @@ Signature: ```typescript -SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "indexPatterns" | "refreshInterval" | "customSubmitButton" | "screenTitle" | "dataTestSubj" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "onRefresh" | "timeHistory" | "onFiltersUpdated" | "onRefreshChange">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "indexPatterns" | "dataTestSubj" | "refreshInterval" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "onRefresh" | "timeHistory" | "onFiltersUpdated" | "onRefreshChange">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; } ``` diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 225ef611c0298..02017b22c0285 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -285,6 +285,7 @@ export interface ChromeNavLink { readonly disableSubUrlTracking?: boolean; readonly euiIconType?: string; readonly hidden?: boolean; + readonly href?: string; readonly icon?: string; readonly id: string; // @internal @@ -313,7 +314,7 @@ export interface ChromeNavLinks { } // @public (undocumented) -export type ChromeNavLinkUpdateableFields = Partial>; +export type ChromeNavLinkUpdateableFields = Partial>; // @public export interface ChromeRecentlyAccessed { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index e157d2ac6a522..23fa1dd001f39 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1364,7 +1364,7 @@ export interface QueryState { // Warning: (ae-missing-release-tag) "QueryStringInput" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const QueryStringInput: React.FC>; +export const QueryStringInput: React.FC>; // @public (undocumented) export type QuerySuggestion = QuerySuggestionBasic | QuerySuggestionField; @@ -1576,8 +1576,8 @@ export const search: { // Warning: (ae-missing-release-tag) "SearchBar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "indexPatterns" | "refreshInterval" | "customSubmitButton" | "screenTitle" | "dataTestSubj" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "onRefresh" | "timeHistory" | "onFiltersUpdated" | "onRefreshChange">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "indexPatterns" | "dataTestSubj" | "refreshInterval" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "onRefresh" | "timeHistory" | "onFiltersUpdated" | "onRefreshChange">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; }; // Warning: (ae-forgotten-export) The symbol "SearchBarOwnProps" needs to be exported by the entry point index.d.ts From 196639b694b2b9ece96c7d681196ef02198b05ab Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Wed, 20 May 2020 12:09:16 -0400 Subject: [PATCH 04/11] finishing up tests, fixing PR feedback... --- ...a-plugin-core-public.chromenavlink.href.md | 13 + .../application/application_service.mock.ts | 1 - .../header/__snapshots__/header.test.tsx.snap | 14265 ++++++++++++++++ .../chrome/ui/header/collapsible_nav.tsx | 10 +- .../public/chrome/ui/header/header.test.tsx | 65 +- src/core/public/chrome/ui/header/header.tsx | 30 +- .../ui/header/header_breadcrumbs.test.tsx | 2 +- .../chrome/ui/header/header_breadcrumbs.tsx | 9 +- .../public/chrome/ui/header/header_logo.tsx | 12 +- .../chrome/ui/header/header_nav_controls.tsx | 6 +- .../public/chrome/ui/header/nav_drawer.tsx | 20 +- src/core/public/chrome/ui/header/nav_link.tsx | 6 +- .../server/legacy/plugins/get_nav_links.ts | 2 - src/core/server/legacy/types.ts | 2 +- 14 files changed, 14384 insertions(+), 59 deletions(-) create mode 100644 docs/development/core/public/kibana-plugin-core-public.chromenavlink.href.md create mode 100644 src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.href.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.href.md new file mode 100644 index 0000000000000..a8af0c997ca78 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.href.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeNavLink](./kibana-plugin-core-public.chromenavlink.md) > [href](./kibana-plugin-core-public.chromenavlink.href.md) + +## ChromeNavLink.href property + +Settled state between `url`, `baseUrl`, and `active` + +Signature: + +```typescript +readonly href?: string; +``` diff --git a/src/core/public/application/application_service.mock.ts b/src/core/public/application/application_service.mock.ts index 6e2af915713c9..d2a827d381be5 100644 --- a/src/core/public/application/application_service.mock.ts +++ b/src/core/public/application/application_service.mock.ts @@ -34,7 +34,6 @@ const createSetupContractMock = (): jest.Mocked => ({ register: jest.fn(), registerAppUpdater: jest.fn(), registerMountContext: jest.fn(), - getComponent: jest.fn().mockImplementation(() => null), }); const createInternalSetupContractMock = (): jest.Mocked => ({ diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap new file mode 100644 index 0000000000000..9473f3c38e714 --- /dev/null +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -0,0 +1,14265 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Header renders 1`] = ` +
    + +
    +
    +
    + +
    +`; + +exports[`Header renders 2`] = ` +
    + +
    +`; + +exports[`Header renders 3`] = ` +
    + +
    +
    +
    + +
    + +
    + +
    + +
    + + + +
    +
    + +
    + + + + + + + + + + +
    + +
    + + + + + + } + closePopover={[Function]} + data-test-subj="helpMenuButton" + display="inlineBlock" + hasArrow={true} + id="headerHelpMenu" + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + repositionOnScroll={true} + > + +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + + + + + + +
    + +
    +
    +
    + + +
    + } + onActivation={[Function]} + onDeactivation={[Function]} + persistentFocus={false} + > + + +
    + } + onActivation={[Function]} + onDeactivation={[Function]} + persistentFocus={false} + /> + + +
    +
    + +
    + + + + + +
    +
    +`; + +exports[`Header renders 4`] = ` +
    + +
    +
    +
    + +
    + +
    + +
    + + +
    + + + +
    +
    +
    + +
    + + + + +
    + + + + +
    + + +
    + + + + + + + + + + +
    + +
    + + + + + + } + closePopover={[Function]} + data-test-subj="helpMenuButton" + display="inlineBlock" + hasArrow={true} + id="headerHelpMenu" + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + repositionOnScroll={true} + > + +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + + + + + + + +
    +
    +`; diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index e8af6160aae1a..30c4693b1af6b 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -30,15 +30,15 @@ import { import { i18n } from '@kbn/i18n'; import { groupBy, sortBy } from 'lodash'; import React, { useRef } from 'react'; -import * as Rx from 'rxjs'; import { useObservable } from 'react-use'; -import { AppCategory } from '../../../../types'; -import { OnIsLockedUpdate } from './'; +import * as Rx from 'rxjs'; import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem } from '../..'; -import { createRecentNavLink, createEuiListItem } from './nav_link'; -import { HttpStart } from '../../../http'; +import { AppCategory } from '../../../../types'; import { InternalApplicationStart } from '../../../application/types'; +import { HttpStart } from '../../../http'; +import { OnIsLockedUpdate } from './'; import './collapsible_nav.scss'; +import { createEuiListItem, createRecentNavLink } from './nav_link'; function getAllCategories(allCategorizedLinks: Record) { const allCategories = {} as Record; diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx index 9709a055e5b19..7fc6530b49051 100644 --- a/src/core/public/chrome/ui/header/header.test.tsx +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -18,12 +18,17 @@ */ import React from 'react'; +import { act } from 'react-dom/test-utils'; import { BehaviorSubject } from 'rxjs'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { NavType } from '.'; import { httpServiceMock } from '../../../http/http_service.mock'; import { applicationServiceMock } from '../../../mocks'; import { Header } from './header'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; + +interface LocalStore { + [key: string]: string; +} function mockProps() { const http = httpServiceMock.createSetupContract({ basePath: '/test' }); @@ -54,9 +59,55 @@ function mockProps() { }; } -// describe('Header', () => { -// it('renders an empty header', () => { -// const component = mountWithIntl(
    ); -// expect(component).toMatchSnapshot(); -// }); -// }); +describe('Header', () => { + beforeAll(() => { + const STORE: LocalStore = {}; + Object.defineProperty(window, 'localStorage', { + value: { + getItem: (key: string) => { + return STORE[key] || null; + }, + setItem: (key: string, value: any) => { + STORE[key] = value.toString(); + }, + }, + }); + }); + + it('renders', () => { + const isVisible$ = new BehaviorSubject(false); + const breadcrumbs$ = new BehaviorSubject([{ text: 'test' }]); + const isLocked$ = new BehaviorSubject(false); + const navType$ = new BehaviorSubject('modern' as NavType); + const navLinks$ = new BehaviorSubject([ + { id: 'kibana', title: 'kibana', baseUrl: '', legacy: false }, + ]); + const recentlyAccessed$ = new BehaviorSubject([ + { link: '', label: 'dashboard', id: 'dashboard' }, + ]); + const component = mountWithIntl( +
    + ); + expect(component).toMatchSnapshot(); + + act(() => isVisible$.next(true)); + component.update(); + expect(component).toMatchSnapshot(); + + act(() => isLocked$.next(true)); + component.update(); + expect(component).toMatchSnapshot(); + + act(() => navType$.next('legacy' as NavType)); + component.update(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index f7ee1edebbf2a..76f224ba62250 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -32,7 +32,8 @@ import { i18n } from '@kbn/i18n'; import classnames from 'classnames'; import React, { createRef, useState } from 'react'; import { useObservable } from 'react-use'; -import * as Rx from 'rxjs'; +import { Observable } from 'rxjs'; +import { LoadingIndicator } from '../'; import { ChromeBadge, ChromeBreadcrumb, @@ -51,28 +52,27 @@ import { HeaderHelpMenu } from './header_help_menu'; import { HeaderLogo } from './header_logo'; import { HeaderNavControls } from './header_nav_controls'; import { NavDrawer } from './nav_drawer'; -import { LoadingIndicator } from '../'; export interface HeaderProps { kibanaVersion: string; application: InternalApplicationStart; - appTitle$: Rx.Observable; - badge$: Rx.Observable; - breadcrumbs$: Rx.Observable; + appTitle$: Observable; + badge$: Observable; + breadcrumbs$: Observable; homeHref: string; - isVisible$: Rx.Observable; + isVisible$: Observable; kibanaDocLink: string; - navLinks$: Rx.Observable; - recentlyAccessed$: Rx.Observable; - forceAppSwitcherNavigation$: Rx.Observable; - helpExtension$: Rx.Observable; - helpSupportUrl$: Rx.Observable; + navLinks$: Observable; + recentlyAccessed$: Observable; + forceAppSwitcherNavigation$: Observable; + helpExtension$: Observable; + helpSupportUrl$: Observable; legacyMode: boolean; - navControlsLeft$: Rx.Observable; - navControlsRight$: Rx.Observable; + navControlsLeft$: Observable; + navControlsRight$: Observable; basePath: HttpStart['basePath']; - isLocked$: Rx.Observable; - navType$: Rx.Observable; + isLocked$: Observable; + navType$: Observable; loadingCount$: ReturnType; onIsLockedUpdate: OnIsLockedUpdate; } diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx index f0af4d2b01a94..7fe2c91087090 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx @@ -19,9 +19,9 @@ import { mount } from 'enzyme'; import React from 'react'; +import { act } from 'react-dom/test-utils'; import { BehaviorSubject } from 'rxjs'; import { HeaderBreadcrumbs } from './header_breadcrumbs'; -import { act } from 'react-dom/test-utils'; describe('HeaderBreadcrumbs', () => { it('renders updates to the breadcrumbs$ observable', () => { diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx index ad7b53f03daf5..174c46981db53 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx @@ -17,17 +17,16 @@ * under the License. */ +import { EuiHeaderBreadcrumbs } from '@elastic/eui'; import classNames from 'classnames'; import React from 'react'; -import * as Rx from 'rxjs'; - -import { EuiHeaderBreadcrumbs } from '@elastic/eui'; import { useObservable } from 'react-use'; +import { Observable } from 'rxjs'; import { ChromeBreadcrumb } from '../../chrome_service'; interface Props { - appTitle$: Rx.Observable; - breadcrumbs$: Rx.Observable; + appTitle$: Observable; + breadcrumbs$: Observable; } export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$ }: Props) { diff --git a/src/core/public/chrome/ui/header/header_logo.tsx b/src/core/public/chrome/ui/header/header_logo.tsx index 518ea476728e5..54d80c54ed183 100644 --- a/src/core/public/chrome/ui/header/header_logo.tsx +++ b/src/core/public/chrome/ui/header/header_logo.tsx @@ -17,12 +17,12 @@ * under the License. */ -import Url from 'url'; -import React from 'react'; -import { i18n } from '@kbn/i18n'; import { EuiHeaderLogo } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; import { useObservable } from 'react-use'; -import * as Rx from 'rxjs'; +import { Observable } from 'rxjs'; +import Url from 'url'; import { ChromeNavLink } from '../..'; function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { @@ -87,8 +87,8 @@ function onClick( interface Props { href: string; - navLinks$: Rx.Observable; - forceNavigation$: Rx.Observable; + navLinks$: Observable; + forceNavigation$: Observable; navigateToApp: (appId: string) => void; } diff --git a/src/core/public/chrome/ui/header/header_nav_controls.tsx b/src/core/public/chrome/ui/header/header_nav_controls.tsx index d6d5be05c4904..0941f7b27b662 100644 --- a/src/core/public/chrome/ui/header/header_nav_controls.tsx +++ b/src/core/public/chrome/ui/header/header_nav_controls.tsx @@ -17,15 +17,15 @@ * under the License. */ -import React from 'react'; import { EuiHeaderSectionItem } from '@elastic/eui'; -import * as Rx from 'rxjs'; +import React from 'react'; import { useObservable } from 'react-use'; +import { Observable } from 'rxjs'; import { ChromeNavControl } from '../../nav_controls'; import { HeaderExtension } from './header_extension'; interface Props { - navControls$: Rx.Observable; + navControls$: Observable; side: 'left' | 'right'; } diff --git a/src/core/public/chrome/ui/header/nav_drawer.tsx b/src/core/public/chrome/ui/header/nav_drawer.tsx index cb2d1dbef5c90..f6d5e36c857d0 100644 --- a/src/core/public/chrome/ui/header/nav_drawer.tsx +++ b/src/core/public/chrome/ui/header/nav_drawer.tsx @@ -17,26 +17,26 @@ * under the License. */ -import React from 'react'; -import { i18n } from '@kbn/i18n'; // @ts-ignore -import { EuiNavDrawer, EuiHorizontalRule, EuiNavDrawerGroup } from '@elastic/eui'; -import * as Rx from 'rxjs'; +import { EuiHorizontalRule, EuiNavDrawer, EuiNavDrawerGroup } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; import { useObservable } from 'react-use'; +import { Observable } from 'rxjs'; +import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem, CoreStart } from '../../..'; +import { InternalApplicationStart } from '../../../application/types'; +import { HttpStart } from '../../../http'; import { OnIsLockedUpdate } from './'; +import { createEuiListItem, createRecentNavLink } from './nav_link'; import { RecentLinks } from './recent_links'; -import { HttpStart } from '../../../http'; -import { InternalApplicationStart } from '../../../application/types'; -import { createRecentNavLink, createEuiListItem } from './nav_link'; -import { ChromeNavLink, CoreStart, ChromeRecentlyAccessedHistoryItem } from '../../..'; export interface Props { appId$: InternalApplicationStart['currentAppId$']; basePath: HttpStart['basePath']; isLocked?: boolean; legacyMode: boolean; - navLinks$: Rx.Observable; - recentlyAccessed$: Rx.Observable; + navLinks$: Observable; + recentlyAccessed$: Observable; navigateToApp: CoreStart['application']['navigateToApp']; onIsLockedUpdate?: OnIsLockedUpdate; } diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index c56d799238a49..1e24851c0242c 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -17,10 +17,10 @@ * under the License. */ -import React from 'react'; -import { i18n } from '@kbn/i18n'; import { EuiImage } from '@elastic/eui'; -import { ChromeNavLink, CoreStart, ChromeRecentlyAccessedHistoryItem } from '../../..'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem, CoreStart } from '../../..'; import { HttpStart } from '../../../http'; import { relativeToAbsolute } from '../../nav_links/to_nav_link'; diff --git a/src/core/server/legacy/plugins/get_nav_links.ts b/src/core/server/legacy/plugins/get_nav_links.ts index d8c7bbc83391b..067fb204ca7f3 100644 --- a/src/core/server/legacy/plugins/get_nav_links.ts +++ b/src/core/server/legacy/plugins/get_nav_links.ts @@ -38,7 +38,6 @@ function legacyAppToNavLink(spec: LegacyAppSpec): LegacyNavLink { euiIconType: spec.euiIconType, url: spec.url || `/app/${spec.id}`, linkToLastSubUrl: spec.linkToLastSubUrl ?? true, - href: undefined, }; } @@ -57,7 +56,6 @@ function legacyLinkToNavLink(spec: LegacyNavLinkSpec): LegacyNavLink { hidden: spec.hidden ?? false, disabled: spec.disabled ?? false, tooltip: spec.tooltip ?? '', - href: undefined, }; } diff --git a/src/core/server/legacy/types.ts b/src/core/server/legacy/types.ts index 2567ca790e04f..98f8d874c7088 100644 --- a/src/core/server/legacy/types.ts +++ b/src/core/server/legacy/types.ts @@ -151,7 +151,7 @@ export type LegacyAppSpec = Partial & { * @internal * @deprecated */ -export type LegacyNavLink = Omit & { +export type LegacyNavLink = Omit & { order: number; }; From 06ac35ea5816ba67dc285caaa74c228cfd6bb9ac Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Thu, 21 May 2020 15:54:10 -0400 Subject: [PATCH 05/11] stubbing test autogenerated ids --- .../header/__snapshots__/header.test.tsx.snap | 62 +++++++++---------- .../public/chrome/ui/header/header.test.tsx | 4 ++ 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index 9473f3c38e714..8dcca2db9fb88 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -2192,7 +2192,7 @@ exports[`Header renders 2`] = ` className="euiHeaderSectionItem euiHeaderSectionItem--borderRight header__toggleNavButtonSection" >

    Recently viewed

    @@ -9388,7 +9388,7 @@ exports[`Header renders 3`] = ` } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading" data-test-subj="collapsibleNavGroup-recentlyViewed" - id="ief311951-9ab2-11ea-a255-b1da15b4d3ca" + id="mockId" initialIsOpen={true} onToggle={[Function]} paddingSize="none" @@ -9402,7 +9402,7 @@ exports[`Header renders 3`] = ` className="euiAccordion__triggerWrapper" >
    ({ + htmlIdGenerator: () => () => 'mockId', +})); + interface LocalStore { [key: string]: string; } From 37ac9e438d179b2f31f7fff2afeda044b07cb608 Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Thu, 21 May 2020 19:29:15 -0400 Subject: [PATCH 06/11] header snapshot fix --- .../header/__snapshots__/header.test.tsx.snap | 58 ++++++++++++++----- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index 8dcca2db9fb88..4f59e88dbfe88 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -2191,7 +2191,7 @@ exports[`Header renders 2`] = `
    - - +
    - - +
    @@ -6273,7 +6274,7 @@ exports[`Header renders 3`] = `
    - - +
    - - +
    @@ -8807,6 +8809,7 @@ exports[`Header renders 3`] = `
    - @@ -11450,7 +11458,7 @@ exports[`Header renders 4`] = ` /> - +
    @@ -11619,6 +11627,7 @@ exports[`Header renders 4`] = ` data-test-subj="logo" href="/" onClick={[Function]} + rel="noreferrer" > - - +
    @@ -13902,7 +13911,26 @@ exports[`Header renders 4`] = ` } > +
    + + Collapse + + , + } + } className="navDrawerExpandButton-isExpanded" data-test-subj="navDrawerExpandButton-isExpanded" extraAction={ @@ -14232,6 +14260,7 @@ exports[`Header renders 4`] = ` @@ -14244,12 +14273,11 @@ exports[`Header renders 4`] = ` From 3b3b663af53dd49a09b565c5ec3a4c415591c3bd Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Wed, 27 May 2020 13:51:51 -0400 Subject: [PATCH 07/11] PR nit resolution --- .../public/chrome/ui/header/collapsible_nav.tsx | 2 +- src/core/public/chrome/ui/header/header.test.tsx | 15 ++------------- src/core/public/chrome/ui/header/nav_link.tsx | 6 +++--- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index 30c4693b1af6b..b62affc08c688 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -17,6 +17,7 @@ * under the License. */ +import './collapsible_nav.scss'; import { EuiCollapsibleNav, EuiCollapsibleNavGroup, @@ -37,7 +38,6 @@ import { AppCategory } from '../../../../types'; import { InternalApplicationStart } from '../../../application/types'; import { HttpStart } from '../../../http'; import { OnIsLockedUpdate } from './'; -import './collapsible_nav.scss'; import { createEuiListItem, createRecentNavLink } from './nav_link'; function getAllCategories(allCategorizedLinks: Record) { diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx index d1025dc94e892..13e1f6f086ae2 100644 --- a/src/core/public/chrome/ui/header/header.test.tsx +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -25,15 +25,12 @@ import { NavType } from '.'; import { httpServiceMock } from '../../../http/http_service.mock'; import { applicationServiceMock } from '../../../mocks'; import { Header } from './header'; +import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ htmlIdGenerator: () => () => 'mockId', })); -interface LocalStore { - [key: string]: string; -} - function mockProps() { const http = httpServiceMock.createSetupContract({ basePath: '/test' }); const application = applicationServiceMock.createInternalStartContract(); @@ -65,16 +62,8 @@ function mockProps() { describe('Header', () => { beforeAll(() => { - const STORE: LocalStore = {}; Object.defineProperty(window, 'localStorage', { - value: { - getItem: (key: string) => { - return STORE[key] || null; - }, - setItem: (key: string, value: any) => { - STORE[key] = value.toString(); - }, - }, + value: new StubBrowserStorage(), }); }); diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index 1e24851c0242c..76e54ddf2668a 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -17,7 +17,7 @@ * under the License. */ -import { EuiImage } from '@elastic/eui'; +import { EuiImage, EuiListGroupItemProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem, CoreStart } from '../../..'; @@ -50,7 +50,7 @@ export function createEuiListItem({ onClick = () => {}, navigateToApp, dataTestSubj, -}: Props) { +}: Props): EuiListGroupItemProps { const { legacy, active, id, title, disabled, euiIconType, icon, tooltip, href } = link; return { @@ -102,7 +102,7 @@ export function createRecentNavLink( recentLink: ChromeRecentlyAccessedHistoryItem, navLinks: ChromeNavLink[], basePath: HttpStart['basePath'] -) { +): EuiListGroupItemProps { const { link, label } = recentLink; const href = relativeToAbsolute(basePath.prepend(link)); const navLink = navLinks.find(nl => href.startsWith(nl.baseUrl ?? nl.subUrlBase)); From 4a045037aa53a679df75932b6bd025ed3506fe6d Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 27 May 2020 16:10:40 -0700 Subject: [PATCH 08/11] apply prettier to changed files --- src/core/public/chrome/chrome_service.tsx | 6 +++--- .../public/chrome/ui/header/collapsible_nav.tsx | 16 ++++++++-------- src/core/public/chrome/ui/header/header_logo.tsx | 4 ++-- src/core/public/chrome/ui/header/nav_drawer.tsx | 6 +++--- src/core/public/chrome/ui/header/nav_link.tsx | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index e3decb8ca671d..67cd43f0647e4 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -117,9 +117,9 @@ 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 => + flatMap((appId) => application.applications$.pipe( - map(applications => { + map((applications) => { return !!appId && applications.has(appId) && !!applications.get(appId)!.chromeless; }) ) @@ -258,7 +258,7 @@ export class ChromeService { getApplicationClasses$: () => applicationClasses$.pipe( - map(set => [...set]), + map((set) => [...set]), takeUntil(this.stop$) ), diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index b62affc08c688..9494e22920de8 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -56,7 +56,7 @@ function getOrderedCategories( ) { return sortBy( Object.keys(mainCategories), - categoryName => categoryDictionary[categoryName]?.order + (categoryName) => categoryDictionary[categoryName]?.order ); } @@ -103,11 +103,11 @@ export function CollapsibleNav({ navigateToApp, ...observables }: Props) { - const navLinks = useObservable(observables.navLinks$, []).filter(link => !link.hidden); + const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); const recentlyAccessed = useObservable(observables.recentlyAccessed$, []); const appId = useObservable(observables.appId$, ''); const lockRef = useRef(null); - const groupedNavLinks = groupBy(navLinks, link => link?.category?.id); + const groupedNavLinks = groupBy(navLinks, (link) => link?.category?.id); const { undefined: unknowns = [], ...allCategorizedLinks } = groupedNavLinks; const categoryDictionary = getAllCategories(allCategorizedLinks); const orderedCategories = getOrderedCategories(allCategorizedLinks, categoryDictionary); @@ -180,7 +180,7 @@ export function CollapsibleNav({ title={i18n.translate('core.ui.recentlyViewed', { defaultMessage: 'Recently viewed' })} isCollapsible={true} initialIsOpen={getIsCategoryOpen('recentlyViewed', storage)} - onToggle={isCategoryOpen => setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)} + onToggle={(isCategoryOpen) => setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)} data-test-subj="collapsibleNavGroup-recentlyViewed" > {recentlyAccessed.length > 0 ? ( @@ -188,7 +188,7 @@ export function CollapsibleNav({ aria-label={i18n.translate('core.ui.recentlyViewedAriaLabel', { defaultMessage: 'Recently viewed links', })} - listItems={recentlyAccessed.map(link => { + listItems={recentlyAccessed.map((link) => { // TODO #64541 // Can remove icon from recent links completely const { iconType, ...hydratedLink } = createRecentNavLink(link, navLinks, basePath); @@ -220,7 +220,7 @@ export function CollapsibleNav({ {/* Kibana, Observability, Security, and Management sections */} - {orderedCategories.map(categoryName => { + {orderedCategories.map((categoryName) => { const category = categoryDictionary[categoryName]!; return ( @@ -230,7 +230,7 @@ export function CollapsibleNav({ title={category.label} isCollapsible={true} initialIsOpen={getIsCategoryOpen(category.id, storage)} - onToggle={isCategoryOpen => setIsCategoryOpen(category.id, isCategoryOpen, storage)} + onToggle={(isCategoryOpen) => setIsCategoryOpen(category.id, isCategoryOpen, storage)} data-test-subj={`collapsibleNavGroup-${category.id}`} > readyForEUI(link))} + listItems={allCategorizedLinks[categoryName].map((link) => readyForEUI(link))} maxWidth="none" color="subdued" gutterSize="none" diff --git a/src/core/public/chrome/ui/header/header_logo.tsx b/src/core/public/chrome/ui/header/header_logo.tsx index 54d80c54ed183..9bec946b6b76e 100644 --- a/src/core/public/chrome/ui/header/header_logo.tsx +++ b/src/core/public/chrome/ui/header/header_logo.tsx @@ -51,7 +51,7 @@ function onClick( return; } - const navLink = navLinks.find(item => item.href === anchor.href); + const navLink = navLinks.find((item) => item.href === anchor.href); if (navLink && navLink.disabled) { event.preventDefault(); return; @@ -100,7 +100,7 @@ export function HeaderLogo({ href, navigateToApp, ...observables }: Props) { onClick(e, forceNavigation, navLinks, navigateToApp)} + onClick={(e) => onClick(e, forceNavigation, navLinks, navigateToApp)} href={href} aria-label={i18n.translate('core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel', { defaultMessage: 'Go to home page', diff --git a/src/core/public/chrome/ui/header/nav_drawer.tsx b/src/core/public/chrome/ui/header/nav_drawer.tsx index 26bf07ef3690f..ee4bff6cc0ac4 100644 --- a/src/core/public/chrome/ui/header/nav_drawer.tsx +++ b/src/core/public/chrome/ui/header/nav_drawer.tsx @@ -45,8 +45,8 @@ function NavDrawerRenderer( ref: React.Ref ) { const appId = useObservable(observables.appId$, ''); - const navLinks = useObservable(observables.navLinks$, []).filter(link => !link.hidden); - const recentNavLinks = useObservable(observables.recentlyAccessed$, []).map(link => + const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); + const recentNavLinks = useObservable(observables.recentlyAccessed$, []).map((link) => createRecentNavLink(link, navLinks, basePath) ); @@ -64,7 +64,7 @@ function NavDrawerRenderer( + listItems={navLinks.map((link) => createEuiListItem({ link, legacyMode, diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index 76e54ddf2668a..a55c63a412d61 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -105,7 +105,7 @@ export function createRecentNavLink( ): EuiListGroupItemProps { const { link, label } = recentLink; const href = relativeToAbsolute(basePath.prepend(link)); - const navLink = navLinks.find(nl => href.startsWith(nl.baseUrl ?? nl.subUrlBase)); + const navLink = navLinks.find((nl) => href.startsWith(nl.baseUrl ?? nl.subUrlBase)); let titleAndAriaLabel = label; if (navLink) { From 125735cc2a31c5f87f870ca0fb10bc4018cff275 Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Wed, 27 May 2020 22:09:02 -0400 Subject: [PATCH 09/11] fixing types --- .../public/chrome/ui/header/collapsible_nav.tsx | 16 ++++++++-------- .../public/chrome/ui/header/header_badge.tsx | 2 +- src/core/public/chrome/ui/header/header_logo.tsx | 4 ++-- src/core/public/chrome/ui/header/nav_drawer.tsx | 6 +++--- src/core/public/chrome/ui/header/nav_link.tsx | 13 +++++++++---- 5 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index 9494e22920de8..b62affc08c688 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -56,7 +56,7 @@ function getOrderedCategories( ) { return sortBy( Object.keys(mainCategories), - (categoryName) => categoryDictionary[categoryName]?.order + categoryName => categoryDictionary[categoryName]?.order ); } @@ -103,11 +103,11 @@ export function CollapsibleNav({ navigateToApp, ...observables }: Props) { - const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); + const navLinks = useObservable(observables.navLinks$, []).filter(link => !link.hidden); const recentlyAccessed = useObservable(observables.recentlyAccessed$, []); const appId = useObservable(observables.appId$, ''); const lockRef = useRef(null); - const groupedNavLinks = groupBy(navLinks, (link) => link?.category?.id); + const groupedNavLinks = groupBy(navLinks, link => link?.category?.id); const { undefined: unknowns = [], ...allCategorizedLinks } = groupedNavLinks; const categoryDictionary = getAllCategories(allCategorizedLinks); const orderedCategories = getOrderedCategories(allCategorizedLinks, categoryDictionary); @@ -180,7 +180,7 @@ export function CollapsibleNav({ title={i18n.translate('core.ui.recentlyViewed', { defaultMessage: 'Recently viewed' })} isCollapsible={true} initialIsOpen={getIsCategoryOpen('recentlyViewed', storage)} - onToggle={(isCategoryOpen) => setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)} + onToggle={isCategoryOpen => setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)} data-test-subj="collapsibleNavGroup-recentlyViewed" > {recentlyAccessed.length > 0 ? ( @@ -188,7 +188,7 @@ export function CollapsibleNav({ aria-label={i18n.translate('core.ui.recentlyViewedAriaLabel', { defaultMessage: 'Recently viewed links', })} - listItems={recentlyAccessed.map((link) => { + listItems={recentlyAccessed.map(link => { // TODO #64541 // Can remove icon from recent links completely const { iconType, ...hydratedLink } = createRecentNavLink(link, navLinks, basePath); @@ -220,7 +220,7 @@ export function CollapsibleNav({ {/* Kibana, Observability, Security, and Management sections */} - {orderedCategories.map((categoryName) => { + {orderedCategories.map(categoryName => { const category = categoryDictionary[categoryName]!; return ( @@ -230,7 +230,7 @@ export function CollapsibleNav({ title={category.label} isCollapsible={true} initialIsOpen={getIsCategoryOpen(category.id, storage)} - onToggle={(isCategoryOpen) => setIsCategoryOpen(category.id, isCategoryOpen, storage)} + onToggle={isCategoryOpen => setIsCategoryOpen(category.id, isCategoryOpen, storage)} data-test-subj={`collapsibleNavGroup-${category.id}`} > readyForEUI(link))} + listItems={allCategorizedLinks[categoryName].map(link => readyForEUI(link))} maxWidth="none" color="subdued" gutterSize="none" diff --git a/src/core/public/chrome/ui/header/header_badge.tsx b/src/core/public/chrome/ui/header/header_badge.tsx index d2d5e5d663300..4e529ad9a410b 100644 --- a/src/core/public/chrome/ui/header/header_badge.tsx +++ b/src/core/public/chrome/ui/header/header_badge.tsx @@ -77,7 +77,7 @@ export class HeaderBadge extends Component { } private subscribe() { - this.subscription = this.props.badge$.subscribe((badge) => { + this.subscription = this.props.badge$.subscribe(badge => { this.setState({ badge, }); diff --git a/src/core/public/chrome/ui/header/header_logo.tsx b/src/core/public/chrome/ui/header/header_logo.tsx index 9bec946b6b76e..54d80c54ed183 100644 --- a/src/core/public/chrome/ui/header/header_logo.tsx +++ b/src/core/public/chrome/ui/header/header_logo.tsx @@ -51,7 +51,7 @@ function onClick( return; } - const navLink = navLinks.find((item) => item.href === anchor.href); + const navLink = navLinks.find(item => item.href === anchor.href); if (navLink && navLink.disabled) { event.preventDefault(); return; @@ -100,7 +100,7 @@ export function HeaderLogo({ href, navigateToApp, ...observables }: Props) { onClick(e, forceNavigation, navLinks, navigateToApp)} + onClick={e => onClick(e, forceNavigation, navLinks, navigateToApp)} href={href} aria-label={i18n.translate('core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel', { defaultMessage: 'Go to home page', diff --git a/src/core/public/chrome/ui/header/nav_drawer.tsx b/src/core/public/chrome/ui/header/nav_drawer.tsx index ee4bff6cc0ac4..26bf07ef3690f 100644 --- a/src/core/public/chrome/ui/header/nav_drawer.tsx +++ b/src/core/public/chrome/ui/header/nav_drawer.tsx @@ -45,8 +45,8 @@ function NavDrawerRenderer( ref: React.Ref ) { const appId = useObservable(observables.appId$, ''); - const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); - const recentNavLinks = useObservable(observables.recentlyAccessed$, []).map((link) => + const navLinks = useObservable(observables.navLinks$, []).filter(link => !link.hidden); + const recentNavLinks = useObservable(observables.recentlyAccessed$, []).map(link => createRecentNavLink(link, navLinks, basePath) ); @@ -64,7 +64,7 @@ function NavDrawerRenderer( + listItems={navLinks.map(link => createEuiListItem({ link, legacyMode, diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index a55c63a412d61..1de457eb8ae49 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -17,7 +17,7 @@ * under the License. */ -import { EuiImage, EuiListGroupItemProps } from '@elastic/eui'; +import { EuiImage } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem, CoreStart } from '../../..'; @@ -42,6 +42,10 @@ interface Props { navigateToApp: CoreStart['application']['navigateToApp']; } +// TODO #64541 +// Set return type to EuiListGroupItemProps +// Currently it's a subset of EuiListGroupItemProps+FlyoutMenuItem for CollapsibleNav and NavDrawer +// But FlyoutMenuItem isn't exported from EUI export function createEuiListItem({ link, legacyMode, @@ -50,7 +54,7 @@ export function createEuiListItem({ onClick = () => {}, navigateToApp, dataTestSubj, -}: Props): EuiListGroupItemProps { +}: Props) { const { legacy, active, id, title, disabled, euiIconType, icon, tooltip, href } = link; return { @@ -91,6 +95,7 @@ export interface RecentNavLink { /** * Add saved object type info to recently links + * TODO #64541 - set return type to EuiListGroupItemProps * * Recent nav links are similar to normal nav links but are missing some Kibana Platform magic and * because of legacy reasons have slightly different properties. @@ -102,10 +107,10 @@ export function createRecentNavLink( recentLink: ChromeRecentlyAccessedHistoryItem, navLinks: ChromeNavLink[], basePath: HttpStart['basePath'] -): EuiListGroupItemProps { +) { const { link, label } = recentLink; const href = relativeToAbsolute(basePath.prepend(link)); - const navLink = navLinks.find((nl) => href.startsWith(nl.baseUrl ?? nl.subUrlBase)); + const navLink = navLinks.find(nl => href.startsWith(nl.baseUrl ?? nl.subUrlBase)); let titleAndAriaLabel = label; if (navLink) { From 728bd7acff3c088c7c564328734d450535200370 Mon Sep 17 00:00:00 2001 From: spalger Date: Thu, 28 May 2020 15:28:52 -0700 Subject: [PATCH 10/11] fix eslint violations --- .../public/chrome/ui/header/collapsible_nav.tsx | 16 ++++++++-------- .../public/chrome/ui/header/header_badge.tsx | 2 +- src/core/public/chrome/ui/header/header_logo.tsx | 4 ++-- src/core/public/chrome/ui/header/nav_drawer.tsx | 6 +++--- src/core/public/chrome/ui/header/nav_link.tsx | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index b62affc08c688..9494e22920de8 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -56,7 +56,7 @@ function getOrderedCategories( ) { return sortBy( Object.keys(mainCategories), - categoryName => categoryDictionary[categoryName]?.order + (categoryName) => categoryDictionary[categoryName]?.order ); } @@ -103,11 +103,11 @@ export function CollapsibleNav({ navigateToApp, ...observables }: Props) { - const navLinks = useObservable(observables.navLinks$, []).filter(link => !link.hidden); + const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); const recentlyAccessed = useObservable(observables.recentlyAccessed$, []); const appId = useObservable(observables.appId$, ''); const lockRef = useRef(null); - const groupedNavLinks = groupBy(navLinks, link => link?.category?.id); + const groupedNavLinks = groupBy(navLinks, (link) => link?.category?.id); const { undefined: unknowns = [], ...allCategorizedLinks } = groupedNavLinks; const categoryDictionary = getAllCategories(allCategorizedLinks); const orderedCategories = getOrderedCategories(allCategorizedLinks, categoryDictionary); @@ -180,7 +180,7 @@ export function CollapsibleNav({ title={i18n.translate('core.ui.recentlyViewed', { defaultMessage: 'Recently viewed' })} isCollapsible={true} initialIsOpen={getIsCategoryOpen('recentlyViewed', storage)} - onToggle={isCategoryOpen => setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)} + onToggle={(isCategoryOpen) => setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)} data-test-subj="collapsibleNavGroup-recentlyViewed" > {recentlyAccessed.length > 0 ? ( @@ -188,7 +188,7 @@ export function CollapsibleNav({ aria-label={i18n.translate('core.ui.recentlyViewedAriaLabel', { defaultMessage: 'Recently viewed links', })} - listItems={recentlyAccessed.map(link => { + listItems={recentlyAccessed.map((link) => { // TODO #64541 // Can remove icon from recent links completely const { iconType, ...hydratedLink } = createRecentNavLink(link, navLinks, basePath); @@ -220,7 +220,7 @@ export function CollapsibleNav({ {/* Kibana, Observability, Security, and Management sections */} - {orderedCategories.map(categoryName => { + {orderedCategories.map((categoryName) => { const category = categoryDictionary[categoryName]!; return ( @@ -230,7 +230,7 @@ export function CollapsibleNav({ title={category.label} isCollapsible={true} initialIsOpen={getIsCategoryOpen(category.id, storage)} - onToggle={isCategoryOpen => setIsCategoryOpen(category.id, isCategoryOpen, storage)} + onToggle={(isCategoryOpen) => setIsCategoryOpen(category.id, isCategoryOpen, storage)} data-test-subj={`collapsibleNavGroup-${category.id}`} > readyForEUI(link))} + listItems={allCategorizedLinks[categoryName].map((link) => readyForEUI(link))} maxWidth="none" color="subdued" gutterSize="none" diff --git a/src/core/public/chrome/ui/header/header_badge.tsx b/src/core/public/chrome/ui/header/header_badge.tsx index 4e529ad9a410b..d2d5e5d663300 100644 --- a/src/core/public/chrome/ui/header/header_badge.tsx +++ b/src/core/public/chrome/ui/header/header_badge.tsx @@ -77,7 +77,7 @@ export class HeaderBadge extends Component { } private subscribe() { - this.subscription = this.props.badge$.subscribe(badge => { + this.subscription = this.props.badge$.subscribe((badge) => { this.setState({ badge, }); diff --git a/src/core/public/chrome/ui/header/header_logo.tsx b/src/core/public/chrome/ui/header/header_logo.tsx index 54d80c54ed183..9bec946b6b76e 100644 --- a/src/core/public/chrome/ui/header/header_logo.tsx +++ b/src/core/public/chrome/ui/header/header_logo.tsx @@ -51,7 +51,7 @@ function onClick( return; } - const navLink = navLinks.find(item => item.href === anchor.href); + const navLink = navLinks.find((item) => item.href === anchor.href); if (navLink && navLink.disabled) { event.preventDefault(); return; @@ -100,7 +100,7 @@ export function HeaderLogo({ href, navigateToApp, ...observables }: Props) { onClick(e, forceNavigation, navLinks, navigateToApp)} + onClick={(e) => onClick(e, forceNavigation, navLinks, navigateToApp)} href={href} aria-label={i18n.translate('core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel', { defaultMessage: 'Go to home page', diff --git a/src/core/public/chrome/ui/header/nav_drawer.tsx b/src/core/public/chrome/ui/header/nav_drawer.tsx index 26bf07ef3690f..ee4bff6cc0ac4 100644 --- a/src/core/public/chrome/ui/header/nav_drawer.tsx +++ b/src/core/public/chrome/ui/header/nav_drawer.tsx @@ -45,8 +45,8 @@ function NavDrawerRenderer( ref: React.Ref ) { const appId = useObservable(observables.appId$, ''); - const navLinks = useObservable(observables.navLinks$, []).filter(link => !link.hidden); - const recentNavLinks = useObservable(observables.recentlyAccessed$, []).map(link => + const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); + const recentNavLinks = useObservable(observables.recentlyAccessed$, []).map((link) => createRecentNavLink(link, navLinks, basePath) ); @@ -64,7 +64,7 @@ function NavDrawerRenderer( + listItems={navLinks.map((link) => createEuiListItem({ link, legacyMode, diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index 1de457eb8ae49..c09b15fac9bdb 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -110,7 +110,7 @@ export function createRecentNavLink( ) { const { link, label } = recentLink; const href = relativeToAbsolute(basePath.prepend(link)); - const navLink = navLinks.find(nl => href.startsWith(nl.baseUrl ?? nl.subUrlBase)); + const navLink = navLinks.find((nl) => href.startsWith(nl.baseUrl ?? nl.subUrlBase)); let titleAndAriaLabel = label; if (navLink) { From d6f21ba2a1b053fced484420f4405919ae1c808b Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Thu, 28 May 2020 21:17:33 -0400 Subject: [PATCH 11/11] jest test fix --- .../chrome/ui/header/__snapshots__/header.test.tsx.snap | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index 4f59e88dbfe88..b563c6d2a4ec4 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -43,6 +43,7 @@ exports[`Header renders 1`] = ` "getComponent": [MockFunction], "getUrlForApp": [MockFunction], "navigateToApp": [MockFunction], + "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], } } @@ -657,6 +658,7 @@ exports[`Header renders 2`] = ` "getComponent": [MockFunction], "getUrlForApp": [MockFunction], "navigateToApp": [MockFunction], + "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], } } @@ -4740,6 +4742,7 @@ exports[`Header renders 3`] = ` "getComponent": [MockFunction], "getUrlForApp": [MockFunction], "navigateToApp": [MockFunction], + "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], } } @@ -9895,6 +9898,7 @@ exports[`Header renders 4`] = ` "getComponent": [MockFunction], "getUrlForApp": [MockFunction], "navigateToApp": [MockFunction], + "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], } }