From 308b8f9a5e230e1329abf9b627ea30f3f1d6d34f Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 22 Mar 2023 17:22:44 -0700 Subject: [PATCH 01/41] shared ux navigation package --- .github/CODEOWNERS | 1 + package.json | 1 + .../shared-ux/chrome/navigation/README.mdx | 26 +++ .../shared-ux/chrome/navigation/constants.ts | 13 ++ packages/shared-ux/chrome/navigation/index.ts | 11 ++ .../chrome/navigation/jest.config.js | 13 ++ .../shared-ux/chrome/navigation/kibana.jsonc | 5 + .../chrome/navigation/mocks/index.ts | 11 ++ .../chrome/navigation/mocks/src/jest.ts | 23 +++ .../chrome/navigation/mocks/src/storybook.ts | 61 ++++++ .../shared-ux/chrome/navigation/package.json | 6 + .../chrome/navigation/src/elastic_mark.tsx | 23 +++ .../chrome/navigation/src/header_logo.scss | 4 + .../chrome/navigation/src/nav_items.tsx | 110 +++++++++++ .../navigation/src/navigation.stories.tsx | 63 ++++++ .../chrome/navigation/src/navigation.test.tsx | 30 +++ .../chrome/navigation/src/navigation.tsx | 187 ++++++++++++++++++ .../chrome/navigation/src/services.tsx | 67 +++++++ .../shared-ux/chrome/navigation/tsconfig.json | 25 +++ packages/shared-ux/chrome/navigation/types.ts | 133 +++++++++++++ tsconfig.base.json | 2 + yarn.lock | 4 + 22 files changed, 819 insertions(+) create mode 100644 packages/shared-ux/chrome/navigation/README.mdx create mode 100644 packages/shared-ux/chrome/navigation/constants.ts create mode 100644 packages/shared-ux/chrome/navigation/index.ts create mode 100644 packages/shared-ux/chrome/navigation/jest.config.js create mode 100644 packages/shared-ux/chrome/navigation/kibana.jsonc create mode 100644 packages/shared-ux/chrome/navigation/mocks/index.ts create mode 100644 packages/shared-ux/chrome/navigation/mocks/src/jest.ts create mode 100644 packages/shared-ux/chrome/navigation/mocks/src/storybook.ts create mode 100644 packages/shared-ux/chrome/navigation/package.json create mode 100644 packages/shared-ux/chrome/navigation/src/elastic_mark.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/header_logo.scss create mode 100644 packages/shared-ux/chrome/navigation/src/nav_items.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/navigation.stories.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/navigation.test.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/navigation.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/services.tsx create mode 100644 packages/shared-ux/chrome/navigation/tsconfig.json create mode 100644 packages/shared-ux/chrome/navigation/types.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9b3a1b7a7909b..9fd242f4a71c2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -581,6 +581,7 @@ packages/shared-ux/button_toolbar @elastic/appex-sharedux packages/shared-ux/card/no_data/impl @elastic/appex-sharedux packages/shared-ux/card/no_data/mocks @elastic/appex-sharedux packages/shared-ux/card/no_data/types @elastic/appex-sharedux +packages/shared-ux/chrome/navigation @elastic/appex-sharedux packages/shared-ux/file/context @elastic/appex-sharedux packages/shared-ux/file/image/impl @elastic/appex-sharedux packages/shared-ux/file/image/mocks @elastic/appex-sharedux diff --git a/package.json b/package.json index b7dc6a0b647d6..5c473a14e8adc 100644 --- a/package.json +++ b/package.json @@ -582,6 +582,7 @@ "@kbn/shared-ux-card-no-data": "link:packages/shared-ux/card/no_data/impl", "@kbn/shared-ux-card-no-data-mocks": "link:packages/shared-ux/card/no_data/mocks", "@kbn/shared-ux-card-no-data-types": "link:packages/shared-ux/card/no_data/types", + "@kbn/shared-ux-chrome-navigation": "link:packages/shared-ux/chrome/navigation", "@kbn/shared-ux-file-context": "link:packages/shared-ux/file/context", "@kbn/shared-ux-file-image": "link:packages/shared-ux/file/image/impl", "@kbn/shared-ux-file-image-mocks": "link:packages/shared-ux/file/image/mocks", diff --git a/packages/shared-ux/chrome/navigation/README.mdx b/packages/shared-ux/chrome/navigation/README.mdx new file mode 100644 index 0000000000000..1542dd3dc1ed5 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/README.mdx @@ -0,0 +1,26 @@ +--- +id: sharedUX/Chrome/Navigation +slug: /shared-ux/chrome/navigation +title: Kibana Chrome Navigation +description: Navigation container to render items containing Locator IDs +tags: ['shared-ux', 'component', 'chrome', 'navigation'] +date: 2023-02-28 +--- + +## Description + +Empty package generated by @kbn/generate +@kbn/shared-ux-chrome-navigation +TODO tsullivan + +## API + +| Export | Description | +|---|---| +| `NavigationProvider` | Provides contextual services to `Navigation`. | +| `NavigationKibanaProvider` | Maps Kibana dependencies to provide contextual services to `Navigation`. | +| `Navigation` | Uses a `Provider` to access contextual services and render the component. | + +## EUI Promotion Status + +This component is not currently considered for promotion to EUI. diff --git a/packages/shared-ux/chrome/navigation/constants.ts b/packages/shared-ux/chrome/navigation/constants.ts new file mode 100644 index 0000000000000..87ec5e21cb0de --- /dev/null +++ b/packages/shared-ux/chrome/navigation/constants.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const RECENTS_SECTION_KEY = 'recents'; +export const ANALYTICS_SECTION_KEY = 'analytics'; +export const ML_SECTION_KEY = 'ml'; +export const DEVTOOLS_SECTION_KEY = 'devTools'; +export const MANAGEMENT_SECTION_KEY = 'management'; diff --git a/packages/shared-ux/chrome/navigation/index.ts b/packages/shared-ux/chrome/navigation/index.ts new file mode 100644 index 0000000000000..a5493ab12481a --- /dev/null +++ b/packages/shared-ux/chrome/navigation/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { Navigation } from './src/navigation'; +export { NavigationKibanaProvider, NavigationProvider } from './src/services'; +export type { NavigationProps, NavigationServices, NavItemProps } from './types'; diff --git a/packages/shared-ux/chrome/navigation/jest.config.js b/packages/shared-ux/chrome/navigation/jest.config.js new file mode 100644 index 0000000000000..808dc82a089c2 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/packages/shared-ux/chrome/navigation'], +}; diff --git a/packages/shared-ux/chrome/navigation/kibana.jsonc b/packages/shared-ux/chrome/navigation/kibana.jsonc new file mode 100644 index 0000000000000..74cd1ccea3252 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/shared-ux-chrome-navigation", + "owner": "@elastic/appex-sharedux" +} diff --git a/packages/shared-ux/chrome/navigation/mocks/index.ts b/packages/shared-ux/chrome/navigation/mocks/index.ts new file mode 100644 index 0000000000000..a9536ce51309d --- /dev/null +++ b/packages/shared-ux/chrome/navigation/mocks/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { getServicesMock as getNavigationServicesMock } from './src/jest'; +export { StorybookMock as NavigationStorybookMock } from './src/storybook'; +export type { Params as NavigationStorybookParams } from './src/storybook'; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts new file mode 100644 index 0000000000000..28d787c8fc381 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { NavigationServices } from '../../types'; + +export const getServicesMock = (): NavigationServices => { + const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; + + return { + getLocator() { + return { + navigateSync: jest.fn(), + }; + }, + navIsOpen: true, + recentItems, + }; +}; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts new file mode 100644 index 0000000000000..a0fbd5c805e86 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock'; +import { SerializableRecord } from '@kbn/utility-types'; +import { action } from '@storybook/addon-actions'; +import { NavigationProps, NavigationServices } from '../../types'; +import { getMockNavItems, mockLocatorId } from './nav_items'; + +type Arguments = NavigationProps & NavigationServices; +export type Params = Pick; + +export class StorybookMock extends AbstractStorybookMock { + propArguments = {}; + + serviceArguments = { + navIsOpen: { + control: 'boolean', + defaultValue: true, + }, + }; + + dependencies = []; + + getServices(params: Params): NavigationServices { + const { navIsOpen, recentItems } = params; + const navAction = action('Navigation'); + + return { + getLocator(_locatorId: string) { + return { + navigateSync(locatorParams?: SerializableRecord) { + // show nav info with storybook add-on + navAction(`Locator: ${mockLocatorId} / Params: ${JSON.stringify(locatorParams)}`); + }, + }; + }, + navIsOpen, + recentItems, + }; + } + + getProps(params: Params): NavigationProps { + const { initiallyOpenSections } = params; + return { + id: 'example_project', + title: { + name: 'Example Project', + icon: 'logoObservability', + }, + sections: {}, + items: getMockNavItems(), + initiallyOpenSections, + }; + } +} diff --git a/packages/shared-ux/chrome/navigation/package.json b/packages/shared-ux/chrome/navigation/package.json new file mode 100644 index 0000000000000..312c11c6f7c1e --- /dev/null +++ b/packages/shared-ux/chrome/navigation/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/shared-ux-chrome-navigation", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/shared-ux/chrome/navigation/src/elastic_mark.tsx b/packages/shared-ux/chrome/navigation/src/elastic_mark.tsx new file mode 100644 index 0000000000000..b538eb5d1756c --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/elastic_mark.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { HTMLAttributes } from 'react'; + +export const ElasticMark = ({ ...props }: HTMLAttributes) => ( + + Elastic + + +); diff --git a/packages/shared-ux/chrome/navigation/src/header_logo.scss b/packages/shared-ux/chrome/navigation/src/header_logo.scss new file mode 100644 index 0000000000000..f75fd9cfa2466 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/header_logo.scss @@ -0,0 +1,4 @@ +.chrHeaderLogo__mark { + margin-left: $euiSizeS; + fill: $euiColorGhost; +} diff --git a/packages/shared-ux/chrome/navigation/src/nav_items.tsx b/packages/shared-ux/chrome/navigation/src/nav_items.tsx new file mode 100644 index 0000000000000..c0c3639012b28 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/nav_items.tsx @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const navItems = { + dataExploration: [ + { + name: '', + id: 'data_exploration_root', + items: [ + { name: 'Discover', id: 'discover' }, + { name: 'Dashboard', id: 'dashboard' }, + { name: 'Visualize Library', id: 'visualize_library' }, + ], + }, + ], + machineLearning: [ + { + name: '', + id: 'ml_root', + items: [ + { name: 'Overview', id: 'ml_1' }, + { name: 'Notifications', id: 'ml_2' }, + ], + }, + { + name: 'Anomaly detection', + id: 'anomaly_detection', + items: [ + { name: 'Jobs', id: 'ml_1' }, + { name: 'Anomaly explorer', id: 'ml_2' }, + { name: 'Single metric viewer', id: 'ml_2' }, + { name: 'Settings', id: 'ml_2' }, + ], + }, + { + name: 'Data frame analytics', + id: 'data_frame_analytics', + items: [ + { name: 'Jobs', id: 'ml_1' }, + { name: 'Results explorer', id: 'ml_2' }, + { name: 'Analytics map', id: 'ml_2' }, + ], + }, + { + name: 'Model management', + id: 'model_management', + items: [ + { name: 'Trained models', id: 'ml_1' }, + { name: 'Nodes', id: 'ml_2' }, + ], + }, + { + name: 'Data visualizer', + id: 'data_visualizer', + items: [ + { name: 'File', id: 'file' }, + { name: 'Data view', id: 'data_view' }, + ], + }, + { + name: 'AIOps labs', + id: 'aiops_labs', + items: [ + { name: 'Explain log rate spikes', id: 'explain_log_rate_spikes' }, + { name: 'Log pattern analysis', id: 'log_pattern_analysis' }, + ], + }, + ], + devTools: [ + { + name: '', + id: 'devtools_root', + items: [ + { name: 'Console', id: 'console' }, + { name: 'Search profiler', id: 'search_profiler' }, + { name: 'Grok debugger', id: 'grok_debugger' }, + { name: 'Painless lab', id: 'painless_lab' }, + ], + }, + ], + management: [ + { + name: 'Ingest', + id: 'management_ingest', + items: [ + { name: 'Ingest Pipelines', id: 'management_1' }, + { name: 'Logstash Pipelines', id: 'management_2' }, + ], + }, + { + name: 'Data', + id: 'management_data', + items: [ + { name: 'Index Management', id: 'management_1' }, + { name: 'Index Lifecycle Policies', id: 'management_2' }, + { name: 'Snapshot and Restore', id: 'management_2' }, + { name: 'Rollup Jobs', id: 'management_2' }, + { name: 'Transforms', id: 'management_2' }, + { name: 'Cross-Cluster Replication', id: 'management_2' }, + { name: 'Remote Clusters', id: 'management_2' }, + { name: 'Remote Clusters', id: 'management_2' }, + ], + }, + ], +}; diff --git a/packages/shared-ux/chrome/navigation/src/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/navigation.stories.tsx new file mode 100644 index 0000000000000..5084d2efa619c --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/navigation.stories.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import React from 'react'; +import { NavigationStorybookMock } from '../mocks'; +import mdx from '../README.mdx'; +import { NavigationProps, NavigationServices } from '../types'; +import { Navigation as Component } from './navigation'; +import { NavigationProvider } from './services'; + +const mock = new NavigationStorybookMock(); + +const Template = (args: NavigationProps & NavigationServices) => ( + + + +); + +export default { + title: 'Chrome/Navigation', + description: + 'An accordion-like component that renders a nested array of navigation items that use Locator information.', + parameters: { + docs: { + page: mdx, + }, + }, + component: Template, +} as ComponentMeta; + +export const SingleExpanded: ComponentStory = Template.bind({}); +SingleExpanded.args = { + initiallyOpenSections: ['example_project'], +}; +SingleExpanded.argTypes = mock.getArgumentTypes(); + +export const ReducedSections: ComponentStory = Template.bind({}); +ReducedSections.args = { + sections: { + analytics: { enabled: false }, + ml: { enabled: false }, + management: { enabled: false }, + devTools: { enabled: false }, + }, +}; +ReducedSections.argTypes = mock.getArgumentTypes(); + +export const WithRecentItems: ComponentStory = Template.bind({}); +WithRecentItems.args = { + initiallyOpenSections: ['example_project'], + recentItems: [ + { id: 'recent_1', label: 'The Elder Scrolls: Morrowind', link: 'testo' }, + { id: 'recent_1', label: 'TIE Fighter', link: 'testo' }, + { id: 'recent_1', label: 'Quake II', link: 'testo' }, + ], +}; +WithRecentItems.argTypes = mock.getArgumentTypes(); diff --git a/packages/shared-ux/chrome/navigation/src/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/navigation.test.tsx new file mode 100644 index 0000000000000..52d9fe2274877 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/navigation.test.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render } from 'react-dom'; +import { getServicesMock } from '../mocks/src/jest'; +import { Navigation } from './navigation'; +import { NavigationProvider } from './services'; + +describe('', () => { + test('renders with minimal props', () => { + const div = document.createElement('div'); + const { getLocator } = getServicesMock(); + const title = { name: 'Navigation testing', icon: 'gear' }; + + const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; + + render( + + + , + div + ); + }); +}); diff --git a/packages/shared-ux/chrome/navigation/src/navigation.tsx b/packages/shared-ux/chrome/navigation/src/navigation.tsx new file mode 100644 index 0000000000000..3b72d5b710e7c --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/navigation.tsx @@ -0,0 +1,187 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + EuiCollapsibleNavGroup, + EuiFlexGroup, + EuiFlexItem, + EuiHeaderLogo, + EuiIcon, + EuiSideNav, + EuiSideNavItemType, + EuiSpacer, + IconType, + useEuiTheme, +} from '@elastic/eui'; +import React from 'react'; +import { + ANALYTICS_SECTION_KEY, + DEVTOOLS_SECTION_KEY, + MANAGEMENT_SECTION_KEY, + ML_SECTION_KEY, + RECENTS_SECTION_KEY, +} from '../constants'; +import { ILocatorDefinition, NavigationProps, NavItemProps, RecentItem } from '../types'; +import { ElasticMark } from './elastic_mark'; +import './header_logo.scss'; +import { navItems } from './nav_items'; +import { useNavigation } from './services'; + +export const Navigation = (props: NavigationProps) => { + // const { euiTheme } = useEuiTheme(); + // const { fontSize: navSectionFontSize } = useEuiFontSize('m'); + // const { fontSize: navItemFontSize } = useEuiFontSize('s'); + const { getLocator, navIsOpen, recentItems } = useNavigation(); + + const { euiTheme } = useEuiTheme(); + + const locatorNavigation = (locator: ILocatorDefinition | undefined) => () => { + if (locator) { + const locatorInstance = getLocator(locator.id); + + if (!locatorInstance) { + throw new Error(`Unresolved Locator instance for ${locator.id}`); + } + + if (locator.params) { + locatorInstance.navigateSync(locator.params); + } + } + }; + + // implement onClick using item's locator params + const convertSolutionNavItemsToEuiSideNavItems = (items: NavItemProps[]) => { + return items.map((item) => { + const navItem: EuiSideNavItemType = { ...item }; + if (item.locator) { + navItem.onClick = locatorNavigation(item.locator); + } + if (item.items) { + // recurse + navItem.items = convertSolutionNavItemsToEuiSideNavItems(item.items); + } + return navItem; + }); + }; + + // implement "name" from item's label and onClick using item's "link" + const convertRecentItemsToEuiSideNavItems = (items: RecentItem[]) => [ + { + name: '', + id: 'recent_items_root', + items: items.map((item) => ({ + id: item.id, + name: item.label, + onClick: () => { + // FIXME not implemented + // console.log(`Go to ${item.link}`); + }, + })), + }, + ]; + + const renderBucket = ( + iconType: IconType, + title: React.ReactNode, + items: NavItemProps[], + bucketKey: keyof NavigationProps['sections'] | undefined, + solutionKey?: string + ) => { + // ability to turn off bucket completely with {[bucketKey]: { enabled: false }} + if (bucketKey && props.sections?.[bucketKey]?.enabled === false) { + return null; + } + + if (navIsOpen) { + return ( + + + + ); + } + + return ( +
+ +
+
+ ); + }; + + const isOpen = (sectionId?: string) => { + return sectionId ? props.initiallyOpenSections?.includes(sectionId) : false; + }; + + const { + id, + title: { icon, name }, + } = props; + + let euiSideNavRecentItems: Array> | undefined; + if (recentItems) { + euiSideNavRecentItems = convertRecentItemsToEuiSideNavItems(recentItems); + } + + const euiSideNavSolutionItems = props.items + ? convertSolutionNavItemsToEuiSideNavItems(props.items) + : []; + + return ( + <> + + + + e.preventDefault()} + aria-label="Go to home page" + /> + + {navIsOpen ? : null} + + {euiSideNavRecentItems + ? renderBucket('clock', 'Recent', euiSideNavRecentItems, RECENTS_SECTION_KEY) + : null} + {renderBucket(icon, name, euiSideNavSolutionItems, undefined, id)} + {renderBucket( + 'stats', + 'Data exploration', + navItems.dataExploration, + ANALYTICS_SECTION_KEY + )} + {renderBucket( + 'indexMapping', + 'Machine learning', + navItems.machineLearning, + ML_SECTION_KEY + )} + + + + + + + + {renderBucket( + 'editorCodeBlock', + 'Developer tools', + navItems.devTools, + DEVTOOLS_SECTION_KEY + )} + {renderBucket('gear', 'Management', navItems.management, MANAGEMENT_SECTION_KEY)} + + + + ); +}; diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx new file mode 100644 index 0000000000000..85fefcb936802 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FC, useContext } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { NavigationKibanaDependencies, NavigationServices } from '../types'; + +const Context = React.createContext(null); + +/** + * A Context Provider that provides services to the component and its dependencies. + */ +export const NavigationProvider: FC = ({ children, ...services }) => { + const { getLocator, navIsOpen, recentItems } = services; + return ( + {children} + ); +}; + +/** + * Kibana-specific Provider that maps dependencies to services. + */ +export const NavigationKibanaProvider: FC = ({ + children, + ...dependencies +}) => { + const navIsOpen = useObservable(dependencies.core.chrome.getProjectNavIsOpen$(), true); + const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; + + // FIXME + // const recentItems$ = dependencies.core.chrome.recentlyAccessed.get$(); + // const recentItems = useObservable(recentItems$, []); + + const value: NavigationServices = { + navIsOpen, + recentItems, + getLocator(id: string) { + return dependencies.share.url.locators.get(id); + }, + }; + + return ( + + {children} + + ); +}; + +/** + * React hook for accessing pre-wired services. + */ +export function useNavigation() { + const context = useContext(Context); + + if (!context) { + throw new Error( + 'Navigation Context is missing. Ensure your component or React root is wrapped with NavigationContext.' + ); + } + + return context; +} diff --git a/packages/shared-ux/chrome/navigation/tsconfig.json b/packages/shared-ux/chrome/navigation/tsconfig.json new file mode 100644 index 0000000000000..ff88face27cef --- /dev/null +++ b/packages/shared-ux/chrome/navigation/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + "@emotion/react/types/css-prop", + "@kbn/ambient-ui-types" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "kbn_references": [ + "@kbn/shared-ux-storybook-mock", + "@kbn/utility-types" + ], + "exclude": [ + "target/**/*" + ] +} + diff --git a/packages/shared-ux/chrome/navigation/types.ts b/packages/shared-ux/chrome/navigation/types.ts new file mode 100644 index 0000000000000..7aa925edfef1a --- /dev/null +++ b/packages/shared-ux/chrome/navigation/types.ts @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Observable } from 'rxjs'; +import type { EuiSideNavItemType, IconType } from '@elastic/eui'; +import type { SerializableRecord } from '@kbn/utility-types'; + +/** + * TODO move definition from the Share plugin to a package + * @private + */ +export interface ILocatorPublic { + navigateSync:

(params: P) => void; +} + +type GetLocatorFn = (id: string) => ILocatorPublic | undefined; + +/** + * @internal + */ +export interface RecentItem { + link: string; + label: string; + id: string; +} + +/** + * A list of services that are consumed by this component. + */ +export interface NavigationServices { + getLocator: GetLocatorFn; + navIsOpen: boolean; + recentItems: RecentItem[]; +} + +/** + * An interface containing a collection of Kibana dependencies required to + * render this component + */ +export interface NavigationKibanaDependencies { + share: { + url: { + locators: { get: GetLocatorFn }; + }; + }; + core: { + chrome: { + getProjectNavIsOpen$: () => Observable; + recentlyAccessed: { + get$: () => Observable; + }; + }; + }; +} + +/** + * Locator info used for navigating around Kibana + */ +export interface ILocatorDefinition

{ + /** + * ID of a registered LocatorDefinition + */ + id: string; + /** + * Navigational params in the form understood by the locator's plugin. + */ + params?: P; +} + +/** + * Props for the `NavItem` component representing the content of a navigational item with optional children. + * @public + */ +export type NavItemProps = Pick< + EuiSideNavItemType, + 'id' | 'name' | 'forceOpen' | 'isSelected' +> & { + items?: Array>; + /** + * ID of a registered LocatorDefinition + */ + locator?: ILocatorDefinition; +}; + +/** + * Props for the `Navigation` component. + */ +export interface NavigationProps { + /** + * IDs of Navigation sections that should be rendered initially open when the component is mounted + */ + initiallyOpenSections?: string[]; + /** + * Items of navigation content + */ + items?: NavItemProps[]; + /** + * ID of the item, for highlighting and showing as initially open + */ + id: string; + title: { + /** + * Name of the project, i.e. "Observability" + */ + name: React.ReactNode; + /** + * Solution logo, i.e. "logoObservability" + */ + icon: IconType; + }; + sections: { + recents?: { + enabled?: boolean; // default: true + }; + analytics?: { + enabled?: boolean; // default: true + }; + ml?: { + enabled?: boolean; // default: true + }; + devTools?: { + enabled?: boolean; // default: true + }; + management?: { + enabled?: boolean; // default: true + }; + }; +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 60c1f23d037b8..84a6b4443e5cc 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1156,6 +1156,8 @@ "@kbn/shared-ux-card-no-data-mocks/*": ["packages/shared-ux/card/no_data/mocks/*"], "@kbn/shared-ux-card-no-data-types": ["packages/shared-ux/card/no_data/types"], "@kbn/shared-ux-card-no-data-types/*": ["packages/shared-ux/card/no_data/types/*"], + "@kbn/shared-ux-chrome-navigation": ["packages/shared-ux/chrome/navigation"], + "@kbn/shared-ux-chrome-navigation/*": ["packages/shared-ux/chrome/navigation/*"], "@kbn/shared-ux-file-context": ["packages/shared-ux/file/context"], "@kbn/shared-ux-file-context/*": ["packages/shared-ux/file/context/*"], "@kbn/shared-ux-file-image": ["packages/shared-ux/file/image/impl"], diff --git a/yarn.lock b/yarn.lock index 38b7781f7b17a..b01f2066a0272 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5049,6 +5049,10 @@ version "0.0.0" uid "" +"@kbn/shared-ux-chrome-navigation@link:packages/shared-ux/chrome/navigation": + version "0.0.0" + uid "" + "@kbn/shared-ux-file-context@link:packages/shared-ux/file/context": version "0.0.0" uid "" From 45803c2a64312f1f3d124566b263db2fb29cafe3 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 31 Mar 2023 12:46:38 -0700 Subject: [PATCH 02/41] setNavIsOpen service --- .../shared-ux/chrome/navigation/constants.ts | 12 ++- .../chrome/navigation/mocks/src/nav_items.ts | 53 +++++++++++ .../chrome/navigation/mocks/src/storybook.ts | 23 ++--- .../chrome/navigation/src/nav_items.tsx | 40 +++----- .../navigation/src/navigation.stories.tsx | 93 +++++++++++++++---- .../chrome/navigation/src/navigation.test.tsx | 2 +- .../chrome/navigation/src/navigation.tsx | 37 ++------ .../chrome/navigation/src/services.tsx | 17 ++-- packages/shared-ux/chrome/navigation/types.ts | 5 +- 9 files changed, 182 insertions(+), 100 deletions(-) create mode 100644 packages/shared-ux/chrome/navigation/mocks/src/nav_items.ts diff --git a/packages/shared-ux/chrome/navigation/constants.ts b/packages/shared-ux/chrome/navigation/constants.ts index 87ec5e21cb0de..04e025d322b2a 100644 --- a/packages/shared-ux/chrome/navigation/constants.ts +++ b/packages/shared-ux/chrome/navigation/constants.ts @@ -6,8 +6,10 @@ * Side Public License, v 1. */ -export const RECENTS_SECTION_KEY = 'recents'; -export const ANALYTICS_SECTION_KEY = 'analytics'; -export const ML_SECTION_KEY = 'ml'; -export const DEVTOOLS_SECTION_KEY = 'devTools'; -export const MANAGEMENT_SECTION_KEY = 'management'; +export enum PLATFORM_SECTIONS { + RECENTS = 'recents', + ANALYTICS = 'analytics', + MACHINE_LEARNING = 'ml', + DEVTOOLS = 'devTools', + MANAGEMENT = 'management', +} diff --git a/packages/shared-ux/chrome/navigation/mocks/src/nav_items.ts b/packages/shared-ux/chrome/navigation/mocks/src/nav_items.ts new file mode 100644 index 0000000000000..a62582af215b3 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/mocks/src/nav_items.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { NavItemProps } from '../../types'; + +export const mockLocatorId = 'MOCK_LOCATOR_ID'; + +export const getMockNavItems = (): Array> => [ + { + id: 'root', + name: '', + forceOpen: true, + items: [ + { + id: 'get_started', + name: 'Get started', + locator: { id: mockLocatorId, params: { view: '/app/observability/overview' } }, + isSelected: true, + }, + { + id: 'alerts', + name: 'Alerts', + locator: { id: mockLocatorId, params: { view: '/app/observability/alerts' } }, + }, + { + id: 'cases', + name: 'Cases', + locator: { id: mockLocatorId, params: { view: '/app/observability/cases' } }, + }, + ], + }, + { + id: 'signals_root', + name: 'Signals', + items: [ + { + id: 'logs', + name: 'Logs', + locator: { id: mockLocatorId, params: { view: '/app/management/ingest/ingest_pipelines' } }, + }, + { + id: 'tracing', + name: 'Tracing', + locator: { id: mockLocatorId, params: { view: '/app/management/ingest/ingest_pipelines' } }, + }, + ], + }, +]; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index a0fbd5c805e86..5771481adf109 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -13,7 +13,10 @@ import { NavigationProps, NavigationServices } from '../../types'; import { getMockNavItems, mockLocatorId } from './nav_items'; type Arguments = NavigationProps & NavigationServices; -export type Params = Pick; +export type Params = Pick< + Arguments, + 'navIsOpen' | 'recentItems' | 'initiallyOpenSections' | 'platformSections' +>; export class StorybookMock extends AbstractStorybookMock { propArguments = {}; @@ -29,31 +32,29 @@ export class StorybookMock extends AbstractStorybookMock { + navAction(`Locator: ${mockLocatorId} / Params: ${JSON.stringify(locatorParams)}`); + }; + const getLocator = (_locatorId: string) => ({ navigateSync }); return { - getLocator(_locatorId: string) { - return { - navigateSync(locatorParams?: SerializableRecord) { - // show nav info with storybook add-on - navAction(`Locator: ${mockLocatorId} / Params: ${JSON.stringify(locatorParams)}`); - }, - }; - }, + getLocator, navIsOpen, recentItems, }; } getProps(params: Params): NavigationProps { - const { initiallyOpenSections } = params; + const { initiallyOpenSections, platformSections } = params; return { id: 'example_project', title: { name: 'Example Project', icon: 'logoObservability', }, - sections: {}, + platformSections, items: getMockNavItems(), initiallyOpenSections, }; diff --git a/packages/shared-ux/chrome/navigation/src/nav_items.tsx b/packages/shared-ux/chrome/navigation/src/nav_items.tsx index c0c3639012b28..d0880f267c0d1 100644 --- a/packages/shared-ux/chrome/navigation/src/nav_items.tsx +++ b/packages/shared-ux/chrome/navigation/src/nav_items.tsx @@ -23,35 +23,35 @@ export const navItems = { name: '', id: 'ml_root', items: [ - { name: 'Overview', id: 'ml_1' }, - { name: 'Notifications', id: 'ml_2' }, + { name: 'Overview', id: 'ml_overview' }, + { name: 'Notifications', id: 'ml_notifications' }, ], }, { name: 'Anomaly detection', id: 'anomaly_detection', items: [ - { name: 'Jobs', id: 'ml_1' }, - { name: 'Anomaly explorer', id: 'ml_2' }, - { name: 'Single metric viewer', id: 'ml_2' }, - { name: 'Settings', id: 'ml_2' }, + { name: 'Jobs', id: 'ml_anomaly_detection_jobs' }, + { name: 'Anomaly explorer', id: 'ml_anomaly_explorer' }, + { name: 'Single metric viewer', id: 'ml_single_metric_viewer' }, + { name: 'Settings', id: 'ml_settings' }, ], }, { name: 'Data frame analytics', id: 'data_frame_analytics', items: [ - { name: 'Jobs', id: 'ml_1' }, - { name: 'Results explorer', id: 'ml_2' }, - { name: 'Analytics map', id: 'ml_2' }, + { name: 'Jobs', id: 'ml_data_frame_analytics_jobs' }, + { name: 'Results explorer', id: 'ml_results_explorer' }, + { name: 'Analytics map', id: 'ml_analytics_map' }, ], }, { name: 'Model management', id: 'model_management', items: [ - { name: 'Trained models', id: 'ml_1' }, - { name: 'Nodes', id: 'ml_2' }, + { name: 'Trained models', id: 'ml_trained_models' }, + { name: 'Nodes', id: 'ml_nodes' }, ], }, { @@ -88,22 +88,8 @@ export const navItems = { name: 'Ingest', id: 'management_ingest', items: [ - { name: 'Ingest Pipelines', id: 'management_1' }, - { name: 'Logstash Pipelines', id: 'management_2' }, - ], - }, - { - name: 'Data', - id: 'management_data', - items: [ - { name: 'Index Management', id: 'management_1' }, - { name: 'Index Lifecycle Policies', id: 'management_2' }, - { name: 'Snapshot and Restore', id: 'management_2' }, - { name: 'Rollup Jobs', id: 'management_2' }, - { name: 'Transforms', id: 'management_2' }, - { name: 'Cross-Cluster Replication', id: 'management_2' }, - { name: 'Remote Clusters', id: 'management_2' }, - { name: 'Remote Clusters', id: 'management_2' }, + { name: 'Ingest Pipelines', id: 'management_ingest_pipelines' }, + { name: 'Logstash Pipelines', id: 'management_logstash_pipelines' }, ], }, ], diff --git a/packages/shared-ux/chrome/navigation/src/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/navigation.stories.tsx index 5084d2efa619c..9880612309803 100644 --- a/packages/shared-ux/chrome/navigation/src/navigation.stories.tsx +++ b/packages/shared-ux/chrome/navigation/src/navigation.stories.tsx @@ -6,8 +6,11 @@ * Side Public License, v 1. */ +import { EuiButtonIcon, EuiCollapsibleNav, EuiThemeProvider } from '@elastic/eui'; +import { action } from '@storybook/addon-actions'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import React from 'react'; +import { PLATFORM_SECTIONS } from '../constants'; import { NavigationStorybookMock } from '../mocks'; import mdx from '../README.mdx'; import { NavigationProps, NavigationServices } from '../types'; @@ -15,12 +18,59 @@ import { Navigation as Component } from './navigation'; import { NavigationProvider } from './services'; const mock = new NavigationStorybookMock(); +let colorMode = ''; +colorMode = 'LIGHT'; -const Template = (args: NavigationProps & NavigationServices) => ( - - - -); +const Template = (args: NavigationProps & NavigationServices) => { + const services = mock.getServices(args); + const props = mock.getProps(args); + + const { navIsOpen } = services; + const collapseAction = action('setNavIsOpen'); + + const setNavIsOpen = (value: boolean) => { + collapseAction(value); + }; + + return ( + + + { + setNavIsOpen(!navIsOpen); + }} + /> + + + } + onClose={() => { + setNavIsOpen(false); + }} + > + + + + + ); +}; export default { title: 'Chrome/Navigation', @@ -40,17 +90,6 @@ SingleExpanded.args = { }; SingleExpanded.argTypes = mock.getArgumentTypes(); -export const ReducedSections: ComponentStory = Template.bind({}); -ReducedSections.args = { - sections: { - analytics: { enabled: false }, - ml: { enabled: false }, - management: { enabled: false }, - devTools: { enabled: false }, - }, -}; -ReducedSections.argTypes = mock.getArgumentTypes(); - export const WithRecentItems: ComponentStory = Template.bind({}); WithRecentItems.args = { initiallyOpenSections: ['example_project'], @@ -61,3 +100,25 @@ WithRecentItems.args = { ], }; WithRecentItems.argTypes = mock.getArgumentTypes(); + +export const ReducedSections: ComponentStory = Template.bind({}); +ReducedSections.args = { + platformSections: { + [PLATFORM_SECTIONS.ANALYTICS]: { enabled: false }, + [PLATFORM_SECTIONS.MACHINE_LEARNING]: { enabled: false }, + [PLATFORM_SECTIONS.DEVTOOLS]: { enabled: false }, + [PLATFORM_SECTIONS.MANAGEMENT]: { enabled: false }, + }, +}; +ReducedSections.argTypes = mock.getArgumentTypes(); + +/** +// Home button +// Spaces menu +export const WithClassicBuckets: ComponentStory = Template.bind({}); +WithClassicBuckets.args = {}; +WithClassicBuckets.argTypes = mock.getArgumentTypes(); + +// With banner? +// with bottom bar? + **/ diff --git a/packages/shared-ux/chrome/navigation/src/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/navigation.test.tsx index 52d9fe2274877..556f95fd3a1e1 100644 --- a/packages/shared-ux/chrome/navigation/src/navigation.test.tsx +++ b/packages/shared-ux/chrome/navigation/src/navigation.test.tsx @@ -22,7 +22,7 @@ describe('', () => { render( - + , div ); diff --git a/packages/shared-ux/chrome/navigation/src/navigation.tsx b/packages/shared-ux/chrome/navigation/src/navigation.tsx index 3b72d5b710e7c..0c1a7e628ca44 100644 --- a/packages/shared-ux/chrome/navigation/src/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/navigation.tsx @@ -19,13 +19,7 @@ import { useEuiTheme, } from '@elastic/eui'; import React from 'react'; -import { - ANALYTICS_SECTION_KEY, - DEVTOOLS_SECTION_KEY, - MANAGEMENT_SECTION_KEY, - ML_SECTION_KEY, - RECENTS_SECTION_KEY, -} from '../constants'; +import { PLATFORM_SECTIONS } from '../constants'; import { ILocatorDefinition, NavigationProps, NavItemProps, RecentItem } from '../types'; import { ElasticMark } from './elastic_mark'; import './header_logo.scss'; @@ -89,11 +83,11 @@ export const Navigation = (props: NavigationProps) => { iconType: IconType, title: React.ReactNode, items: NavItemProps[], - bucketKey: keyof NavigationProps['sections'] | undefined, + platformSection?: keyof NavigationProps['platformSections'], solutionKey?: string ) => { // ability to turn off bucket completely with {[bucketKey]: { enabled: false }} - if (bucketKey && props.sections?.[bucketKey]?.enabled === false) { + if (platformSection && props.platformSections?.[platformSection]?.enabled === false) { return null; } @@ -103,7 +97,7 @@ export const Navigation = (props: NavigationProps) => { title={title} iconType={iconType} isCollapsible={true} - initialIsOpen={isOpen(bucketKey ?? solutionKey)} + initialIsOpen={isOpen(platformSection ?? solutionKey)} > @@ -136,6 +130,7 @@ export const Navigation = (props: NavigationProps) => { ? convertSolutionNavItemsToEuiSideNavItems(props.items) : []; + const { ANALYTICS, MACHINE_LEARNING, DEVTOOLS, MANAGEMENT } = PLATFORM_SECTIONS; return ( <> @@ -150,21 +145,14 @@ export const Navigation = (props: NavigationProps) => { {navIsOpen ? : null} - {euiSideNavRecentItems - ? renderBucket('clock', 'Recent', euiSideNavRecentItems, RECENTS_SECTION_KEY) - : null} + {euiSideNavRecentItems ? renderBucket('clock', 'Recent', euiSideNavRecentItems) : null} {renderBucket(icon, name, euiSideNavSolutionItems, undefined, id)} - {renderBucket( - 'stats', - 'Data exploration', - navItems.dataExploration, - ANALYTICS_SECTION_KEY - )} + {renderBucket('stats', 'Data exploration', navItems.dataExploration, ANALYTICS)} {renderBucket( 'indexMapping', 'Machine learning', navItems.machineLearning, - ML_SECTION_KEY + MACHINE_LEARNING )} @@ -173,13 +161,8 @@ export const Navigation = (props: NavigationProps) => { - {renderBucket( - 'editorCodeBlock', - 'Developer tools', - navItems.devTools, - DEVTOOLS_SECTION_KEY - )} - {renderBucket('gear', 'Management', navItems.management, MANAGEMENT_SECTION_KEY)} + {renderBucket('editorCodeBlock', 'Developer tools', navItems.devTools, DEVTOOLS)} + {renderBucket('gear', 'Management', navItems.management, MANAGEMENT)} diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index 85fefcb936802..2ebbc4ca43b08 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -16,9 +16,9 @@ const Context = React.createContext(null); * A Context Provider that provides services to the component and its dependencies. */ export const NavigationProvider: FC = ({ children, ...services }) => { - const { getLocator, navIsOpen, recentItems } = services; + const { getLocator, recentItems, navIsOpen } = services; return ( - {children} + {children} ); }; @@ -29,19 +29,18 @@ export const NavigationKibanaProvider: FC = ({ children, ...dependencies }) => { - const navIsOpen = useObservable(dependencies.core.chrome.getProjectNavIsOpen$(), true); - const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; - // FIXME // const recentItems$ = dependencies.core.chrome.recentlyAccessed.get$(); // const recentItems = useObservable(recentItems$, []); + const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; + + const getLocator = (id: string) => dependencies.share.url.locators.get(id); + const navIsOpen = useObservable(dependencies.core.chrome.getProjectNavIsOpen$(), true); const value: NavigationServices = { - navIsOpen, recentItems, - getLocator(id: string) { - return dependencies.share.url.locators.get(id); - }, + getLocator, + navIsOpen, }; return ( diff --git a/packages/shared-ux/chrome/navigation/types.ts b/packages/shared-ux/chrome/navigation/types.ts index 7aa925edfef1a..66e7ab26b668f 100644 --- a/packages/shared-ux/chrome/navigation/types.ts +++ b/packages/shared-ux/chrome/navigation/types.ts @@ -113,10 +113,7 @@ export interface NavigationProps { */ icon: IconType; }; - sections: { - recents?: { - enabled?: boolean; // default: true - }; + platformSections: { analytics?: { enabled?: boolean; // default: true }; From de11016e15b92c47570efcc6062802d22bf8f808 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 31 Mar 2023 18:53:59 -0700 Subject: [PATCH 03/41] refactor out navigation_buckets data modeling --- packages/shared-ux/chrome/navigation/index.ts | 2 +- .../chrome/navigation/mocks/index.ts | 1 + .../chrome/navigation/mocks/src/jest.ts | 4 +- .../chrome/navigation/mocks/src/mocks.ts | 63 ++++++ .../chrome/navigation/mocks/src/nav_items.ts | 53 ----- .../chrome/navigation/mocks/src/storybook.ts | 28 +-- .../chrome/navigation/src/model/index.ts | 35 +++ .../chrome/navigation/src/model/model.ts | 100 +++++++++ .../src/model/platform_nav/_locators.ts | 30 +++ .../src/model/platform_nav/analytics.ts | 33 +++ .../src/model/platform_nav/devtools.ts | 39 ++++ .../model/platform_nav/machine_learning.ts | 124 +++++++++++ .../src/model/platform_nav/management.ts | 206 ++++++++++++++++++ .../chrome/navigation/src/nav_items.tsx | 96 -------- .../chrome/navigation/src/navigation.tsx | 170 --------------- .../{constants.ts => src/styles.ts} | 14 +- .../navigation/src/{ => ui}/elastic_mark.tsx | 0 .../navigation/src/{ => ui}/header_logo.scss | 0 .../src/{ => ui}/navigation.stories.tsx | 68 +++--- .../src/{ => ui}/navigation.test.tsx | 15 +- .../chrome/navigation/src/ui/navigation.tsx | 102 +++++++++ .../navigation/src/ui/navigation_bucket.tsx | 60 +++++ .../shared-ux/chrome/navigation/src/utils.tsx | 82 +++++++ .../shared-ux/chrome/navigation/tsconfig.json | 2 +- .../navigation/{types.ts => types/index.ts} | 127 ++++++----- .../chrome/navigation/types/internal.ts | 47 ++++ 26 files changed, 1050 insertions(+), 451 deletions(-) create mode 100644 packages/shared-ux/chrome/navigation/mocks/src/mocks.ts delete mode 100644 packages/shared-ux/chrome/navigation/mocks/src/nav_items.ts create mode 100644 packages/shared-ux/chrome/navigation/src/model/index.ts create mode 100644 packages/shared-ux/chrome/navigation/src/model/model.ts create mode 100644 packages/shared-ux/chrome/navigation/src/model/platform_nav/_locators.ts create mode 100644 packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts create mode 100644 packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts create mode 100644 packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts create mode 100644 packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts delete mode 100644 packages/shared-ux/chrome/navigation/src/nav_items.tsx delete mode 100644 packages/shared-ux/chrome/navigation/src/navigation.tsx rename packages/shared-ux/chrome/navigation/{constants.ts => src/styles.ts} (68%) rename packages/shared-ux/chrome/navigation/src/{ => ui}/elastic_mark.tsx (100%) rename packages/shared-ux/chrome/navigation/src/{ => ui}/header_logo.scss (100%) rename packages/shared-ux/chrome/navigation/src/{ => ui}/navigation.stories.tsx (65%) rename packages/shared-ux/chrome/navigation/src/{ => ui}/navigation.test.tsx (71%) create mode 100644 packages/shared-ux/chrome/navigation/src/ui/navigation.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx create mode 100644 packages/shared-ux/chrome/navigation/src/utils.tsx rename packages/shared-ux/chrome/navigation/{types.ts => types/index.ts} (52%) create mode 100644 packages/shared-ux/chrome/navigation/types/internal.ts diff --git a/packages/shared-ux/chrome/navigation/index.ts b/packages/shared-ux/chrome/navigation/index.ts index a5493ab12481a..f6070a30a5836 100644 --- a/packages/shared-ux/chrome/navigation/index.ts +++ b/packages/shared-ux/chrome/navigation/index.ts @@ -6,6 +6,6 @@ * Side Public License, v 1. */ -export { Navigation } from './src/navigation'; export { NavigationKibanaProvider, NavigationProvider } from './src/services'; +export { Navigation } from './src/ui/navigation'; export type { NavigationProps, NavigationServices, NavItemProps } from './types'; diff --git a/packages/shared-ux/chrome/navigation/mocks/index.ts b/packages/shared-ux/chrome/navigation/mocks/index.ts index a9536ce51309d..be262b787aced 100644 --- a/packages/shared-ux/chrome/navigation/mocks/index.ts +++ b/packages/shared-ux/chrome/navigation/mocks/index.ts @@ -7,5 +7,6 @@ */ export { getServicesMock as getNavigationServicesMock } from './src/jest'; +export { mocks } from './src/mocks'; export { StorybookMock as NavigationStorybookMock } from './src/storybook'; export type { Params as NavigationStorybookParams } from './src/storybook'; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index 28d787c8fc381..d89b45c17077d 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -13,9 +13,7 @@ export const getServicesMock = (): NavigationServices => { return { getLocator() { - return { - navigateSync: jest.fn(), - }; + return { navigateSync: jest.fn() }; }, navIsOpen: true, recentItems, diff --git a/packages/shared-ux/chrome/navigation/mocks/src/mocks.ts b/packages/shared-ux/chrome/navigation/mocks/src/mocks.ts new file mode 100644 index 0000000000000..37d18b35cbadd --- /dev/null +++ b/packages/shared-ux/chrome/navigation/mocks/src/mocks.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SolutionProperties } from '../../types'; + +const MOCK_LOCATOR_ID = 'MOCK_LOCATOR_ID'; + +export const mocks: SolutionProperties & { locatorId: string } = { + id: 'example_project', + icon: 'logoObservability', + name: 'Example Project', + locatorId: MOCK_LOCATOR_ID, + items: [ + { + id: 'root', + name: '', + items: [ + { + id: 'get_started', + name: 'Get started', + locator: { id: MOCK_LOCATOR_ID, params: { view: '/app/observability/overview' } }, + }, + { + id: 'alerts', + name: 'Alerts', + locator: { id: MOCK_LOCATOR_ID, params: { view: '/app/observability/alerts' } }, + }, + { + id: 'cases', + name: 'Cases', + locator: { id: MOCK_LOCATOR_ID, params: { view: '/app/observability/cases' } }, + }, + ], + }, + { + id: 'signals', + name: 'Signals', + items: [ + { + id: 'logs', + name: 'Logs', + locator: { + id: MOCK_LOCATOR_ID, + params: { view: '/app/management/ingest/ingest_pipelines' }, + }, + }, + { + id: 'tracing', + name: 'Tracing', + locator: { + id: MOCK_LOCATOR_ID, + params: { view: '/app/management/ingest/ingest_pipelines' }, + }, + }, + ], + }, + ], +}; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/nav_items.ts b/packages/shared-ux/chrome/navigation/mocks/src/nav_items.ts deleted file mode 100644 index a62582af215b3..0000000000000 --- a/packages/shared-ux/chrome/navigation/mocks/src/nav_items.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { NavItemProps } from '../../types'; - -export const mockLocatorId = 'MOCK_LOCATOR_ID'; - -export const getMockNavItems = (): Array> => [ - { - id: 'root', - name: '', - forceOpen: true, - items: [ - { - id: 'get_started', - name: 'Get started', - locator: { id: mockLocatorId, params: { view: '/app/observability/overview' } }, - isSelected: true, - }, - { - id: 'alerts', - name: 'Alerts', - locator: { id: mockLocatorId, params: { view: '/app/observability/alerts' } }, - }, - { - id: 'cases', - name: 'Cases', - locator: { id: mockLocatorId, params: { view: '/app/observability/cases' } }, - }, - ], - }, - { - id: 'signals_root', - name: 'Signals', - items: [ - { - id: 'logs', - name: 'Logs', - locator: { id: mockLocatorId, params: { view: '/app/management/ingest/ingest_pipelines' } }, - }, - { - id: 'tracing', - name: 'Tracing', - locator: { id: mockLocatorId, params: { view: '/app/management/ingest/ingest_pipelines' } }, - }, - ], - }, -]; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index 5771481adf109..4e99af0af1f0d 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -10,13 +10,13 @@ import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock'; import { SerializableRecord } from '@kbn/utility-types'; import { action } from '@storybook/addon-actions'; import { NavigationProps, NavigationServices } from '../../types'; -import { getMockNavItems, mockLocatorId } from './nav_items'; type Arguments = NavigationProps & NavigationServices; export type Params = Pick< Arguments, - 'navIsOpen' | 'recentItems' | 'initiallyOpenSections' | 'platformSections' ->; + 'navIsOpen' | 'recentItems' | 'activeNavItemId' | 'platformConfig' | 'solutions' +> & + Arguments['platformConfig']; export class StorybookMock extends AbstractStorybookMock { propArguments = {}; @@ -34,10 +34,12 @@ export class StorybookMock extends AbstractStorybookMock { - navAction(`Locator: ${mockLocatorId} / Params: ${JSON.stringify(locatorParams)}`); - }; - const getLocator = (_locatorId: string) => ({ navigateSync }); + + const getLocator = (locatorId: string) => ({ + navigateSync: (locatorParams?: SerializableRecord) => { + navAction(`Locator: ${locatorId} / Params: ${JSON.stringify(locatorParams)}`); + }, + }); return { getLocator, @@ -47,16 +49,10 @@ export class StorybookMock extends AbstractStorybookMock> | undefined, + private platformConfig: NavigationProps['platformConfig'] | undefined, + private solutions: SolutionProperties[] + ) { + this.common = { + locatorNavigation, + activeNavItemId, + }; + } + + public getRecent(): NavigationBucketProps { + return { + id: 'recent', + icon: 'clock', + name: 'Recent', + items: this.recentItems, + ...this.common, + }; + } + + public getPlatform(): Record { + return { + [Platform.Analytics]: { + id: Platform.Analytics, + icon: 'stats', + name: 'Data exploration', + items: navItemSet[Platform.Analytics], + platformConfig: this.platformConfig?.[Platform.Analytics], + ...this.common, + }, + [Platform.MachineLearning]: { + id: Platform.MachineLearning, + icon: 'indexMapping', + name: 'Machine learning', + items: navItemSet[Platform.MachineLearning], + platformConfig: this.platformConfig?.[Platform.MachineLearning], + ...this.common, + }, + [Platform.DevTools]: { + id: Platform.DevTools, + icon: 'editorCodeBlock', + name: 'Developer tools', + items: navItemSet[Platform.DevTools], + platformConfig: this.platformConfig?.[Platform.DevTools], + ...this.common, + }, + [Platform.Management]: { + id: Platform.Management, + icon: 'gear', + name: 'Management', + items: navItemSet[Platform.Management], + platformConfig: this.platformConfig?.[Platform.Management], + ...this.common, + }, + }; + } + + public getSolutions(): NavigationBucketProps[] { + // Allow multiple solutions' collapsible nav buckets side-by-side + return this.solutions.map((s) => ({ + id: s.id, + name: s.name, + icon: s.icon, + items: s.items, + ...this.common, + })); + } + + // public findById(): {}; TODO +} diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/_locators.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/_locators.ts new file mode 100644 index 0000000000000..e2ffd6d38793c --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/_locators.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * @internal + */ +export const locators = { + analytics: (params = {}) => ({ + locator: { id: 'ANALYTICS_APP_LOCATOR', params }, + }), + ml: (params: { page: string; pageState?: string }) => ({ + locator: { id: 'ML_APP_LOCATOR', params }, + }), + devTools: (params = {}) => ({ + locator: { id: 'DEVTOOLS_APP_LOCATOR', params }, + }), + // TODO: import ManagementAppLocatorParams for type safety + management: (params: { sectionId: string; appId?: string }) => ({ + locator: { id: 'MANAGEMENT_APP_LOCATOR', params }, + }), + // FIXME: Do not use + unknown: (params: any) => ({ + locator: { id: 'UNKNOWN_APP_LOCATOR', params }, + }), +}; diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts new file mode 100644 index 0000000000000..0a65127dbd26d --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { NavItemProps } from '../../../types'; + +export const analyticsItemSet: NavItemProps[] = [ + { + name: '', + id: 'root', + items: [ + { + name: 'Discover', + id: 'discover', + locator: { id: 'DISCOVER_APP_LOCATOR', params: {} }, + }, + { + name: 'Dashboard', + id: 'dashboard', + locator: { id: 'DASHBOARD_APP_LOCATOR', params: {} }, + }, + { + name: 'Visualize Library', + id: 'visualize_library', + locator: { id: 'VISUALIZE_APP_LOCATOR', params: {} }, + }, + ], + }, +]; diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts new file mode 100644 index 0000000000000..0ce207155ed61 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { NavItemProps } from '../../../types'; +import { locators } from './_locators'; + +export const devtoolsItemSet: NavItemProps[] = [ + { + name: '', + id: 'root', + items: [ + { + name: 'Console', + id: 'console', + locator: { id: 'CONSOLE_APP_LOCATOR' }, + }, + { + name: 'Search profiler', + id: 'search_profiler', + ...locators.devTools({ sectionId: 'searchprofiler' }), + }, + { + name: 'Grok debugger', + id: 'grok_debugger', + ...locators.devTools({ view: 'grokdebugger' }), + }, + { + name: 'Painless lab', + id: 'painless_lab', + ...locators.devTools({ view: 'painless_lab' }), + }, + ], + }, +]; diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts new file mode 100644 index 0000000000000..bc2c5ca3b44e9 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { NavItemProps } from '../../../types'; +import { locators } from './_locators'; + +export const mlItemSet: NavItemProps[] = [ + { + name: '', + id: 'root', + items: [ + { + name: 'Overview', + id: 'overview', + ...locators.ml({ page: 'overview' }), + }, + { + name: 'Notifications', + id: 'notifications', + ...locators.ml({ page: 'notifications' }), + }, + ], + }, + { + name: 'Anomaly detection', + id: 'anomaly_detection', + items: [ + { + name: 'Jobs', + id: 'jobs', + ...locators.ml({ page: 'jobs' }), + }, + { + name: 'Anomaly explorer', + id: 'explorer', + ...locators.ml({ page: 'explorer' }), + }, + { + name: 'Single metric viewer', + id: 'single_metric_viewer', + ...locators.ml({ page: 'timeseriesexplorer' }), + }, + { + name: 'Settings', + id: 'settings', + ...locators.ml({ page: 'settings' }), + }, + ], + }, + { + name: 'Data frame analytics', + id: 'data_frame_analytics', + items: [ + { + name: 'Jobs', + id: 'jobs', + ...locators.ml({ page: 'data_frame_analytics' }), + }, + { + name: 'Results explorer', + id: 'results_explorer', + ...locators.ml({ page: 'data_frame_analytics/exploration' }), + }, + { + name: 'Analytics map', + id: 'analytics_map', + ...locators.ml({ page: 'data_frame_analytics/map' }), + }, + ], + }, + { + name: 'Model management', + id: 'model_management', + items: [ + { + name: 'Trained models', + id: 'trained_models', + ...locators.ml({ page: 'trained_models' }), + }, + { + name: 'Nodes', + id: 'nodes', + ...locators.unknown({ page: 'nodes' }), + }, + ], + }, + { + name: 'Data visualizer', + id: 'data_visualizer', + items: [ + { + name: 'File', + id: 'file', + ...locators.ml({ page: 'filedatavisualizer' }), + }, + { + name: 'Data page', + id: 'data_view', + ...locators.ml({ page: 'datavisualizer_index_select' }), + }, + ], + }, + { + name: 'AIOps labs', + id: 'aiops_labs', + items: [ + { + name: 'Explain log rate spikes', + id: 'explain_log_rate_spikes', + ...locators.ml({ page: 'explain_log_rate_spikes_index_select' }), + }, + { + name: 'Log pattern analysis', + id: 'log_pattern_analysis', + ...locators.ml({ page: 'log_categorization_index_select' }), + }, + ], + }, +]; diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts new file mode 100644 index 0000000000000..e787e0921fe59 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts @@ -0,0 +1,206 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { NavItemProps } from '../../../types'; +import { locators } from './_locators'; + +export const managementItemSet: NavItemProps[] = [ + { + name: '', + id: 'root', + items: [ + { + name: 'Stack monitoring', + id: 'stack_monitoring', + ...locators.unknown({ id: 'stack_monitoring' }), + }, + ], + }, + { + name: 'Integration management', + id: 'integration_management', + items: [ + { + name: 'Integrations', + id: 'integrations', + ...locators.unknown({ sectionId: 'integrations' }), + }, + { name: 'Fleet', id: 'fleet', ...locators.unknown({ sectionId: 'fleet' }) }, + { name: 'Osquery', id: 'osquery', ...locators.unknown({ sectionId: 'osquery' }) }, + ], + }, + { + name: 'Stack management', + id: 'stack_management', + items: [ + { + name: 'Ingest', + id: 'ingest', + items: [ + { + name: 'Ingest pipelines', + id: 'ingest_pipelines', + ...locators.management({ sectionId: 'ingest', appId: 'ingest_pipelines' }), + }, + { + name: 'Logstash pipelines', + id: 'logstash_pipelines', + ...locators.management({ sectionId: 'logstash', appId: 'pipelines' }), + }, + ], + }, + { + name: 'Data', + id: 'data', + items: [ + { + name: 'Index management', + id: 'index_management', + ...locators.management({ sectionId: 'data', appId: 'index_management' }), + }, + { + name: 'Index lifecycle policies', + id: 'index_lifecycle_policies', + ...locators.management({ sectionId: 'data', appId: 'index_lifecycle_management' }), + }, + { + name: 'Snapshot and restore', + id: 'snapshot_and_restore', + ...locators.management({ sectionId: 'data', appId: 'snapshot_restore' }), + }, + { + name: 'Rollup jobs', + id: 'rollup_jobs', + ...locators.management({ sectionId: 'data', appId: 'rollup_jobs' }), + }, + { + name: 'Transforms', + id: 'transforms', + ...locators.management({ sectionId: 'data', appId: 'transform' }), + }, + { + name: 'Cross-cluster replication', + id: 'cross_cluster_replication', + ...locators.management({ sectionId: 'data', appId: 'cross_cluster_replication' }), + }, + { + name: 'Remote clusters', + id: 'remote_clusters', + ...locators.management({ sectionId: 'data', appId: 'remote_clusters' }), + }, + ], + }, + { + name: 'Alerts and insights', + id: 'alerts_and_insights', + items: [ + { + name: 'Rules', + id: 'rules', + ...locators.management({ sectionId: 'insightsAndAlerting', appId: 'triggersActions' }), + }, + { + name: 'Cases', + id: 'cases', + ...locators.management({ sectionId: 'insightsAndAlerting', appId: 'cases' }), + }, + { + name: 'Connectors', + id: 'connectors', + ...locators.management({ + sectionId: 'insightsAndAlerting', + appId: 'triggersActionsConnectors', + }), + }, + { + name: 'Reporting', + id: 'reporting', + ...locators.management({ sectionId: 'insightsAndAlerting', appId: 'reporting' }), + }, + { + name: 'Machine learning', + id: 'machine_learning', + ...locators.management({ sectionId: 'insightsAndAlerting', appId: 'jobsListLink' }), + }, + { + name: 'Watcher', + id: 'watcher', + ...locators.management({ sectionId: 'insightsAndAlerting', appId: 'watcher' }), + }, + ], + }, + { + name: 'Security', + id: 'security', + items: [ + { + name: 'Users', + id: 'users', + ...locators.management({ sectionId: 'security', appId: 'users' }), + }, + { + name: 'Roles', + id: 'roles', + ...locators.management({ sectionId: 'security', appId: 'roles' }), + }, + { + name: 'Role mappings', + id: 'role_mappings', + ...locators.management({ sectionId: 'security', appId: 'role_mappings' }), + }, + { + name: 'API keys', + id: 'api_keys', + ...locators.management({ sectionId: 'security', appId: 'api_keys' }), + }, + ], + }, + { + name: 'Kibana', + id: 'kibana', + items: [ + { + name: 'Data view', + id: 'data_views', + ...locators.management({ sectionId: 'kibana', appId: 'data_views' }), + }, + { + name: 'Saved objects', + id: 'saved_objects', + ...locators.management({ sectionId: 'kibana', appId: 'saved_objects' }), + }, + { + name: 'Tags', + id: 'tags', + ...locators.management({ sectionId: 'kibana', appId: 'tags' }), + }, + { + name: 'Search sessions', + id: 'search_sessions', + ...locators.management({ sectionId: 'kibana', appId: 'search_sessions' }), + }, + { + name: 'Spaces', + id: 'spaces', + ...locators.management({ sectionId: 'kibana', appId: 'spaces' }), + }, + { + name: 'Advanced settings', + id: 'advanced_settings', + ...locators.management({ sectionId: 'kibana', appId: 'settings' }), + }, + ], + }, + { + name: 'Upgrade assistant', + id: 'upgrade_assistant', + ...locators.management({ sectionId: 'kibana', appId: 'stack/upgrade_assistant' }), + }, + ], + }, +]; diff --git a/packages/shared-ux/chrome/navigation/src/nav_items.tsx b/packages/shared-ux/chrome/navigation/src/nav_items.tsx deleted file mode 100644 index d0880f267c0d1..0000000000000 --- a/packages/shared-ux/chrome/navigation/src/nav_items.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export const navItems = { - dataExploration: [ - { - name: '', - id: 'data_exploration_root', - items: [ - { name: 'Discover', id: 'discover' }, - { name: 'Dashboard', id: 'dashboard' }, - { name: 'Visualize Library', id: 'visualize_library' }, - ], - }, - ], - machineLearning: [ - { - name: '', - id: 'ml_root', - items: [ - { name: 'Overview', id: 'ml_overview' }, - { name: 'Notifications', id: 'ml_notifications' }, - ], - }, - { - name: 'Anomaly detection', - id: 'anomaly_detection', - items: [ - { name: 'Jobs', id: 'ml_anomaly_detection_jobs' }, - { name: 'Anomaly explorer', id: 'ml_anomaly_explorer' }, - { name: 'Single metric viewer', id: 'ml_single_metric_viewer' }, - { name: 'Settings', id: 'ml_settings' }, - ], - }, - { - name: 'Data frame analytics', - id: 'data_frame_analytics', - items: [ - { name: 'Jobs', id: 'ml_data_frame_analytics_jobs' }, - { name: 'Results explorer', id: 'ml_results_explorer' }, - { name: 'Analytics map', id: 'ml_analytics_map' }, - ], - }, - { - name: 'Model management', - id: 'model_management', - items: [ - { name: 'Trained models', id: 'ml_trained_models' }, - { name: 'Nodes', id: 'ml_nodes' }, - ], - }, - { - name: 'Data visualizer', - id: 'data_visualizer', - items: [ - { name: 'File', id: 'file' }, - { name: 'Data view', id: 'data_view' }, - ], - }, - { - name: 'AIOps labs', - id: 'aiops_labs', - items: [ - { name: 'Explain log rate spikes', id: 'explain_log_rate_spikes' }, - { name: 'Log pattern analysis', id: 'log_pattern_analysis' }, - ], - }, - ], - devTools: [ - { - name: '', - id: 'devtools_root', - items: [ - { name: 'Console', id: 'console' }, - { name: 'Search profiler', id: 'search_profiler' }, - { name: 'Grok debugger', id: 'grok_debugger' }, - { name: 'Painless lab', id: 'painless_lab' }, - ], - }, - ], - management: [ - { - name: 'Ingest', - id: 'management_ingest', - items: [ - { name: 'Ingest Pipelines', id: 'management_ingest_pipelines' }, - { name: 'Logstash Pipelines', id: 'management_logstash_pipelines' }, - ], - }, - ], -}; diff --git a/packages/shared-ux/chrome/navigation/src/navigation.tsx b/packages/shared-ux/chrome/navigation/src/navigation.tsx deleted file mode 100644 index 0c1a7e628ca44..0000000000000 --- a/packages/shared-ux/chrome/navigation/src/navigation.tsx +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - EuiCollapsibleNavGroup, - EuiFlexGroup, - EuiFlexItem, - EuiHeaderLogo, - EuiIcon, - EuiSideNav, - EuiSideNavItemType, - EuiSpacer, - IconType, - useEuiTheme, -} from '@elastic/eui'; -import React from 'react'; -import { PLATFORM_SECTIONS } from '../constants'; -import { ILocatorDefinition, NavigationProps, NavItemProps, RecentItem } from '../types'; -import { ElasticMark } from './elastic_mark'; -import './header_logo.scss'; -import { navItems } from './nav_items'; -import { useNavigation } from './services'; - -export const Navigation = (props: NavigationProps) => { - // const { euiTheme } = useEuiTheme(); - // const { fontSize: navSectionFontSize } = useEuiFontSize('m'); - // const { fontSize: navItemFontSize } = useEuiFontSize('s'); - const { getLocator, navIsOpen, recentItems } = useNavigation(); - - const { euiTheme } = useEuiTheme(); - - const locatorNavigation = (locator: ILocatorDefinition | undefined) => () => { - if (locator) { - const locatorInstance = getLocator(locator.id); - - if (!locatorInstance) { - throw new Error(`Unresolved Locator instance for ${locator.id}`); - } - - if (locator.params) { - locatorInstance.navigateSync(locator.params); - } - } - }; - - // implement onClick using item's locator params - const convertSolutionNavItemsToEuiSideNavItems = (items: NavItemProps[]) => { - return items.map((item) => { - const navItem: EuiSideNavItemType = { ...item }; - if (item.locator) { - navItem.onClick = locatorNavigation(item.locator); - } - if (item.items) { - // recurse - navItem.items = convertSolutionNavItemsToEuiSideNavItems(item.items); - } - return navItem; - }); - }; - - // implement "name" from item's label and onClick using item's "link" - const convertRecentItemsToEuiSideNavItems = (items: RecentItem[]) => [ - { - name: '', - id: 'recent_items_root', - items: items.map((item) => ({ - id: item.id, - name: item.label, - onClick: () => { - // FIXME not implemented - // console.log(`Go to ${item.link}`); - }, - })), - }, - ]; - - const renderBucket = ( - iconType: IconType, - title: React.ReactNode, - items: NavItemProps[], - platformSection?: keyof NavigationProps['platformSections'], - solutionKey?: string - ) => { - // ability to turn off bucket completely with {[bucketKey]: { enabled: false }} - if (platformSection && props.platformSections?.[platformSection]?.enabled === false) { - return null; - } - - if (navIsOpen) { - return ( - - - - ); - } - - return ( -

- -
-
- ); - }; - - const isOpen = (sectionId?: string) => { - return sectionId ? props.initiallyOpenSections?.includes(sectionId) : false; - }; - - const { - id, - title: { icon, name }, - } = props; - - let euiSideNavRecentItems: Array> | undefined; - if (recentItems) { - euiSideNavRecentItems = convertRecentItemsToEuiSideNavItems(recentItems); - } - - const euiSideNavSolutionItems = props.items - ? convertSolutionNavItemsToEuiSideNavItems(props.items) - : []; - - const { ANALYTICS, MACHINE_LEARNING, DEVTOOLS, MANAGEMENT } = PLATFORM_SECTIONS; - return ( - <> - - - - e.preventDefault()} - aria-label="Go to home page" - /> - - {navIsOpen ? : null} - - {euiSideNavRecentItems ? renderBucket('clock', 'Recent', euiSideNavRecentItems) : null} - {renderBucket(icon, name, euiSideNavSolutionItems, undefined, id)} - {renderBucket('stats', 'Data exploration', navItems.dataExploration, ANALYTICS)} - {renderBucket( - 'indexMapping', - 'Machine learning', - navItems.machineLearning, - MACHINE_LEARNING - )} - - - - - - - - {renderBucket('editorCodeBlock', 'Developer tools', navItems.devTools, DEVTOOLS)} - {renderBucket('gear', 'Management', navItems.management, MANAGEMENT)} - - - - ); -}; diff --git a/packages/shared-ux/chrome/navigation/constants.ts b/packages/shared-ux/chrome/navigation/src/styles.ts similarity index 68% rename from packages/shared-ux/chrome/navigation/constants.ts rename to packages/shared-ux/chrome/navigation/src/styles.ts index 04e025d322b2a..72db66e12f7bf 100644 --- a/packages/shared-ux/chrome/navigation/constants.ts +++ b/packages/shared-ux/chrome/navigation/src/styles.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -export enum PLATFORM_SECTIONS { - RECENTS = 'recents', - ANALYTICS = 'analytics', - MACHINE_LEARNING = 'ml', - DEVTOOLS = 'devTools', - MANAGEMENT = 'management', -} +import { css } from '@emotion/react'; + +export const navigationStyles = { + euiSideNavItems: css` + padding-left: 45px; + `, +}; diff --git a/packages/shared-ux/chrome/navigation/src/elastic_mark.tsx b/packages/shared-ux/chrome/navigation/src/ui/elastic_mark.tsx similarity index 100% rename from packages/shared-ux/chrome/navigation/src/elastic_mark.tsx rename to packages/shared-ux/chrome/navigation/src/ui/elastic_mark.tsx diff --git a/packages/shared-ux/chrome/navigation/src/header_logo.scss b/packages/shared-ux/chrome/navigation/src/ui/header_logo.scss similarity index 100% rename from packages/shared-ux/chrome/navigation/src/header_logo.scss rename to packages/shared-ux/chrome/navigation/src/ui/header_logo.scss diff --git a/packages/shared-ux/chrome/navigation/src/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx similarity index 65% rename from packages/shared-ux/chrome/navigation/src/navigation.stories.tsx rename to packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx index 9880612309803..39cca2e94872d 100644 --- a/packages/shared-ux/chrome/navigation/src/navigation.stories.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx @@ -10,20 +10,22 @@ import { EuiButtonIcon, EuiCollapsibleNav, EuiThemeProvider } from '@elastic/eui import { action } from '@storybook/addon-actions'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import React from 'react'; -import { PLATFORM_SECTIONS } from '../constants'; -import { NavigationStorybookMock } from '../mocks'; -import mdx from '../README.mdx'; -import { NavigationProps, NavigationServices } from '../types'; +import { mocks, NavigationStorybookMock } from '../../mocks'; +import mdx from '../../README.mdx'; +import { NavigationProps, NavigationServices } from '../../types'; +import { Platform } from '../model'; +import { NavigationProvider } from '../services'; import { Navigation as Component } from './navigation'; -import { NavigationProvider } from './services'; -const mock = new NavigationStorybookMock(); +const { locatorId, ...solutionProperties } = mocks; + +const storybookMock = new NavigationStorybookMock(); let colorMode = ''; colorMode = 'LIGHT'; const Template = (args: NavigationProps & NavigationServices) => { - const services = mock.getServices(args); - const props = mock.getProps(args); + const services = storybookMock.getServices(args); + const props = storybookMock.getProps(args); const { navIsOpen } = services; const collapseAction = action('setNavIsOpen'); @@ -86,39 +88,37 @@ export default { export const SingleExpanded: ComponentStory = Template.bind({}); SingleExpanded.args = { - initiallyOpenSections: ['example_project'], + activeNavItemId: 'example_project.root.get_started', + solutions: [solutionProperties], }; -SingleExpanded.argTypes = mock.getArgumentTypes(); +SingleExpanded.argTypes = storybookMock.getArgumentTypes(); export const WithRecentItems: ComponentStory = Template.bind({}); WithRecentItems.args = { - initiallyOpenSections: ['example_project'], - recentItems: [ - { id: 'recent_1', label: 'The Elder Scrolls: Morrowind', link: 'testo' }, - { id: 'recent_1', label: 'TIE Fighter', link: 'testo' }, - { id: 'recent_1', label: 'Quake II', link: 'testo' }, - ], + activeNavItemId: 'example_project.root.get_started', + recentItems: [{ id: 'recent_1', label: 'This is a test recent link', link: 'testo' }], + solutions: [solutionProperties], }; -WithRecentItems.argTypes = mock.getArgumentTypes(); +WithRecentItems.argTypes = storybookMock.getArgumentTypes(); export const ReducedSections: ComponentStory = Template.bind({}); ReducedSections.args = { - platformSections: { - [PLATFORM_SECTIONS.ANALYTICS]: { enabled: false }, - [PLATFORM_SECTIONS.MACHINE_LEARNING]: { enabled: false }, - [PLATFORM_SECTIONS.DEVTOOLS]: { enabled: false }, - [PLATFORM_SECTIONS.MANAGEMENT]: { enabled: false }, + activeNavItemId: 'example_project.root.get_started', + platformConfig: { + [Platform.Analytics]: { enabled: false }, + [Platform.MachineLearning]: { enabled: false }, + [Platform.DevTools]: { enabled: false }, + [Platform.Management]: { + properties: { + management_stack_monitoring: { + enabled: false, + }, + management_integration_management: { + enabled: false, + }, + }, + }, }, + solutions: [solutionProperties], }; -ReducedSections.argTypes = mock.getArgumentTypes(); - -/** -// Home button -// Spaces menu -export const WithClassicBuckets: ComponentStory = Template.bind({}); -WithClassicBuckets.args = {}; -WithClassicBuckets.argTypes = mock.getArgumentTypes(); - -// With banner? -// with bottom bar? - **/ +ReducedSections.argTypes = storybookMock.getArgumentTypes(); diff --git a/packages/shared-ux/chrome/navigation/src/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx similarity index 71% rename from packages/shared-ux/chrome/navigation/src/navigation.test.tsx rename to packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx index 556f95fd3a1e1..923983102f395 100644 --- a/packages/shared-ux/chrome/navigation/src/navigation.test.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx @@ -8,21 +8,28 @@ import React from 'react'; import { render } from 'react-dom'; -import { getServicesMock } from '../mocks/src/jest'; +import { getServicesMock } from '../../mocks/src/jest'; +import { NavigationProvider } from '../services'; import { Navigation } from './navigation'; -import { NavigationProvider } from './services'; describe('', () => { test('renders with minimal props', () => { const div = document.createElement('div'); const { getLocator } = getServicesMock(); - const title = { name: 'Navigation testing', icon: 'gear' }; const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; + const platformSections = {}; + const solutions = [ + { + id: 'navigation_testing', + name: 'Navigation testing', + icon: 'gear', + }, + ]; render( - + , div ); diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx new file mode 100644 index 0000000000000..1d3a6c3702019 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + EuiCollapsibleNavGroup, + EuiFlexGroup, + EuiFlexItem, + EuiHeaderLogo, + EuiSideNavItemType, + EuiSpacer, + useEuiTheme, +} from '@elastic/eui'; +import React from 'react'; +import { NavigationProps } from '../../types'; +import { NavigationModel } from '../model'; +import { useNavigation } from '../services'; +import { getLocatorNavigation } from '../utils'; +import { ElasticMark } from './elastic_mark'; +import './header_logo.scss'; +import { NavigationBucket } from './navigation_bucket'; + +export const Navigation = (props: NavigationProps) => { + // const { euiTheme } = useEuiTheme(); + // const { fontSize: navSectionFontSize } = useEuiFontSize('m'); + // const { fontSize: navItemFontSize } = useEuiFontSize('s'); + const { getLocator, recentItems, navIsOpen } = useNavigation(); + + const { euiTheme } = useEuiTheme(); + + const locatorNavigation = getLocatorNavigation(getLocator); + + let euiSideNavRecentItems: Array> | undefined; + if (recentItems) { + euiSideNavRecentItems = [ + { + name: '', + id: 'recent_items_root', + items: recentItems.map((item) => ({ + id: item.id, + name: item.label, + onClick: () => { + // FIXME not implemented + // console.log(`Go to ${item.link}`); + }, + })), + }, + ]; + } + + const nav = new NavigationModel( + locatorNavigation, + props.activeNavItemId, + euiSideNavRecentItems, + props.platformConfig, + props.solutions + ); + const recent = nav.getRecent(); + const solutions = nav.getSolutions(); + const { analytics, ml, devTools, management } = nav.getPlatform(); + + return ( + + + + e.preventDefault()} + aria-label="Go to home page" + /> + {navIsOpen ? : null} + + + {euiSideNavRecentItems ? : null} + + {solutions.map((solutionBucket, idx) => { + return ; + })} + + + + + + + + + + + + + + + ); +}; diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx new file mode 100644 index 0000000000000..bd154174e8836 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiCollapsibleNavGroup, EuiIcon, EuiSideNav, EuiSideNavItemType } from '@elastic/eui'; +import React from 'react'; +import { NavigationBucketProps } from '../../types'; +import { useNavigation } from '../services'; +import { navigationStyles as styles } from '../styles'; +import { convertNavItemsToEui } from '../utils'; + +export const NavigationBucket = (opts: NavigationBucketProps) => { + const { id, items, platformConfig, activeNavItemId, ...props } = opts; + const { navIsOpen } = useNavigation(); + + let euiSideNavItems: Array> | undefined; + + if (platformConfig) { + // ability to turn off platform section in the nav + if (platformConfig?.enabled === false) { + return null; + } + } + + if (items) { + euiSideNavItems = convertNavItemsToEui( + items, + props.locatorNavigation, + platformConfig, + activeNavItemId, + id + ); + } + + if (navIsOpen) { + return ( + + + + ); + } + + return ( +
+ +
+
+ ); +}; diff --git a/packages/shared-ux/chrome/navigation/src/utils.tsx b/packages/shared-ux/chrome/navigation/src/utils.tsx new file mode 100644 index 0000000000000..0eb937cba9356 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/utils.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiSideNavItemType } from '@elastic/eui'; +import { NavItemProps, PlatformSectionConfig } from '../types'; +import { GetLocatorFn, ILocatorDefinition, LocatorNavigationFn } from '../types/internal'; + +export const getLocatorNavigation = (getLocator: GetLocatorFn): LocatorNavigationFn => { + const locatorNavigation = (locator: ILocatorDefinition | undefined) => () => { + if (locator) { + const locatorInstance = getLocator(locator.id); + + if (!locatorInstance) { + throw new Error(`Unresolved Locator instance for ${locator.id}`); + } + + locatorInstance.navigateSync(locator.params ?? {}); + } + }; + return locatorNavigation; +}; + +type MyEuiSideNavItem = EuiSideNavItemType; +type OnClickFn = MyEuiSideNavItem['onClick']; + +export const convertNavItemsToEui = ( + navItems: NavItemProps[], + locatorNavigation: LocatorNavigationFn, + config?: PlatformSectionConfig, + activeNav?: string, + parentNavPath: string | number = '' +): Array> => { + return navItems.reduce((accum, item) => { + const { id, name, items: subNav } = item; + const matcher = config?.properties?.[id]; + if (matcher?.enabled === false) { + // return accumulated set without the item that is not enabled + return accum; + } + + let onClick: OnClickFn | undefined; + if (item.locator) { + onClick = locatorNavigation(item.locator); + } + + const fullId = [parentNavPath, id].filter(Boolean).join('.'); + + let filteredSubNav: NavItemProps[] | undefined; + if (subNav) { + // recursion + const nextConfig = config?.properties?.[id]; + filteredSubNav = convertNavItemsToEui( + subNav, + locatorNavigation, + nextConfig, + activeNav, + fullId + ); + } + + let isSelected: boolean = false; + if (!subNav && fullId === activeNav) { + // if there are no subnav items and ID is current, mark the item as selected + isSelected = true; + } + + const next: MyEuiSideNavItem = { + id: fullId, + name, + isSelected, + onClick, + items: filteredSubNav, + ['data-test-subj']: `nav-item-${fullId}`, + }; + return [...accum, next]; + }, []); +}; diff --git a/packages/shared-ux/chrome/navigation/tsconfig.json b/packages/shared-ux/chrome/navigation/tsconfig.json index ff88face27cef..3de369d8d1b15 100644 --- a/packages/shared-ux/chrome/navigation/tsconfig.json +++ b/packages/shared-ux/chrome/navigation/tsconfig.json @@ -12,7 +12,7 @@ }, "include": [ "**/*.ts", - "**/*.tsx", + "**/*.tsx" ], "kbn_references": [ "@kbn/shared-ux-storybook-mock", diff --git a/packages/shared-ux/chrome/navigation/types.ts b/packages/shared-ux/chrome/navigation/types/index.ts similarity index 52% rename from packages/shared-ux/chrome/navigation/types.ts rename to packages/shared-ux/chrome/navigation/types/index.ts index 66e7ab26b668f..cd7e180480c3e 100644 --- a/packages/shared-ux/chrome/navigation/types.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -6,31 +6,13 @@ * Side Public License, v 1. */ -import { Observable } from 'rxjs'; import type { EuiSideNavItemType, IconType } from '@elastic/eui'; -import type { SerializableRecord } from '@kbn/utility-types'; - -/** - * TODO move definition from the Share plugin to a package - * @private - */ -export interface ILocatorPublic { - navigateSync:

(params: P) => void; -} - -type GetLocatorFn = (id: string) => ILocatorPublic | undefined; - -/** - * @internal - */ -export interface RecentItem { - link: string; - label: string; - id: string; -} +import { Observable } from 'rxjs'; +import { GetLocatorFn, ILocatorDefinition, LocatorNavigationFn, RecentItem } from './internal'; /** * A list of services that are consumed by this component. + * @public */ export interface NavigationServices { getLocator: GetLocatorFn; @@ -41,6 +23,7 @@ export interface NavigationServices { /** * An interface containing a collection of Kibana dependencies required to * render this component + * @public */ export interface NavigationKibanaDependencies { share: { @@ -58,27 +41,13 @@ export interface NavigationKibanaDependencies { }; } -/** - * Locator info used for navigating around Kibana - */ -export interface ILocatorDefinition

{ - /** - * ID of a registered LocatorDefinition - */ - id: string; - /** - * Navigational params in the form understood by the locator's plugin. - */ - params?: P; -} - /** * Props for the `NavItem` component representing the content of a navigational item with optional children. * @public */ export type NavItemProps = Pick< EuiSideNavItemType, - 'id' | 'name' | 'forceOpen' | 'isSelected' + 'id' | 'name' | 'isSelected' > & { items?: Array>; /** @@ -87,44 +56,70 @@ export type NavItemProps = Pick< locator?: ILocatorDefinition; }; +/** + * @public + */ +export interface PlatformSectionConfig { + enabled?: boolean; + properties?: Record; +} + +/** + * @public + */ +export interface SolutionProperties { + /** + * Solutions' navigation items + */ + items?: NavItemProps[]; + /** + * Solutions' navigation collapsible nav ID + */ + id: string; + /** + * Name to show as title for Solutions' collapsible nav "bucket" + */ + name: React.ReactNode; + /** + * Solution logo, i.e. "logoObservability" + */ + icon: IconType; +} + +/** + * @public + */ +export type PlatformId = 'analytics' | 'ml' | 'devTools' | 'management'; + +/** + * Object that will allow parts of the platform-controlled nav to be hidden + * @public + */ +export type PlatformConfigSet = Record; + /** * Props for the `Navigation` component. + * @public */ export interface NavigationProps { /** - * IDs of Navigation sections that should be rendered initially open when the component is mounted + * ID of sections to initially open + * Path to the nav item is given with hierarchy expressed in dotted notation. + * Example: `my_project.settings.index_management` */ - initiallyOpenSections?: string[]; + activeNavItemId?: string; /** - * Items of navigation content + * Configuration for Solutions' section(s) */ - items?: NavItemProps[]; + solutions: SolutionProperties[]; /** - * ID of the item, for highlighting and showing as initially open + * Controls over how Platform nav sections appear */ - id: string; - title: { - /** - * Name of the project, i.e. "Observability" - */ - name: React.ReactNode; - /** - * Solution logo, i.e. "logoObservability" - */ - icon: IconType; - }; - platformSections: { - analytics?: { - enabled?: boolean; // default: true - }; - ml?: { - enabled?: boolean; // default: true - }; - devTools?: { - enabled?: boolean; // default: true - }; - management?: { - enabled?: boolean; // default: true - }; - }; + platformConfig?: Partial; } + +export type NavigationBucketProps = (SolutionProperties & + Pick) & { + locatorNavigation: LocatorNavigationFn; + platformConfig?: PlatformSectionConfig; +}; diff --git a/packages/shared-ux/chrome/navigation/types/internal.ts b/packages/shared-ux/chrome/navigation/types/internal.ts new file mode 100644 index 0000000000000..6f9ca54b2dcb0 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/types/internal.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SerializableRecord } from '@kbn/utility-types'; + +/** + * @internal + */ +export interface ILocatorPublic { + navigateSync:

(params: P) => void; +} + +/** + * @internal + */ +export type GetLocatorFn = (id: string) => ILocatorPublic | undefined; + +/** + * @internal + */ +export interface RecentItem { + link: string; + label: string; + id: string; +} + +/** + * Locator info used for navigating around Kibana + * @internal + */ +export interface ILocatorDefinition

{ + /** + * ID of a registered LocatorDefinition + */ + id: string; + /** + * Navigational params in the form understood by the locator's plugin. + */ + params?: P; +} + +export type LocatorNavigationFn = (locator: ILocatorDefinition | undefined) => () => void; From 33de0831dec3cb13ddc38474f6d15462408a3403 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 6 Apr 2023 14:57:47 -0700 Subject: [PATCH 04/41] setActiveNavitationItemId stubs --- .../chrome/navigation/mocks/src/jest.ts | 5 ++- .../chrome/navigation/mocks/src/storybook.ts | 11 +++++-- .../chrome/navigation/src/services.tsx | 13 ++++++-- .../navigation/src/ui/navigation.test.tsx | 9 ++++-- .../chrome/navigation/src/ui/navigation.tsx | 4 +-- .../shared-ux/chrome/navigation/src/utils.tsx | 31 ++++++++++++------- .../chrome/navigation/types/index.ts | 14 ++++++--- .../chrome/navigation/types/internal.ts | 10 ++++-- 8 files changed, 66 insertions(+), 31 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index d89b45c17077d..6acba2d4c375c 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -12,9 +12,8 @@ export const getServicesMock = (): NavigationServices => { const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; return { - getLocator() { - return { navigateSync: jest.fn() }; - }, + getLocator: () => ({ navigateSync: jest.fn() }), + setActiveNavItemId: jest.fn(), navIsOpen: true, recentItems, }; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index 4e99af0af1f0d..3e9abf118fe7e 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -10,6 +10,7 @@ import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock'; import { SerializableRecord } from '@kbn/utility-types'; import { action } from '@storybook/addon-actions'; import { NavigationProps, NavigationServices } from '../../types'; +import { GetLocatorFn } from '../../types/internal'; type Arguments = NavigationProps & NavigationServices; export type Params = Pick< @@ -33,16 +34,22 @@ export class StorybookMock extends AbstractStorybookMock ({ + const getLocator: GetLocatorFn = (locatorId: string) => ({ navigateSync: (locatorParams?: SerializableRecord) => { navAction(`Locator: ${locatorId} / Params: ${JSON.stringify(locatorParams)}`); }, }); + const setActiveNavItemId = (id: string | number) => { + activeNavItemIdAction(id); + }; + return { getLocator, + setActiveNavItemId, navIsOpen, recentItems, }; diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index 2ebbc4ca43b08..4599b75c85bc9 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -16,9 +16,11 @@ const Context = React.createContext(null); * A Context Provider that provides services to the component and its dependencies. */ export const NavigationProvider: FC = ({ children, ...services }) => { - const { getLocator, recentItems, navIsOpen } = services; + const { getLocator, recentItems, navIsOpen, setActiveNavItemId } = services; return ( - {children} + + {children} + ); }; @@ -37,10 +39,15 @@ export const NavigationKibanaProvider: FC = ({ const getLocator = (id: string) => dependencies.share.url.locators.get(id); const navIsOpen = useObservable(dependencies.core.chrome.getProjectNavIsOpen$(), true); + const setActiveNavItemId = (id: string | number) => { + console.log({ newActiveItemId: id }); + }; + const value: NavigationServices = { + navIsOpen, recentItems, getLocator, - navIsOpen, + setActiveNavItemId, }; return ( diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx index 923983102f395..4b866fa12073a 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx @@ -15,7 +15,7 @@ import { Navigation } from './navigation'; describe('', () => { test('renders with minimal props', () => { const div = document.createElement('div'); - const { getLocator } = getServicesMock(); + const { getLocator, setActiveNavItemId } = getServicesMock(); const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; const platformSections = {}; @@ -28,7 +28,12 @@ describe('', () => { ]; render( - + , div diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index 1d3a6c3702019..b9ea05a608294 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -28,11 +28,11 @@ export const Navigation = (props: NavigationProps) => { // const { euiTheme } = useEuiTheme(); // const { fontSize: navSectionFontSize } = useEuiFontSize('m'); // const { fontSize: navItemFontSize } = useEuiFontSize('s'); - const { getLocator, recentItems, navIsOpen } = useNavigation(); + const { getLocator, recentItems, navIsOpen, setActiveNavItemId } = useNavigation(); const { euiTheme } = useEuiTheme(); - const locatorNavigation = getLocatorNavigation(getLocator); + const locatorNavigation = getLocatorNavigation(getLocator, setActiveNavItemId); let euiSideNavRecentItems: Array> | undefined; if (recentItems) { diff --git a/packages/shared-ux/chrome/navigation/src/utils.tsx b/packages/shared-ux/chrome/navigation/src/utils.tsx index 0eb937cba9356..bb881107890bb 100644 --- a/packages/shared-ux/chrome/navigation/src/utils.tsx +++ b/packages/shared-ux/chrome/navigation/src/utils.tsx @@ -8,18 +8,25 @@ import { EuiSideNavItemType } from '@elastic/eui'; import { NavItemProps, PlatformSectionConfig } from '../types'; -import { GetLocatorFn, ILocatorDefinition, LocatorNavigationFn } from '../types/internal'; +import { GetLocatorFn, LocatorNavigationFn, SetActiveNavItemIdFn } from '../types/internal'; -export const getLocatorNavigation = (getLocator: GetLocatorFn): LocatorNavigationFn => { - const locatorNavigation = (locator: ILocatorDefinition | undefined) => () => { - if (locator) { - const locatorInstance = getLocator(locator.id); +export const getLocatorNavigation = ( + getLocator: GetLocatorFn, + setActiveNavItemId: SetActiveNavItemIdFn +): LocatorNavigationFn => { + const locatorNavigation = (item: NavItemProps | undefined) => () => { + if (item) { + const { locator, id } = item; + setActiveNavItemId(id); + if (locator) { + const locatorInstance = getLocator(locator.id); - if (!locatorInstance) { - throw new Error(`Unresolved Locator instance for ${locator.id}`); - } + if (!locatorInstance) { + throw new Error(`Unresolved Locator instance for ${locator.id}`); + } - locatorInstance.navigateSync(locator.params ?? {}); + locatorInstance.navigateSync(locator.params ?? {}); + } } }; return locatorNavigation; @@ -43,13 +50,13 @@ export const convertNavItemsToEui = ( return accum; } + const fullId = [parentNavPath, id].filter(Boolean).join('.'); + let onClick: OnClickFn | undefined; if (item.locator) { - onClick = locatorNavigation(item.locator); + onClick = locatorNavigation({ ...item, id: fullId }); } - const fullId = [parentNavPath, id].filter(Boolean).join('.'); - let filteredSubNav: NavItemProps[] | undefined; if (subNav) { // recursion diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index cd7e180480c3e..a5d09d459101e 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -8,7 +8,13 @@ import type { EuiSideNavItemType, IconType } from '@elastic/eui'; import { Observable } from 'rxjs'; -import { GetLocatorFn, ILocatorDefinition, LocatorNavigationFn, RecentItem } from './internal'; +import { + GetLocatorFn, + ILocatorDefinition, + LocatorNavigationFn, + RecentItem, + SetActiveNavItemIdFn, +} from './internal'; /** * A list of services that are consumed by this component. @@ -16,6 +22,7 @@ import { GetLocatorFn, ILocatorDefinition, LocatorNavigationFn, RecentItem } fro */ export interface NavigationServices { getLocator: GetLocatorFn; + setActiveNavItemId: SetActiveNavItemIdFn; navIsOpen: boolean; recentItems: RecentItem[]; } @@ -45,10 +52,7 @@ export interface NavigationKibanaDependencies { * Props for the `NavItem` component representing the content of a navigational item with optional children. * @public */ -export type NavItemProps = Pick< - EuiSideNavItemType, - 'id' | 'name' | 'isSelected' -> & { +export type NavItemProps = Pick, 'id' | 'name'> & { items?: Array>; /** * ID of a registered LocatorDefinition diff --git a/packages/shared-ux/chrome/navigation/types/internal.ts b/packages/shared-ux/chrome/navigation/types/internal.ts index 6f9ca54b2dcb0..e059c2096cca7 100644 --- a/packages/shared-ux/chrome/navigation/types/internal.ts +++ b/packages/shared-ux/chrome/navigation/types/internal.ts @@ -7,6 +7,7 @@ */ import { SerializableRecord } from '@kbn/utility-types'; +import { NavItemProps } from '.'; /** * @internal @@ -18,7 +19,12 @@ export interface ILocatorPublic { /** * @internal */ -export type GetLocatorFn = (id: string) => ILocatorPublic | undefined; +export type GetLocatorFn = (locatorId: string) => ILocatorPublic | undefined; + +/** + * @internal + */ +export type SetActiveNavItemIdFn = (activeNavItemId: string | number) => void; /** * @internal @@ -44,4 +50,4 @@ export interface ILocatorDefinition

{ params?: P; } -export type LocatorNavigationFn = (locator: ILocatorDefinition | undefined) => () => void; +export type LocatorNavigationFn = (item: NavItemProps | undefined) => () => void; From 80a991d70aa5d8f40f38066b6bcb8cae5ed4e562 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Mon, 10 Apr 2023 09:58:19 -0700 Subject: [PATCH 05/41] add activeNavItemId$ observable --- .../chrome/navigation/mocks/src/jest.ts | 7 +- .../chrome/navigation/mocks/src/storybook.ts | 12 +- .../chrome/navigation/src/model/model.ts | 122 +++++++++++++----- .../chrome/navigation/src/services.tsx | 41 +++++- .../navigation/src/ui/navigation.test.tsx | 8 +- .../chrome/navigation/src/ui/navigation.tsx | 11 +- .../navigation/src/ui/navigation_bucket.tsx | 26 +--- .../shared-ux/chrome/navigation/src/utils.tsx | 89 ------------- .../chrome/navigation/types/index.ts | 15 +-- .../chrome/navigation/types/internal.ts | 2 +- 10 files changed, 159 insertions(+), 174 deletions(-) delete mode 100644 packages/shared-ux/chrome/navigation/src/utils.tsx diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index 6acba2d4c375c..73ace23577070 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -6,14 +6,17 @@ * Side Public License, v 1. */ +import { BehaviorSubject } from 'rxjs'; +import { getLocatorNavigation } from '../../src/services'; import { NavigationServices } from '../../types'; export const getServicesMock = (): NavigationServices => { + const locatorNavigation = getLocatorNavigation(jest.fn(), jest.fn()); const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; return { - getLocator: () => ({ navigateSync: jest.fn() }), - setActiveNavItemId: jest.fn(), + locatorNavigation, + activeNavItemId$: new BehaviorSubject('test.hello.lamp'), navIsOpen: true, recentItems, }; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index 3e9abf118fe7e..b29389662380b 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -9,6 +9,8 @@ import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock'; import { SerializableRecord } from '@kbn/utility-types'; import { action } from '@storybook/addon-actions'; +import { BehaviorSubject } from 'rxjs'; +import { getLocatorNavigation } from '../../src/services'; import { NavigationProps, NavigationServices } from '../../types'; import { GetLocatorFn } from '../../types/internal'; @@ -43,13 +45,17 @@ export class StorybookMock extends AbstractStorybookMock { + const activeNavItemId$ = new BehaviorSubject(params.activeNavItemId ?? ''); + const setActiveNavItemId = (id: string) => { + activeNavItemId$.next(id); activeNavItemIdAction(id); }; + const locatorNavigation = getLocatorNavigation(getLocator, setActiveNavItemId); + return { - getLocator, - setActiveNavItemId, + activeNavItemId$, + locatorNavigation, navIsOpen, recentItems, }; diff --git a/packages/shared-ux/chrome/navigation/src/model/model.ts b/packages/shared-ux/chrome/navigation/src/model/model.ts index c050ca7f0e1b0..b06a2615c6ea2 100644 --- a/packages/shared-ux/chrome/navigation/src/model/model.ts +++ b/packages/shared-ux/chrome/navigation/src/model/model.ts @@ -11,32 +11,75 @@ import { navItemSet, Platform } from '.'; import { NavigationBucketProps, NavigationProps, + NavItemProps, PlatformId, + PlatformSectionConfig, SolutionProperties, } from '../../types'; import { LocatorNavigationFn } from '../../types/internal'; +type MyEuiSideNavItem = EuiSideNavItemType; +type OnClickFn = MyEuiSideNavItem['onClick']; + +const createSideNavData = ( + parentIds: string | number = '', + navItems: NavItemProps[], + locatorNavigation: LocatorNavigationFn, + activeNav?: string | number, + config?: PlatformSectionConfig +): Array> => { + return navItems.reduce((accum, item) => { + const { id, name, items: subNav } = item; + const matcher = config?.properties?.[id]; + if (matcher?.enabled === false) { + // return accumulated set without the item that is not enabled + return accum; + } + + const fullId = [parentIds, id].filter(Boolean).join('.'); + + let onClick: OnClickFn | undefined; + if (item.locator) { + // TODO check that the locator instance is valid before rendering the link + onClick = locatorNavigation({ ...item, id: fullId }); + } + + let filteredSubNav: MyEuiSideNavItem[] | undefined; + if (subNav) { + // recursion + const nextConfig = config?.properties?.[id]; + filteredSubNav = createSideNavData(fullId, subNav, locatorNavigation, activeNav, nextConfig); + } + + let isSelected: boolean = false; + if (!subNav && fullId === activeNav) { + // if there are no subnav items and ID is current, mark the item as selected + isSelected = true; + } + + const next: MyEuiSideNavItem = { + id: fullId, + name, + isSelected, + onClick, + items: filteredSubNav, + ['data-test-subj']: `nav-item-${fullId}`, + }; + return [...accum, next]; + }, []); +}; + /** * @internal */ export class NavigationModel { - private common: { - locatorNavigation: LocatorNavigationFn; - activeNavItemId?: string; - }; - constructor( - locatorNavigation: LocatorNavigationFn, - activeNavItemId: string | undefined, + private locatorNavigation: LocatorNavigationFn, + private activeNavItemId: string, private recentItems: Array> | undefined, private platformConfig: NavigationProps['platformConfig'] | undefined, private solutions: SolutionProperties[] - ) { - this.common = { - locatorNavigation, - activeNavItemId, - }; - } + ) {} public getRecent(): NavigationBucketProps { return { @@ -44,43 +87,64 @@ export class NavigationModel { icon: 'clock', name: 'Recent', items: this.recentItems, - ...this.common, }; } + private convertToSideNavItems( + id: string, + items: NavItemProps[] | undefined, + platformConfig?: PlatformSectionConfig + ) { + return items + ? createSideNavData(id, items, this.locatorNavigation, this.activeNavItemId, platformConfig) + : undefined; + } + public getPlatform(): Record { return { [Platform.Analytics]: { id: Platform.Analytics, icon: 'stats', name: 'Data exploration', - items: navItemSet[Platform.Analytics], - platformConfig: this.platformConfig?.[Platform.Analytics], - ...this.common, + items: this.convertToSideNavItems( + Platform.Analytics, + navItemSet[Platform.Analytics], + this.platformConfig?.[Platform.Analytics] + ), + activeNavItemId: this.activeNavItemId, }, [Platform.MachineLearning]: { id: Platform.MachineLearning, icon: 'indexMapping', name: 'Machine learning', - items: navItemSet[Platform.MachineLearning], - platformConfig: this.platformConfig?.[Platform.MachineLearning], - ...this.common, + items: this.convertToSideNavItems( + Platform.MachineLearning, + navItemSet[Platform.MachineLearning], + this.platformConfig?.[Platform.MachineLearning] + ), + activeNavItemId: this.activeNavItemId, }, [Platform.DevTools]: { id: Platform.DevTools, icon: 'editorCodeBlock', name: 'Developer tools', - items: navItemSet[Platform.DevTools], - platformConfig: this.platformConfig?.[Platform.DevTools], - ...this.common, + items: this.convertToSideNavItems( + Platform.DevTools, + navItemSet[Platform.DevTools], + this.platformConfig?.[Platform.DevTools] + ), + activeNavItemId: this.activeNavItemId, }, [Platform.Management]: { id: Platform.Management, icon: 'gear', name: 'Management', - items: navItemSet[Platform.Management], - platformConfig: this.platformConfig?.[Platform.Management], - ...this.common, + items: this.convertToSideNavItems( + Platform.Management, + navItemSet[Platform.Management], + this.platformConfig?.[Platform.Management] + ), + activeNavItemId: this.activeNavItemId, }, }; } @@ -91,10 +155,8 @@ export class NavigationModel { id: s.id, name: s.name, icon: s.icon, - items: s.items, - ...this.common, + items: this.convertToSideNavItems(s.id, s.items), + activeNavItemId: this.activeNavItemId, })); } - - // public findById(): {}; TODO } diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index 4599b75c85bc9..5b379bd06cddf 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -8,17 +8,41 @@ import React, { FC, useContext } from 'react'; import useObservable from 'react-use/lib/useObservable'; -import { NavigationKibanaDependencies, NavigationServices } from '../types'; +import { BehaviorSubject } from 'rxjs'; +import { NavigationKibanaDependencies, NavigationServices, NavItemProps } from '../types'; +import { GetLocatorFn, LocatorNavigationFn, SetActiveNavItemIdFn } from '../types/internal'; const Context = React.createContext(null); +export const getLocatorNavigation = ( + getLocator: GetLocatorFn, + setActiveNavItemId: SetActiveNavItemIdFn +): LocatorNavigationFn => { + const locatorNavigation = (item: NavItemProps | undefined) => () => { + if (item) { + const { locator, id } = item; + setActiveNavItemId(id as string); // FIXME handle if navigation action fails + if (locator) { + const locatorInstance = getLocator(locator.id); + + if (!locatorInstance) { + throw new Error(`Unresolved Locator instance for ${locator.id}`); + } + + locatorInstance.navigateSync(locator.params ?? {}); + } + } + }; + return locatorNavigation; +}; + /** * A Context Provider that provides services to the component and its dependencies. */ export const NavigationProvider: FC = ({ children, ...services }) => { - const { getLocator, recentItems, navIsOpen, setActiveNavItemId } = services; + const { recentItems, navIsOpen, locatorNavigation, activeNavItemId$ } = services; return ( - + {children} ); @@ -39,15 +63,18 @@ export const NavigationKibanaProvider: FC = ({ const getLocator = (id: string) => dependencies.share.url.locators.get(id); const navIsOpen = useObservable(dependencies.core.chrome.getProjectNavIsOpen$(), true); - const setActiveNavItemId = (id: string | number) => { - console.log({ newActiveItemId: id }); + const activeNavItemId$ = new BehaviorSubject(''); + const setActiveNavItemId = (id: string) => { + activeNavItemId$.next(id); }; + const locatorNavigation = getLocatorNavigation(getLocator, setActiveNavItemId); + const value: NavigationServices = { + locatorNavigation, navIsOpen, recentItems, - getLocator, - setActiveNavItemId, + activeNavItemId$, }; return ( diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx index 4b866fa12073a..a6c4a3fc57be2 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { render } from 'react-dom'; +import { BehaviorSubject } from 'rxjs'; import { getServicesMock } from '../../mocks/src/jest'; import { NavigationProvider } from '../services'; import { Navigation } from './navigation'; @@ -15,7 +16,7 @@ import { Navigation } from './navigation'; describe('', () => { test('renders with minimal props', () => { const div = document.createElement('div'); - const { getLocator, setActiveNavItemId } = getServicesMock(); + const { locatorNavigation } = getServicesMock(); const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; const platformSections = {}; @@ -26,13 +27,14 @@ describe('', () => { icon: 'gear', }, ]; + const activeNavItemId$ = new BehaviorSubject('hello'); render( , diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index b9ea05a608294..baf72dfe1a689 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -16,10 +16,10 @@ import { useEuiTheme, } from '@elastic/eui'; import React from 'react'; +import useObservable from 'react-use/lib/useObservable'; import { NavigationProps } from '../../types'; import { NavigationModel } from '../model'; import { useNavigation } from '../services'; -import { getLocatorNavigation } from '../utils'; import { ElasticMark } from './elastic_mark'; import './header_logo.scss'; import { NavigationBucket } from './navigation_bucket'; @@ -28,11 +28,11 @@ export const Navigation = (props: NavigationProps) => { // const { euiTheme } = useEuiTheme(); // const { fontSize: navSectionFontSize } = useEuiFontSize('m'); // const { fontSize: navItemFontSize } = useEuiFontSize('s'); - const { getLocator, recentItems, navIsOpen, setActiveNavItemId } = useNavigation(); - const { euiTheme } = useEuiTheme(); + const { locatorNavigation, recentItems, navIsOpen, activeNavItemId$ } = useNavigation(); + const activeNavItemId = useObservable(activeNavItemId$, props.activeNavItemId); - const locatorNavigation = getLocatorNavigation(getLocator, setActiveNavItemId); + const { euiTheme } = useEuiTheme(); let euiSideNavRecentItems: Array> | undefined; if (recentItems) { @@ -54,11 +54,12 @@ export const Navigation = (props: NavigationProps) => { const nav = new NavigationModel( locatorNavigation, - props.activeNavItemId, + activeNavItemId ?? '', euiSideNavRecentItems, props.platformConfig, props.solutions ); + const recent = nav.getRecent(); const solutions = nav.getSolutions(); const { analytics, ml, devTools, management } = nav.getPlatform(); diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx index bd154174e8836..cbcf82e81a894 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx @@ -6,36 +6,16 @@ * Side Public License, v 1. */ -import { EuiCollapsibleNavGroup, EuiIcon, EuiSideNav, EuiSideNavItemType } from '@elastic/eui'; +import { EuiCollapsibleNavGroup, EuiIcon, EuiSideNav } from '@elastic/eui'; import React from 'react'; import { NavigationBucketProps } from '../../types'; import { useNavigation } from '../services'; import { navigationStyles as styles } from '../styles'; -import { convertNavItemsToEui } from '../utils'; export const NavigationBucket = (opts: NavigationBucketProps) => { - const { id, items, platformConfig, activeNavItemId, ...props } = opts; + const { id, items, activeNavItemId, ...props } = opts; const { navIsOpen } = useNavigation(); - let euiSideNavItems: Array> | undefined; - - if (platformConfig) { - // ability to turn off platform section in the nav - if (platformConfig?.enabled === false) { - return null; - } - } - - if (items) { - euiSideNavItems = convertNavItemsToEui( - items, - props.locatorNavigation, - platformConfig, - activeNavItemId, - id - ); - } - if (navIsOpen) { return ( { initialIsOpen={activeNavItemId?.startsWith(id + '.')} data-test-subj={`nav-bucket-${id}`} > - + ); } diff --git a/packages/shared-ux/chrome/navigation/src/utils.tsx b/packages/shared-ux/chrome/navigation/src/utils.tsx deleted file mode 100644 index bb881107890bb..0000000000000 --- a/packages/shared-ux/chrome/navigation/src/utils.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { EuiSideNavItemType } from '@elastic/eui'; -import { NavItemProps, PlatformSectionConfig } from '../types'; -import { GetLocatorFn, LocatorNavigationFn, SetActiveNavItemIdFn } from '../types/internal'; - -export const getLocatorNavigation = ( - getLocator: GetLocatorFn, - setActiveNavItemId: SetActiveNavItemIdFn -): LocatorNavigationFn => { - const locatorNavigation = (item: NavItemProps | undefined) => () => { - if (item) { - const { locator, id } = item; - setActiveNavItemId(id); - if (locator) { - const locatorInstance = getLocator(locator.id); - - if (!locatorInstance) { - throw new Error(`Unresolved Locator instance for ${locator.id}`); - } - - locatorInstance.navigateSync(locator.params ?? {}); - } - } - }; - return locatorNavigation; -}; - -type MyEuiSideNavItem = EuiSideNavItemType; -type OnClickFn = MyEuiSideNavItem['onClick']; - -export const convertNavItemsToEui = ( - navItems: NavItemProps[], - locatorNavigation: LocatorNavigationFn, - config?: PlatformSectionConfig, - activeNav?: string, - parentNavPath: string | number = '' -): Array> => { - return navItems.reduce((accum, item) => { - const { id, name, items: subNav } = item; - const matcher = config?.properties?.[id]; - if (matcher?.enabled === false) { - // return accumulated set without the item that is not enabled - return accum; - } - - const fullId = [parentNavPath, id].filter(Boolean).join('.'); - - let onClick: OnClickFn | undefined; - if (item.locator) { - onClick = locatorNavigation({ ...item, id: fullId }); - } - - let filteredSubNav: NavItemProps[] | undefined; - if (subNav) { - // recursion - const nextConfig = config?.properties?.[id]; - filteredSubNav = convertNavItemsToEui( - subNav, - locatorNavigation, - nextConfig, - activeNav, - fullId - ); - } - - let isSelected: boolean = false; - if (!subNav && fullId === activeNav) { - // if there are no subnav items and ID is current, mark the item as selected - isSelected = true; - } - - const next: MyEuiSideNavItem = { - id: fullId, - name, - isSelected, - onClick, - items: filteredSubNav, - ['data-test-subj']: `nav-item-${fullId}`, - }; - return [...accum, next]; - }, []); -}; diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index a5d09d459101e..cad3931a87384 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -7,22 +7,16 @@ */ import type { EuiSideNavItemType, IconType } from '@elastic/eui'; -import { Observable } from 'rxjs'; -import { - GetLocatorFn, - ILocatorDefinition, - LocatorNavigationFn, - RecentItem, - SetActiveNavItemIdFn, -} from './internal'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { GetLocatorFn, ILocatorDefinition, LocatorNavigationFn, RecentItem } from './internal'; /** * A list of services that are consumed by this component. * @public */ export interface NavigationServices { - getLocator: GetLocatorFn; - setActiveNavItemId: SetActiveNavItemIdFn; + locatorNavigation: LocatorNavigationFn; + activeNavItemId$: BehaviorSubject; navIsOpen: boolean; recentItems: RecentItem[]; } @@ -124,6 +118,5 @@ export interface NavigationProps { export type NavigationBucketProps = (SolutionProperties & Pick) & { - locatorNavigation: LocatorNavigationFn; platformConfig?: PlatformSectionConfig; }; diff --git a/packages/shared-ux/chrome/navigation/types/internal.ts b/packages/shared-ux/chrome/navigation/types/internal.ts index e059c2096cca7..329d47b4081aa 100644 --- a/packages/shared-ux/chrome/navigation/types/internal.ts +++ b/packages/shared-ux/chrome/navigation/types/internal.ts @@ -24,7 +24,7 @@ export type GetLocatorFn = (locatorId: string) => ILocatorPublic | undefined; /** * @internal */ -export type SetActiveNavItemIdFn = (activeNavItemId: string | number) => void; +export type SetActiveNavItemIdFn = (activeNavItemId: string) => void; /** * @internal From 56256ab671028e7dbe6aaa434da37f73ef55aa26 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Mon, 10 Apr 2023 17:20:44 -0700 Subject: [PATCH 06/41] fix home href --- .../chrome/navigation/mocks/src/storybook.ts | 1 + .../shared-ux/chrome/navigation/src/model/model.ts | 4 ++++ .../navigation/src/ui/navigation.stories.tsx | 14 ++++++++++---- .../chrome/navigation/src/ui/navigation.test.tsx | 3 ++- .../chrome/navigation/src/ui/navigation.tsx | 10 +++++----- .../shared-ux/chrome/navigation/types/index.ts | 4 ++++ 6 files changed, 26 insertions(+), 10 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index b29389662380b..fb3a026c241e9 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -66,6 +66,7 @@ export class StorybookMock extends AbstractStorybookMock', () => { const div = document.createElement('div'); const { locatorNavigation } = getServicesMock(); + const homeHref = '#'; const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; const platformSections = {}; const solutions = [ @@ -36,7 +37,7 @@ describe('', () => { recentItems={recentItems} locatorNavigation={locatorNavigation} > - + , div ); diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index baf72dfe1a689..16e96dbfecc75 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -73,7 +73,7 @@ export const Navigation = (props: NavigationProps) => { > e.preventDefault()} aria-label="Go to home page" /> @@ -86,8 +86,8 @@ export const Navigation = (props: NavigationProps) => { return ; })} - - + {nav.isEnabled('analytics') ? : null} + {nav.isEnabled('ml') ? : null} @@ -95,8 +95,8 @@ export const Navigation = (props: NavigationProps) => { - - + {nav.isEnabled('devTools') ? : null} + {nav.isEnabled('management') ? : null} ); diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index cad3931a87384..de3a2bc051083 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -114,6 +114,10 @@ export interface NavigationProps { * Controls over how Platform nav sections appear */ platformConfig?: Partial; + /** + * Target for the logo icon + */ + homeHref: string; } export type NavigationBucketProps = (SolutionProperties & From 3df721dece202f55b1b5130975428370254eae6c Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 11 Apr 2023 09:30:52 -0700 Subject: [PATCH 07/41] fix logo link --- packages/shared-ux/chrome/navigation/src/ui/navigation.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index 16e96dbfecc75..c64f2dd5f32d9 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -74,7 +74,6 @@ export const Navigation = (props: NavigationProps) => { e.preventDefault()} aria-label="Go to home page" /> {navIsOpen ? : null} From 30283c34f95ddac1586cc2ae2709c1fbec5116f0 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 11 Apr 2023 09:56:57 -0700 Subject: [PATCH 08/41] fix some mgmt links --- .../navigation/src/model/platform_nav/management.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts index e787e0921fe59..6b3cf270129f1 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts @@ -50,7 +50,7 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Logstash pipelines', id: 'logstash_pipelines', - ...locators.management({ sectionId: 'logstash', appId: 'pipelines' }), + ...locators.management({ sectionId: 'ingest', appId: 'pipelines' }), }, ], }, @@ -167,12 +167,12 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Data view', id: 'data_views', - ...locators.management({ sectionId: 'kibana', appId: 'data_views' }), + ...locators.management({ sectionId: 'kibana', appId: 'dataViews' }), }, { name: 'Saved objects', id: 'saved_objects', - ...locators.management({ sectionId: 'kibana', appId: 'saved_objects' }), + ...locators.management({ sectionId: 'kibana', appId: 'objects' }), }, { name: 'Tags', @@ -199,7 +199,7 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Upgrade assistant', id: 'upgrade_assistant', - ...locators.management({ sectionId: 'kibana', appId: 'stack/upgrade_assistant' }), + ...locators.management({ sectionId: 'stack', appId: 'upgrade_assistant' }), }, ], }, From e843ca669438ce8bcf8d861fb1f1ec95af256475 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 11 Apr 2023 14:30:10 -0700 Subject: [PATCH 09/41] service to provide `getLocator`, not a wrapper --- .../chrome/navigation/mocks/src/jest.ts | 8 +- .../chrome/navigation/mocks/src/storybook.ts | 15 +-- .../chrome/navigation/src/model/model.ts | 125 ++++++++++-------- .../chrome/navigation/src/services.tsx | 45 +------ .../navigation/src/ui/navigation.test.tsx | 9 +- .../chrome/navigation/src/ui/navigation.tsx | 8 +- .../chrome/navigation/types/index.ts | 9 +- .../chrome/navigation/types/internal.ts | 3 - 8 files changed, 94 insertions(+), 128 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index 73ace23577070..b7ffd9915aea9 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -6,17 +6,15 @@ * Side Public License, v 1. */ -import { BehaviorSubject } from 'rxjs'; -import { getLocatorNavigation } from '../../src/services'; import { NavigationServices } from '../../types'; export const getServicesMock = (): NavigationServices => { - const locatorNavigation = getLocatorNavigation(jest.fn(), jest.fn()); + const getLocator = jest.fn(); const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; return { - locatorNavigation, - activeNavItemId$: new BehaviorSubject('test.hello.lamp'), + getLocator, + activeNavItemId: 'test.hello.lamp', navIsOpen: true, recentItems, }; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index fb3a026c241e9..f63ab7773197b 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -9,8 +9,6 @@ import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock'; import { SerializableRecord } from '@kbn/utility-types'; import { action } from '@storybook/addon-actions'; -import { BehaviorSubject } from 'rxjs'; -import { getLocatorNavigation } from '../../src/services'; import { NavigationProps, NavigationServices } from '../../types'; import { GetLocatorFn } from '../../types/internal'; @@ -37,7 +35,6 @@ export class StorybookMock extends AbstractStorybookMock ({ navigateSync: (locatorParams?: SerializableRecord) => { @@ -45,17 +42,9 @@ export class StorybookMock extends AbstractStorybookMock { - activeNavItemId$.next(id); - activeNavItemIdAction(id); - }; - - const locatorNavigation = getLocatorNavigation(getLocator, setActiveNavItemId); - return { - activeNavItemId$, - locatorNavigation, + activeNavItemId: 'test1', + getLocator, navIsOpen, recentItems, }; diff --git a/packages/shared-ux/chrome/navigation/src/model/model.ts b/packages/shared-ux/chrome/navigation/src/model/model.ts index 2ed55c1c95126..50edf86ce9a5e 100644 --- a/packages/shared-ux/chrome/navigation/src/model/model.ts +++ b/packages/shared-ux/chrome/navigation/src/model/model.ts @@ -16,70 +16,91 @@ import { PlatformSectionConfig, SolutionProperties, } from '../../types'; -import { LocatorNavigationFn } from '../../types/internal'; +import { GetLocatorFn, ILocatorPublic } from '../../types/internal'; type MyEuiSideNavItem = EuiSideNavItemType; type OnClickFn = MyEuiSideNavItem['onClick']; -const createSideNavData = ( - parentIds: string | number = '', - navItems: NavItemProps[], - locatorNavigation: LocatorNavigationFn, - activeNav?: string | number, - config?: PlatformSectionConfig -): Array> => { - return navItems.reduce((accum, item) => { - const { id, name, items: subNav } = item; - const matcher = config?.properties?.[id]; - if (matcher?.enabled === false) { - // return accumulated set without the item that is not enabled - return accum; - } - - const fullId = [parentIds, id].filter(Boolean).join('.'); - - let onClick: OnClickFn | undefined; - if (item.locator) { - // TODO check that the locator instance is valid before rendering the link - onClick = locatorNavigation({ ...item, id: fullId }); - } - - let filteredSubNav: MyEuiSideNavItem[] | undefined; - if (subNav) { - // recursion - const nextConfig = config?.properties?.[id]; - filteredSubNav = createSideNavData(fullId, subNav, locatorNavigation, activeNav, nextConfig); - } - - let isSelected: boolean = false; - if (!subNav && fullId === activeNav) { - // if there are no subnav items and ID is current, mark the item as selected - isSelected = true; - } - - const next: MyEuiSideNavItem = { - id: fullId, - name, - isSelected, - onClick, - items: filteredSubNav, - ['data-test-subj']: `nav-item-${fullId}`, - }; - return [...accum, next]; - }, []); +const createSideNavDataFactory = (getLocator: GetLocatorFn, activeNav?: string | number) => { + const createSideNavData = ( + parentIds: string | number = '', + navItems: NavItemProps[], + config?: PlatformSectionConfig + ): Array> => { + return navItems.reduce((accum, item) => { + const { id, name, items: subNav, locator: locatorDefinition } = item; + const matcher = config?.properties?.[id]; + if (matcher?.enabled === false) { + // return accumulated set without the item that is not enabled + return accum; + } + + const { id: locatorId, params: locatorParams } = locatorDefinition ?? {}; + let locator: ILocatorPublic | undefined; + if (locatorId) { + locator = getLocator(locatorId); + } + + if (locatorId && !locator) { + console.warn(`Invalid locator ID provided: ${locatorId}`); + } + + const fullId = [parentIds, id].filter(Boolean).join('.'); + + let onClick: OnClickFn | undefined; + if (locatorParams) { + onClick = () => { + locator?.navigateSync(locatorParams ?? {}); + }; + } + + let filteredSubNav: MyEuiSideNavItem[] | undefined; + if (subNav) { + // recursion + const nextConfig = config?.properties?.[id]; + filteredSubNav = createSideNavData(fullId, subNav, nextConfig); + } + + let isSelected: boolean = false; + if (!subNav && fullId === activeNav) { + // if there are no subnav items and ID is current, mark the item as selected + isSelected = true; + } + + const next: MyEuiSideNavItem = { + id: fullId, + name, + isSelected, + onClick, + items: filteredSubNav, + ['data-test-subj']: `nav-item-${fullId}`, + }; + return [...accum, next]; + }, []); + }; + + return createSideNavData; }; /** * @internal */ export class NavigationModel { + private createSideNavData: ( + parentIds: string | number | undefined, + navItems: Array>, + config?: PlatformSectionConfig | undefined + ) => Array>; + constructor( - private locatorNavigation: LocatorNavigationFn, - private activeNavItemId: string, + getLocator: GetLocatorFn, + private activeNavItemId: string | undefined, private recentItems: Array> | undefined, private platformConfig: NavigationProps['platformConfig'] | undefined, private solutions: SolutionProperties[] - ) {} + ) { + this.createSideNavData = createSideNavDataFactory(getLocator, activeNavItemId); + } public getRecent(): NavigationBucketProps { return { @@ -95,9 +116,7 @@ export class NavigationModel { items: NavItemProps[] | undefined, platformConfig?: PlatformSectionConfig ) { - return items - ? createSideNavData(id, items, this.locatorNavigation, this.activeNavItemId, platformConfig) - : undefined; + return items ? this.createSideNavData(id, items, platformConfig) : undefined; } public getPlatform(): Record { diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index 5b379bd06cddf..8996538571b0b 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -8,44 +8,15 @@ import React, { FC, useContext } from 'react'; import useObservable from 'react-use/lib/useObservable'; -import { BehaviorSubject } from 'rxjs'; -import { NavigationKibanaDependencies, NavigationServices, NavItemProps } from '../types'; -import { GetLocatorFn, LocatorNavigationFn, SetActiveNavItemIdFn } from '../types/internal'; +import { NavigationKibanaDependencies, NavigationServices } from '../types'; const Context = React.createContext(null); -export const getLocatorNavigation = ( - getLocator: GetLocatorFn, - setActiveNavItemId: SetActiveNavItemIdFn -): LocatorNavigationFn => { - const locatorNavigation = (item: NavItemProps | undefined) => () => { - if (item) { - const { locator, id } = item; - setActiveNavItemId(id as string); // FIXME handle if navigation action fails - if (locator) { - const locatorInstance = getLocator(locator.id); - - if (!locatorInstance) { - throw new Error(`Unresolved Locator instance for ${locator.id}`); - } - - locatorInstance.navigateSync(locator.params ?? {}); - } - } - }; - return locatorNavigation; -}; - /** * A Context Provider that provides services to the component and its dependencies. */ export const NavigationProvider: FC = ({ children, ...services }) => { - const { recentItems, navIsOpen, locatorNavigation, activeNavItemId$ } = services; - return ( - - {children} - - ); + return {children}; }; /** @@ -62,19 +33,13 @@ export const NavigationKibanaProvider: FC = ({ const getLocator = (id: string) => dependencies.share.url.locators.get(id); const navIsOpen = useObservable(dependencies.core.chrome.getProjectNavIsOpen$(), true); - - const activeNavItemId$ = new BehaviorSubject(''); - const setActiveNavItemId = (id: string) => { - activeNavItemId$.next(id); - }; - - const locatorNavigation = getLocatorNavigation(getLocator, setActiveNavItemId); + const activeNavItemId = useObservable(dependencies.core.chrome.getActiveNavItemId$()); const value: NavigationServices = { - locatorNavigation, + getLocator, navIsOpen, recentItems, - activeNavItemId$, + activeNavItemId, }; return ( diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx index a6ec39c5a85ce..a725765e1a415 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { render } from 'react-dom'; -import { BehaviorSubject } from 'rxjs'; import { getServicesMock } from '../../mocks/src/jest'; import { NavigationProvider } from '../services'; import { Navigation } from './navigation'; @@ -16,7 +15,7 @@ import { Navigation } from './navigation'; describe('', () => { test('renders with minimal props', () => { const div = document.createElement('div'); - const { locatorNavigation } = getServicesMock(); + const { getLocator } = getServicesMock(); const homeHref = '#'; const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; @@ -28,14 +27,14 @@ describe('', () => { icon: 'gear', }, ]; - const activeNavItemId$ = new BehaviorSubject('hello'); + const activeNavItemId = 'hello.test.1'; render( , diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index c64f2dd5f32d9..94392e659a05a 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -16,7 +16,6 @@ import { useEuiTheme, } from '@elastic/eui'; import React from 'react'; -import useObservable from 'react-use/lib/useObservable'; import { NavigationProps } from '../../types'; import { NavigationModel } from '../model'; import { useNavigation } from '../services'; @@ -29,8 +28,7 @@ export const Navigation = (props: NavigationProps) => { // const { fontSize: navSectionFontSize } = useEuiFontSize('m'); // const { fontSize: navItemFontSize } = useEuiFontSize('s'); - const { locatorNavigation, recentItems, navIsOpen, activeNavItemId$ } = useNavigation(); - const activeNavItemId = useObservable(activeNavItemId$, props.activeNavItemId); + const { recentItems, navIsOpen, activeNavItemId, getLocator } = useNavigation(); const { euiTheme } = useEuiTheme(); @@ -53,8 +51,8 @@ export const Navigation = (props: NavigationProps) => { } const nav = new NavigationModel( - locatorNavigation, - activeNavItemId ?? '', + getLocator, + activeNavItemId, euiSideNavRecentItems, props.platformConfig, props.solutions diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index de3a2bc051083..c7d13cca95a07 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -7,17 +7,17 @@ */ import type { EuiSideNavItemType, IconType } from '@elastic/eui'; -import { BehaviorSubject, Observable } from 'rxjs'; -import { GetLocatorFn, ILocatorDefinition, LocatorNavigationFn, RecentItem } from './internal'; +import { Observable } from 'rxjs'; +import { GetLocatorFn, ILocatorDefinition, ILocatorPublic, RecentItem } from './internal'; /** * A list of services that are consumed by this component. * @public */ export interface NavigationServices { - locatorNavigation: LocatorNavigationFn; - activeNavItemId$: BehaviorSubject; + getLocator: (id: string) => ILocatorPublic | undefined; navIsOpen: boolean; + activeNavItemId: string | undefined; recentItems: RecentItem[]; } @@ -35,6 +35,7 @@ export interface NavigationKibanaDependencies { core: { chrome: { getProjectNavIsOpen$: () => Observable; + getActiveNavItemId$: () => Observable; recentlyAccessed: { get$: () => Observable; }; diff --git a/packages/shared-ux/chrome/navigation/types/internal.ts b/packages/shared-ux/chrome/navigation/types/internal.ts index 329d47b4081aa..bba89a8f0e110 100644 --- a/packages/shared-ux/chrome/navigation/types/internal.ts +++ b/packages/shared-ux/chrome/navigation/types/internal.ts @@ -7,7 +7,6 @@ */ import { SerializableRecord } from '@kbn/utility-types'; -import { NavItemProps } from '.'; /** * @internal @@ -49,5 +48,3 @@ export interface ILocatorDefinition

{ */ params?: P; } - -export type LocatorNavigationFn = (item: NavItemProps | undefined) => () => void; From 6e29bf6594942fefddb1c18c109d7b8f56a5b2e1 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 11 Apr 2023 15:06:43 -0700 Subject: [PATCH 10/41] Add registerNavItemClick to services --- .../chrome/navigation/mocks/src/jest.ts | 2 + .../chrome/navigation/mocks/src/storybook.ts | 13 ++++-- .../chrome/navigation/src/model/model.ts | 41 +++++++++++++------ .../chrome/navigation/src/services.tsx | 7 +++- .../navigation/src/ui/navigation.test.tsx | 3 +- .../chrome/navigation/src/ui/navigation.tsx | 4 +- .../chrome/navigation/types/index.ts | 10 +++-- .../chrome/navigation/types/internal.ts | 5 +++ 8 files changed, 61 insertions(+), 24 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index b7ffd9915aea9..501d0dca5a119 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -11,9 +11,11 @@ import { NavigationServices } from '../../types'; export const getServicesMock = (): NavigationServices => { const getLocator = jest.fn(); const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; + const registerNavItemClick = jest.fn(); return { getLocator, + registerNavItemClick, activeNavItemId: 'test.hello.lamp', navIsOpen: true, recentItems, diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index f63ab7773197b..8a27d89fa057e 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -9,6 +9,7 @@ import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock'; import { SerializableRecord } from '@kbn/utility-types'; import { action } from '@storybook/addon-actions'; +import { BehaviorSubject } from 'rxjs'; import { NavigationProps, NavigationServices } from '../../types'; import { GetLocatorFn } from '../../types/internal'; @@ -35,6 +36,7 @@ export class StorybookMock extends AbstractStorybookMock ({ navigateSync: (locatorParams?: SerializableRecord) => { @@ -42,19 +44,24 @@ export class StorybookMock extends AbstractStorybookMock(undefined); + const registerNavItemClick = (id: string) => { + activeNavItemId$.next(id); + registerNavItemClickAction(id); + }; + return { - activeNavItemId: 'test1', + ...params, getLocator, navIsOpen, recentItems, + registerNavItemClick, }; } getProps(params: Params): NavigationProps { - const { activeNavItemId: initiallyOpenSections } = params; return { ...params, - activeNavItemId: initiallyOpenSections, homeHref: '#', }; } diff --git a/packages/shared-ux/chrome/navigation/src/model/model.ts b/packages/shared-ux/chrome/navigation/src/model/model.ts index 50edf86ce9a5e..e8577b5f3f7d7 100644 --- a/packages/shared-ux/chrome/navigation/src/model/model.ts +++ b/packages/shared-ux/chrome/navigation/src/model/model.ts @@ -16,21 +16,29 @@ import { PlatformSectionConfig, SolutionProperties, } from '../../types'; -import { GetLocatorFn, ILocatorPublic } from '../../types/internal'; +import { GetLocatorFn, ILocatorPublic, NavItemClickFn } from '../../types/internal'; type MyEuiSideNavItem = EuiSideNavItemType; type OnClickFn = MyEuiSideNavItem['onClick']; -const createSideNavDataFactory = (getLocator: GetLocatorFn, activeNav?: string | number) => { +/** + * Factory function to return a function that processes modeled nav items into EuiSideNavItemType + * The factory puts memoized function arguments in scope for iterations of the recursive item processing. + */ +const createSideNavDataFactory = ( + getLocator: GetLocatorFn, + registerNavItemClick: NavItemClickFn, + activeNav?: string | number +) => { const createSideNavData = ( parentIds: string | number = '', navItems: NavItemProps[], - config?: PlatformSectionConfig - ): Array> => { - return navItems.reduce((accum, item) => { + platformSectionConfig?: PlatformSectionConfig + ): Array> => + navItems.reduce((accum, item) => { const { id, name, items: subNav, locator: locatorDefinition } = item; - const matcher = config?.properties?.[id]; - if (matcher?.enabled === false) { + const config = platformSectionConfig?.properties?.[id]; + if (config?.enabled === false) { // return accumulated set without the item that is not enabled return accum; } @@ -42,22 +50,25 @@ const createSideNavDataFactory = (getLocator: GetLocatorFn, activeNav?: string | } if (locatorId && !locator) { - console.warn(`Invalid locator ID provided: ${locatorId}`); + // console.warn(`Invalid locator ID provided: ${locatorId}`); + return accum; } const fullId = [parentIds, id].filter(Boolean).join('.'); let onClick: OnClickFn | undefined; - if (locatorParams) { + if (locator && locatorParams) { + const outerLocator = locator; onClick = () => { - locator?.navigateSync(locatorParams ?? {}); + outerLocator.navigateSync(locatorParams ?? {}); + registerNavItemClick(fullId); }; } let filteredSubNav: MyEuiSideNavItem[] | undefined; if (subNav) { // recursion - const nextConfig = config?.properties?.[id]; + const nextConfig = platformSectionConfig?.properties?.[id]; filteredSubNav = createSideNavData(fullId, subNav, nextConfig); } @@ -77,7 +88,6 @@ const createSideNavDataFactory = (getLocator: GetLocatorFn, activeNav?: string | }; return [...accum, next]; }, []); - }; return createSideNavData; }; @@ -94,12 +104,17 @@ export class NavigationModel { constructor( getLocator: GetLocatorFn, + registerNavItemClick: NavItemClickFn, private activeNavItemId: string | undefined, private recentItems: Array> | undefined, private platformConfig: NavigationProps['platformConfig'] | undefined, private solutions: SolutionProperties[] ) { - this.createSideNavData = createSideNavDataFactory(getLocator, activeNavItemId); + this.createSideNavData = createSideNavDataFactory( + getLocator, + registerNavItemClick, + activeNavItemId + ); } public getRecent(): NavigationBucketProps { diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index 8996538571b0b..da5007c58e426 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -31,15 +31,18 @@ export const NavigationKibanaProvider: FC = ({ // const recentItems = useObservable(recentItems$, []); const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; - const getLocator = (id: string) => dependencies.share.url.locators.get(id); const navIsOpen = useObservable(dependencies.core.chrome.getProjectNavIsOpen$(), true); const activeNavItemId = useObservable(dependencies.core.chrome.getActiveNavItemId$()); + const getLocator = (id: string) => dependencies.share.url.locators.get(id); + const registerNavItemClick = dependencies.core.chrome.registerNavItemClick; + const value: NavigationServices = { - getLocator, navIsOpen, recentItems, activeNavItemId, + getLocator, + registerNavItemClick, }; return ( diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx index a725765e1a415..8f6fc1ab53669 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx @@ -15,7 +15,7 @@ import { Navigation } from './navigation'; describe('', () => { test('renders with minimal props', () => { const div = document.createElement('div'); - const { getLocator } = getServicesMock(); + const { getLocator, registerNavItemClick } = getServicesMock(); const homeHref = '#'; const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; @@ -32,6 +32,7 @@ describe('', () => { render( { // const { fontSize: navSectionFontSize } = useEuiFontSize('m'); // const { fontSize: navItemFontSize } = useEuiFontSize('s'); - const { recentItems, navIsOpen, activeNavItemId, getLocator } = useNavigation(); + const { recentItems, navIsOpen, activeNavItemId, getLocator, registerNavItemClick } = + useNavigation(); const { euiTheme } = useEuiTheme(); @@ -52,6 +53,7 @@ export const Navigation = (props: NavigationProps) => { const nav = new NavigationModel( getLocator, + registerNavItemClick, activeNavItemId, euiSideNavRecentItems, props.platformConfig, diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index c7d13cca95a07..05b238a692deb 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -8,17 +8,18 @@ import type { EuiSideNavItemType, IconType } from '@elastic/eui'; import { Observable } from 'rxjs'; -import { GetLocatorFn, ILocatorDefinition, ILocatorPublic, RecentItem } from './internal'; +import { GetLocatorFn, ILocatorDefinition, NavItemClickFn, RecentItem } from './internal'; /** * A list of services that are consumed by this component. * @public */ export interface NavigationServices { - getLocator: (id: string) => ILocatorPublic | undefined; navIsOpen: boolean; - activeNavItemId: string | undefined; recentItems: RecentItem[]; + activeNavItemId: string | undefined; + getLocator: GetLocatorFn; + registerNavItemClick: NavItemClickFn; } /** @@ -35,10 +36,11 @@ export interface NavigationKibanaDependencies { core: { chrome: { getProjectNavIsOpen$: () => Observable; - getActiveNavItemId$: () => Observable; recentlyAccessed: { get$: () => Observable; }; + getActiveNavItemId$: () => Observable; + registerNavItemClick: (id: string) => void; }; }; } diff --git a/packages/shared-ux/chrome/navigation/types/internal.ts b/packages/shared-ux/chrome/navigation/types/internal.ts index bba89a8f0e110..826d4f5ae2f90 100644 --- a/packages/shared-ux/chrome/navigation/types/internal.ts +++ b/packages/shared-ux/chrome/navigation/types/internal.ts @@ -20,6 +20,11 @@ export interface ILocatorPublic { */ export type GetLocatorFn = (locatorId: string) => ILocatorPublic | undefined; +/** + * @internal + */ +export type NavItemClickFn = (id: string) => void; + /** * @internal */ From b8ff745a4cac90593465cf12a9a448b3bb1548de Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 11 Apr 2023 16:06:27 -0700 Subject: [PATCH 11/41] href support, default activeNavId --- .../chrome/navigation/mocks/src/mocks.ts | 25 +++++++++---------- .../chrome/navigation/src/model/model.ts | 7 +++++- .../chrome/navigation/src/ui/navigation.tsx | 14 +++++------ .../chrome/navigation/types/index.ts | 9 ++++++- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/mocks/src/mocks.ts b/packages/shared-ux/chrome/navigation/mocks/src/mocks.ts index 37d18b35cbadd..1198fe0242eb8 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/mocks.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/mocks.ts @@ -23,39 +23,38 @@ export const mocks: SolutionProperties & { locatorId: string } = { { id: 'get_started', name: 'Get started', - locator: { id: MOCK_LOCATOR_ID, params: { view: '/app/observability/overview' } }, + href: '/app/example_project/get_started', }, { id: 'alerts', name: 'Alerts', - locator: { id: MOCK_LOCATOR_ID, params: { view: '/app/observability/alerts' } }, + href: '/app/example_project/alerts', }, { id: 'cases', name: 'Cases', - locator: { id: MOCK_LOCATOR_ID, params: { view: '/app/observability/cases' } }, + href: '/app/example_project/cases', }, ], }, { - id: 'signals', - name: 'Signals', + id: 'example_settings', + name: 'Settings', items: [ { id: 'logs', name: 'Logs', - locator: { - id: MOCK_LOCATOR_ID, - params: { view: '/app/management/ingest/ingest_pipelines' }, - }, + href: '/app/management/logs', + }, + { + id: 'signals', + name: 'Signals', + href: '/app/management/signals', }, { id: 'tracing', name: 'Tracing', - locator: { - id: MOCK_LOCATOR_ID, - params: { view: '/app/management/ingest/ingest_pipelines' }, - }, + href: '/app/management/tracing', }, ], }, diff --git a/packages/shared-ux/chrome/navigation/src/model/model.ts b/packages/shared-ux/chrome/navigation/src/model/model.ts index e8577b5f3f7d7..1297b48fc81ee 100644 --- a/packages/shared-ux/chrome/navigation/src/model/model.ts +++ b/packages/shared-ux/chrome/navigation/src/model/model.ts @@ -36,7 +36,7 @@ const createSideNavDataFactory = ( platformSectionConfig?: PlatformSectionConfig ): Array> => navItems.reduce((accum, item) => { - const { id, name, items: subNav, locator: locatorDefinition } = item; + const { id, name, items: subNav, locator: locatorDefinition, href } = item; const config = platformSectionConfig?.properties?.[id]; if (config?.enabled === false) { // return accumulated set without the item that is not enabled @@ -63,6 +63,10 @@ const createSideNavDataFactory = ( outerLocator.navigateSync(locatorParams ?? {}); registerNavItemClick(fullId); }; + } else if (href) { + onClick = () => { + registerNavItemClick(fullId); + }; } let filteredSubNav: MyEuiSideNavItem[] | undefined; @@ -83,6 +87,7 @@ const createSideNavDataFactory = ( name, isSelected, onClick, + href, items: filteredSubNav, ['data-test-subj']: `nav-item-${fullId}`, }; diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index 9a278e656cbca..23ddcbf39c00b 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -28,9 +28,7 @@ export const Navigation = (props: NavigationProps) => { // const { fontSize: navSectionFontSize } = useEuiFontSize('m'); // const { fontSize: navItemFontSize } = useEuiFontSize('s'); - const { recentItems, navIsOpen, activeNavItemId, getLocator, registerNavItemClick } = - useNavigation(); - + const { recentItems, ...services } = useNavigation(); const { euiTheme } = useEuiTheme(); let euiSideNavRecentItems: Array> | undefined; @@ -52,9 +50,9 @@ export const Navigation = (props: NavigationProps) => { } const nav = new NavigationModel( - getLocator, - registerNavItemClick, - activeNavItemId, + services.getLocator, + services.registerNavItemClick, + services.activeNavItemId ?? props.activeNavItemId, euiSideNavRecentItems, props.platformConfig, props.solutions @@ -76,7 +74,9 @@ export const Navigation = (props: NavigationProps) => { href={props.homeHref} aria-label="Go to home page" /> - {navIsOpen ? : null} + {services.navIsOpen ? ( + + ) : null} {euiSideNavRecentItems ? : null} diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index 05b238a692deb..b14b5d7fc70b9 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -35,10 +35,10 @@ export interface NavigationKibanaDependencies { }; core: { chrome: { - getProjectNavIsOpen$: () => Observable; recentlyAccessed: { get$: () => Observable; }; + getProjectNavIsOpen$: () => Observable; getActiveNavItemId$: () => Observable; registerNavItemClick: (id: string) => void; }; @@ -50,11 +50,18 @@ export interface NavigationKibanaDependencies { * @public */ export type NavItemProps = Pick, 'id' | 'name'> & { + /** + * Nav Items that could contain locators + */ items?: Array>; /** * ID of a registered LocatorDefinition */ locator?: ILocatorDefinition; + /** + * Href for a link destination (for links within a project) + */ + href?: string; }; /** From 20125e860384dfc67f3e63853a558ec31c100b72 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 11 Apr 2023 17:45:41 -0700 Subject: [PATCH 12/41] fill in some locator IDs --- .../chrome/navigation/src/model/model.ts | 10 ++- .../src/model/platform_nav/_locators.ts | 51 +++++++----- .../src/model/platform_nav/analytics.ts | 7 +- .../src/model/platform_nav/devtools.ts | 10 +-- .../model/platform_nav/machine_learning.ts | 35 +++++---- .../src/model/platform_nav/management.ts | 77 +++++++++++-------- 6 files changed, 108 insertions(+), 82 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/model/model.ts b/packages/shared-ux/chrome/navigation/src/model/model.ts index 1297b48fc81ee..e738afd1dc820 100644 --- a/packages/shared-ux/chrome/navigation/src/model/model.ts +++ b/packages/shared-ux/chrome/navigation/src/model/model.ts @@ -50,14 +50,18 @@ const createSideNavDataFactory = ( } if (locatorId && !locator) { - // console.warn(`Invalid locator ID provided: ${locatorId}`); - return accum; + // FIXME: in production, we should skip this link in this scenario. We're allowing this broken link in POC for testing. + // return accum; + console.warn( + `Invalid locator provided: ` + + JSON.stringify({ locatorId, ...(locatorParams ? { locatorParams } : {}) }) + ); } const fullId = [parentIds, id].filter(Boolean).join('.'); let onClick: OnClickFn | undefined; - if (locator && locatorParams) { + if (locator) { const outerLocator = locator; onClick = () => { outerLocator.navigateSync(locatorParams ?? {}); diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/_locators.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/_locators.ts index e2ffd6d38793c..4ddba7eb7b70f 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/_locators.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/_locators.ts @@ -6,25 +6,34 @@ * Side Public License, v 1. */ -/** - * @internal - */ -export const locators = { - analytics: (params = {}) => ({ - locator: { id: 'ANALYTICS_APP_LOCATOR', params }, - }), - ml: (params: { page: string; pageState?: string }) => ({ - locator: { id: 'ML_APP_LOCATOR', params }, - }), - devTools: (params = {}) => ({ - locator: { id: 'DEVTOOLS_APP_LOCATOR', params }, - }), - // TODO: import ManagementAppLocatorParams for type safety - management: (params: { sectionId: string; appId?: string }) => ({ - locator: { id: 'MANAGEMENT_APP_LOCATOR', params }, - }), - // FIXME: Do not use - unknown: (params: any) => ({ - locator: { id: 'UNKNOWN_APP_LOCATOR', params }, - }), +// TODO move to package +// +export const locatorIds = { + alertingManagement: 'ALERTING_MANAGEMENT_LOCATOR_ID', + console: 'CONSOLE_APP_LOCATOR', + crossClusterReplication: 'CROSS_CLUSTER_REPLICATION_LOCATOR_ID', + dashboard: 'DASHBOARD_APP_LOCATOR', // FIXME: should go to dashboard listing + discover: 'DISCOVER_APP_LOCATOR', + fleet: 'FLEET_LOCATOR_ID', + grokDebugger: 'GROK_DEBUGGER_LOCATOR_ID', + indexManagement: 'INDEX_MANAGEMENT_LOCATOR_ID', + ilm: 'ILM_LOCATOR_ID', + ml: 'ML_APP_LOCATOR', + mlJobsManagement: 'ML_JOBS_MANAGEMENT_APP_LOCATOR', + ingestPipelines: 'INGEST_PIPELINES_APP_LOCATOR', + integrations: 'INTEGRATIONS_LOCATOR_ID', + logstashPipelines: 'LOGSTASH_PIPELINES_LOCATOR_ID', + osquery: 'OSQUERY_LOCATOR_ID', + painlessLab: 'PAINLESS_LAB_LOCATOR_ID', + remoteClusters: 'REMOTE_CLUSTERS_LOCATOR_ID', + reporting: 'REPORTING_LOCATOR_ID', + rollup: 'ROLLUP_LOCATOR_ID', + searchprofiler: 'SEARCH_PROFILER_LOCATOR_ID', + snapshotRestore: 'SNAPSHOT_RESTORE_LOCATOR_ID', + transform: 'TRANSFORM_LOCATOR_ID', + upgradeAssistant: 'UPGRADE_ASSISTANT_LOCATOR_ID', + visualizeLibrary: 'VISUALIZE_APP_LOCATOR', + management: 'MANAGEMENT_APP_LOCATOR', + watcher: 'WATCHER_APP_LOCATOR', + securityManagement: 'SECURITY_MANAGEMENT_LOCATOR_ID', }; diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts index 0a65127dbd26d..318790bbfaf08 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts @@ -7,6 +7,7 @@ */ import { NavItemProps } from '../../../types'; +import { locatorIds } from './_locators'; export const analyticsItemSet: NavItemProps[] = [ { @@ -16,17 +17,17 @@ export const analyticsItemSet: NavItemProps[] = [ { name: 'Discover', id: 'discover', - locator: { id: 'DISCOVER_APP_LOCATOR', params: {} }, + locator: { id: locatorIds.discover }, }, { name: 'Dashboard', id: 'dashboard', - locator: { id: 'DASHBOARD_APP_LOCATOR', params: {} }, + locator: { id: locatorIds.dashboard }, }, { name: 'Visualize Library', id: 'visualize_library', - locator: { id: 'VISUALIZE_APP_LOCATOR', params: {} }, + locator: { id: locatorIds.visualizeLibrary }, }, ], }, diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts index 0ce207155ed61..65c0fe9e5e82f 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts @@ -7,7 +7,7 @@ */ import { NavItemProps } from '../../../types'; -import { locators } from './_locators'; +import { locatorIds } from './_locators'; export const devtoolsItemSet: NavItemProps[] = [ { @@ -17,22 +17,22 @@ export const devtoolsItemSet: NavItemProps[] = [ { name: 'Console', id: 'console', - locator: { id: 'CONSOLE_APP_LOCATOR' }, + locator: { id: locatorIds.console }, }, { name: 'Search profiler', id: 'search_profiler', - ...locators.devTools({ sectionId: 'searchprofiler' }), + locator: { id: locatorIds.searchprofiler }, }, { name: 'Grok debugger', id: 'grok_debugger', - ...locators.devTools({ view: 'grokdebugger' }), + locator: { id: locatorIds.grokDebugger }, }, { name: 'Painless lab', id: 'painless_lab', - ...locators.devTools({ view: 'painless_lab' }), + locator: { id: locatorIds.painlessLab }, }, ], }, diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts index bc2c5ca3b44e9..0546127a134d7 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts @@ -7,7 +7,10 @@ */ import { NavItemProps } from '../../../types'; -import { locators } from './_locators'; + +const mlLocator = (params: { page: string; pageState?: string }) => ({ + locator: { id: 'ML_APP_LOCATOR', params }, +}); export const mlItemSet: NavItemProps[] = [ { @@ -17,12 +20,12 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Overview', id: 'overview', - ...locators.ml({ page: 'overview' }), + ...mlLocator({ page: 'overview' }), }, { name: 'Notifications', id: 'notifications', - ...locators.ml({ page: 'notifications' }), + ...mlLocator({ page: 'notifications' }), }, ], }, @@ -33,22 +36,22 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Jobs', id: 'jobs', - ...locators.ml({ page: 'jobs' }), + ...mlLocator({ page: 'jobs' }), }, { name: 'Anomaly explorer', id: 'explorer', - ...locators.ml({ page: 'explorer' }), + ...mlLocator({ page: 'explorer' }), }, { name: 'Single metric viewer', id: 'single_metric_viewer', - ...locators.ml({ page: 'timeseriesexplorer' }), + ...mlLocator({ page: 'timeseriesexplorer' }), }, { name: 'Settings', id: 'settings', - ...locators.ml({ page: 'settings' }), + ...mlLocator({ page: 'settings' }), }, ], }, @@ -59,17 +62,17 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Jobs', id: 'jobs', - ...locators.ml({ page: 'data_frame_analytics' }), + ...mlLocator({ page: 'data_frame_analytics' }), }, { name: 'Results explorer', id: 'results_explorer', - ...locators.ml({ page: 'data_frame_analytics/exploration' }), + ...mlLocator({ page: 'data_frame_analytics/exploration' }), }, { name: 'Analytics map', id: 'analytics_map', - ...locators.ml({ page: 'data_frame_analytics/map' }), + ...mlLocator({ page: 'data_frame_analytics/map' }), }, ], }, @@ -80,12 +83,12 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Trained models', id: 'trained_models', - ...locators.ml({ page: 'trained_models' }), + ...mlLocator({ page: 'trained_models' }), }, { name: 'Nodes', id: 'nodes', - ...locators.unknown({ page: 'nodes' }), + ...mlLocator({ page: 'nodes' }), }, ], }, @@ -96,12 +99,12 @@ export const mlItemSet: NavItemProps[] = [ { name: 'File', id: 'file', - ...locators.ml({ page: 'filedatavisualizer' }), + ...mlLocator({ page: 'filedatavisualizer' }), }, { name: 'Data page', id: 'data_view', - ...locators.ml({ page: 'datavisualizer_index_select' }), + ...mlLocator({ page: 'datavisualizer_index_select' }), }, ], }, @@ -112,12 +115,12 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Explain log rate spikes', id: 'explain_log_rate_spikes', - ...locators.ml({ page: 'explain_log_rate_spikes_index_select' }), + ...mlLocator({ page: 'explain_log_rate_spikes_index_select' }), }, { name: 'Log pattern analysis', id: 'log_pattern_analysis', - ...locators.ml({ page: 'log_categorization_index_select' }), + ...mlLocator({ page: 'log_categorization_index_select' }), }, ], }, diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts index 6b3cf270129f1..ee4b18f60bbca 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts @@ -7,7 +7,11 @@ */ import { NavItemProps } from '../../../types'; -import { locators } from './_locators'; +import { locatorIds } from './_locators'; + +const managementLocator = (params: { sectionId: string; appId?: string }) => ({ + locator: { id: locatorIds.management, params }, +}); export const managementItemSet: NavItemProps[] = [ { @@ -17,7 +21,7 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Stack monitoring', id: 'stack_monitoring', - ...locators.unknown({ id: 'stack_monitoring' }), + locator: { id: 'STACK_MONITORING_APP_LOCATOR' }, }, ], }, @@ -28,10 +32,18 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Integrations', id: 'integrations', - ...locators.unknown({ sectionId: 'integrations' }), + locator: { id: locatorIds.integrations }, + }, + { + name: 'Fleet', + id: 'fleet', + locator: { id: locatorIds.fleet }, + }, + { + name: 'Osquery', + id: 'osquery', + locator: { id: locatorIds.osquery }, }, - { name: 'Fleet', id: 'fleet', ...locators.unknown({ sectionId: 'fleet' }) }, - { name: 'Osquery', id: 'osquery', ...locators.unknown({ sectionId: 'osquery' }) }, ], }, { @@ -45,12 +57,12 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Ingest pipelines', id: 'ingest_pipelines', - ...locators.management({ sectionId: 'ingest', appId: 'ingest_pipelines' }), + locator: { id: locatorIds.ingestPipelines }, }, { name: 'Logstash pipelines', id: 'logstash_pipelines', - ...locators.management({ sectionId: 'ingest', appId: 'pipelines' }), + locator: { id: locatorIds.logstashPipelines }, }, ], }, @@ -61,37 +73,37 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Index management', id: 'index_management', - ...locators.management({ sectionId: 'data', appId: 'index_management' }), + locator: { id: locatorIds.indexManagement }, }, { name: 'Index lifecycle policies', id: 'index_lifecycle_policies', - ...locators.management({ sectionId: 'data', appId: 'index_lifecycle_management' }), + locator: { id: locatorIds.ilm }, }, { name: 'Snapshot and restore', id: 'snapshot_and_restore', - ...locators.management({ sectionId: 'data', appId: 'snapshot_restore' }), + locator: { id: locatorIds.snapshotRestore }, }, { name: 'Rollup jobs', id: 'rollup_jobs', - ...locators.management({ sectionId: 'data', appId: 'rollup_jobs' }), + locator: { id: locatorIds.rollup }, }, { name: 'Transforms', id: 'transforms', - ...locators.management({ sectionId: 'data', appId: 'transform' }), + locator: { id: locatorIds.transform }, }, { name: 'Cross-cluster replication', id: 'cross_cluster_replication', - ...locators.management({ sectionId: 'data', appId: 'cross_cluster_replication' }), + locator: { id: locatorIds.crossClusterReplication }, }, { name: 'Remote clusters', id: 'remote_clusters', - ...locators.management({ sectionId: 'data', appId: 'remote_clusters' }), + locator: { id: locatorIds.remoteClusters }, }, ], }, @@ -102,35 +114,32 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Rules', id: 'rules', - ...locators.management({ sectionId: 'insightsAndAlerting', appId: 'triggersActions' }), + locator: { id: locatorIds.alertingManagement, params: { appId: 'triggersActions' } }, }, { name: 'Cases', id: 'cases', - ...locators.management({ sectionId: 'insightsAndAlerting', appId: 'cases' }), + locator: { id: locatorIds.alertingManagement, params: { appId: 'cases' } }, }, { name: 'Connectors', id: 'connectors', - ...locators.management({ - sectionId: 'insightsAndAlerting', - appId: 'triggersActionsConnectors', - }), + locator: { id: locatorIds.alertingManagement, params: { appId: 'triggersActionsConnectors' } }, }, { name: 'Reporting', id: 'reporting', - ...locators.management({ sectionId: 'insightsAndAlerting', appId: 'reporting' }), + locator: { id: locatorIds.reporting }, }, { name: 'Machine learning', id: 'machine_learning', - ...locators.management({ sectionId: 'insightsAndAlerting', appId: 'jobsListLink' }), + locator: { id: locatorIds.mlJobsManagement }, }, { name: 'Watcher', id: 'watcher', - ...locators.management({ sectionId: 'insightsAndAlerting', appId: 'watcher' }), + locator: { id: locatorIds.watcher }, }, ], }, @@ -141,22 +150,22 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Users', id: 'users', - ...locators.management({ sectionId: 'security', appId: 'users' }), + locator: { id: locatorIds.securityManagement, params: { appId: 'users' } }, }, { name: 'Roles', id: 'roles', - ...locators.management({ sectionId: 'security', appId: 'roles' }), + locator: { id: locatorIds.securityManagement, params: { appId: 'roles' } }, }, { name: 'Role mappings', id: 'role_mappings', - ...locators.management({ sectionId: 'security', appId: 'role_mappings' }), + locator: { id: locatorIds.securityManagement, params: { appId: 'role_mappings' } }, }, { name: 'API keys', id: 'api_keys', - ...locators.management({ sectionId: 'security', appId: 'api_keys' }), + locator: { id: locatorIds.securityManagement, params: { appId: 'api_keys' } }, }, ], }, @@ -167,39 +176,39 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Data view', id: 'data_views', - ...locators.management({ sectionId: 'kibana', appId: 'dataViews' }), + ...managementLocator({ sectionId: 'kibana', appId: 'dataViews' }), }, { name: 'Saved objects', id: 'saved_objects', - ...locators.management({ sectionId: 'kibana', appId: 'objects' }), + ...managementLocator({ sectionId: 'kibana', appId: 'objects' }), }, { name: 'Tags', id: 'tags', - ...locators.management({ sectionId: 'kibana', appId: 'tags' }), + ...managementLocator({ sectionId: 'kibana', appId: 'tags' }), }, { name: 'Search sessions', id: 'search_sessions', - ...locators.management({ sectionId: 'kibana', appId: 'search_sessions' }), + ...managementLocator({ sectionId: 'kibana', appId: 'search_sessions' }), }, { name: 'Spaces', id: 'spaces', - ...locators.management({ sectionId: 'kibana', appId: 'spaces' }), + locator: { id: 'SPACES_MANAGEMENT_APP_LOCATOR' }, }, { name: 'Advanced settings', id: 'advanced_settings', - ...locators.management({ sectionId: 'kibana', appId: 'settings' }), + ...managementLocator({ sectionId: 'kibana', appId: 'settings' }), }, ], }, { name: 'Upgrade assistant', id: 'upgrade_assistant', - ...locators.management({ sectionId: 'stack', appId: 'upgrade_assistant' }), + locator: { id: locatorIds.upgradeAssistant }, }, ], }, From 9154f32666c1b24daf740b2a34e8d58ff77990cb Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 12 Apr 2023 01:23:57 +0000 Subject: [PATCH 13/41] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../chrome/navigation/src/model/platform_nav/management.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts index ee4b18f60bbca..731ae2ede2f36 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts @@ -124,7 +124,10 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Connectors', id: 'connectors', - locator: { id: locatorIds.alertingManagement, params: { appId: 'triggersActionsConnectors' } }, + locator: { + id: locatorIds.alertingManagement, + params: { appId: 'triggersActionsConnectors' }, + }, }, { name: 'Reporting', From a9bd56bd58a7b6584556b446e7d3d751fbc7d1be Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 12 Apr 2023 13:53:37 -0700 Subject: [PATCH 14/41] navigateToUrl --- .../chrome/navigation/mocks/src/jest.ts | 10 +- .../chrome/navigation/mocks/src/storybook.ts | 2 + .../navigation/src/model/create_side_nav.ts | 100 +++++++++++++++++ .../chrome/navigation/src/model/index.ts | 13 +++ .../chrome/navigation/src/model/model.ts | 103 ++---------------- .../chrome/navigation/src/services.tsx | 19 +++- .../navigation/src/ui/navigation.test.tsx | 12 +- .../chrome/navigation/src/ui/navigation.tsx | 23 ++-- .../shared-ux/chrome/navigation/tsconfig.json | 2 + .../chrome/navigation/types/index.ts | 29 +++-- .../chrome/navigation/types/internal.ts | 12 +- 11 files changed, 185 insertions(+), 140 deletions(-) create mode 100644 packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index 501d0dca5a119..8f05e2fd9da93 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -9,15 +9,19 @@ import { NavigationServices } from '../../types'; export const getServicesMock = (): NavigationServices => { - const getLocator = jest.fn(); const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; + const navigateToUrl = jest.fn().mockResolvedValue(undefined); + const basePath = { prepend: jest.fn((path: string) => `/base${path}`) }; + const getLocator = jest.fn(); const registerNavItemClick = jest.fn(); return { - getLocator, - registerNavItemClick, activeNavItemId: 'test.hello.lamp', + basePath, + getLocator, navIsOpen: true, + navigateToUrl, recentItems, + registerNavItemClick, }; }; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index 8a27d89fa057e..4fca8ea138d1a 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -52,6 +52,8 @@ export class StorybookMock extends AbstractStorybookMock '' }, + navigateToUrl: () => Promise.resolve(), getLocator, navIsOpen, recentItems, diff --git a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts new file mode 100644 index 0000000000000..1442ad5a75647 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { EuiSideNavItemType } from '@elastic/eui'; +import type { NavigationModelDeps } from '.'; +import type { NavItemProps, PlatformSectionConfig } from '../../types'; +import type { Locator } from '../../types/internal'; + +type MyEuiSideNavItem = EuiSideNavItemType; +type OnClickFn = MyEuiSideNavItem['onClick']; + +/** + * Factory function to return a function that processes modeled nav items into EuiSideNavItemType + * The factory puts memoized function arguments in scope for iterations of the recursive item processing. + */ +export const createSideNavDataFactory = ( + deps: NavigationModelDeps, + activeNavItemId: string | undefined +) => { + const { basePath, navigateToUrl, getLocator, registerNavItemClick } = deps; + const createSideNavData = ( + parentIds: string | number = '', + navItems: NavItemProps[], + platformSectionConfig?: PlatformSectionConfig + ): Array> => + navItems.reduce((accum, item) => { + const { id, name, items: subNav, locator: locatorDefinition, href } = item; + const config = platformSectionConfig?.properties?.[id]; + if (config?.enabled === false) { + // return accumulated set without the item that is not enabled + return accum; + } + + const { id: locatorId, params: locatorParams } = locatorDefinition ?? {}; + let locator: Locator | undefined; + if (locatorId) { + locator = getLocator(locatorId); + } + + let onClick: OnClickFn | undefined; + + if (locatorId && !locator) { + // FIXME: we need to return `accum` to skip this link + // return accum; + + // for DEBUG + onClick = () => { + window.alert( + `Locator not found: ` + + JSON.stringify({ locatorId, ...(locatorParams ? { locatorParams } : {}) }) + ); + }; + } + + const fullId = [parentIds, id].filter(Boolean).join('.'); + + if (locator) { + const storedLocator: Locator = locator; // never undefined + onClick = () => { + storedLocator.navigateSync(locatorParams ?? {}); + registerNavItemClick(fullId); + }; + } else if (href) { + onClick = () => { + navigateToUrl(basePath.prepend(href)); + registerNavItemClick(fullId); + }; + } + + let filteredSubNav: MyEuiSideNavItem[] | undefined; + if (subNav) { + // recursion + const nextConfig = platformSectionConfig?.properties?.[id]; + filteredSubNav = createSideNavData(fullId, subNav, nextConfig); + } + + let isSelected: boolean = false; + if (!subNav && fullId === activeNavItemId) { + // if there are no subnav items and ID is current, mark the item as selected + isSelected = true; + } + + const next: MyEuiSideNavItem = { + id: fullId, + name, + isSelected, + onClick, + items: filteredSubNav, + ['data-test-subj']: `nav-item-${fullId}`, + }; + return [...accum, next]; + }, []); + + return createSideNavData; +}; diff --git a/packages/shared-ux/chrome/navigation/src/model/index.ts b/packages/shared-ux/chrome/navigation/src/model/index.ts index 827bd1bb5bb46..1dc717b09429f 100644 --- a/packages/shared-ux/chrome/navigation/src/model/index.ts +++ b/packages/shared-ux/chrome/navigation/src/model/index.ts @@ -10,6 +10,19 @@ import { analyticsItemSet } from './platform_nav/analytics'; import { devtoolsItemSet } from './platform_nav/devtools'; import { mlItemSet } from './platform_nav/machine_learning'; import { managementItemSet } from './platform_nav/management'; +import { + BasePathService, + GetLocatorFn, + NavigateToUrlFn, + NavItemClickFn, +} from '../../types/internal'; + +export interface NavigationModelDeps { + basePath: BasePathService; + navigateToUrl: NavigateToUrlFn; + getLocator: GetLocatorFn; + registerNavItemClick: NavItemClickFn; +} /** * @public diff --git a/packages/shared-ux/chrome/navigation/src/model/model.ts b/packages/shared-ux/chrome/navigation/src/model/model.ts index e738afd1dc820..df5c2bdd9f7c4 100644 --- a/packages/shared-ux/chrome/navigation/src/model/model.ts +++ b/packages/shared-ux/chrome/navigation/src/model/model.ts @@ -6,9 +6,10 @@ * Side Public License, v 1. */ -import { EuiSideNavItemType } from '@elastic/eui'; +import type { EuiSideNavItemType } from '@elastic/eui'; +import type { NavigationModelDeps } from '.'; import { navItemSet, Platform } from '.'; -import { +import type { NavigationBucketProps, NavigationProps, NavItemProps, @@ -16,90 +17,7 @@ import { PlatformSectionConfig, SolutionProperties, } from '../../types'; -import { GetLocatorFn, ILocatorPublic, NavItemClickFn } from '../../types/internal'; - -type MyEuiSideNavItem = EuiSideNavItemType; -type OnClickFn = MyEuiSideNavItem['onClick']; - -/** - * Factory function to return a function that processes modeled nav items into EuiSideNavItemType - * The factory puts memoized function arguments in scope for iterations of the recursive item processing. - */ -const createSideNavDataFactory = ( - getLocator: GetLocatorFn, - registerNavItemClick: NavItemClickFn, - activeNav?: string | number -) => { - const createSideNavData = ( - parentIds: string | number = '', - navItems: NavItemProps[], - platformSectionConfig?: PlatformSectionConfig - ): Array> => - navItems.reduce((accum, item) => { - const { id, name, items: subNav, locator: locatorDefinition, href } = item; - const config = platformSectionConfig?.properties?.[id]; - if (config?.enabled === false) { - // return accumulated set without the item that is not enabled - return accum; - } - - const { id: locatorId, params: locatorParams } = locatorDefinition ?? {}; - let locator: ILocatorPublic | undefined; - if (locatorId) { - locator = getLocator(locatorId); - } - - if (locatorId && !locator) { - // FIXME: in production, we should skip this link in this scenario. We're allowing this broken link in POC for testing. - // return accum; - console.warn( - `Invalid locator provided: ` + - JSON.stringify({ locatorId, ...(locatorParams ? { locatorParams } : {}) }) - ); - } - - const fullId = [parentIds, id].filter(Boolean).join('.'); - - let onClick: OnClickFn | undefined; - if (locator) { - const outerLocator = locator; - onClick = () => { - outerLocator.navigateSync(locatorParams ?? {}); - registerNavItemClick(fullId); - }; - } else if (href) { - onClick = () => { - registerNavItemClick(fullId); - }; - } - - let filteredSubNav: MyEuiSideNavItem[] | undefined; - if (subNav) { - // recursion - const nextConfig = platformSectionConfig?.properties?.[id]; - filteredSubNav = createSideNavData(fullId, subNav, nextConfig); - } - - let isSelected: boolean = false; - if (!subNav && fullId === activeNav) { - // if there are no subnav items and ID is current, mark the item as selected - isSelected = true; - } - - const next: MyEuiSideNavItem = { - id: fullId, - name, - isSelected, - onClick, - href, - items: filteredSubNav, - ['data-test-subj']: `nav-item-${fullId}`, - }; - return [...accum, next]; - }, []); - - return createSideNavData; -}; +import { createSideNavDataFactory } from './create_side_nav'; /** * @internal @@ -112,18 +30,13 @@ export class NavigationModel { ) => Array>; constructor( - getLocator: GetLocatorFn, - registerNavItemClick: NavItemClickFn, - private activeNavItemId: string | undefined, + deps: NavigationModelDeps, private recentItems: Array> | undefined, private platformConfig: NavigationProps['platformConfig'] | undefined, - private solutions: SolutionProperties[] + private solutions: SolutionProperties[], + private activeNavItemId: string | undefined ) { - this.createSideNavData = createSideNavDataFactory( - getLocator, - registerNavItemClick, - activeNavItemId - ); + this.createSideNavData = createSideNavDataFactory(deps, activeNavItemId); } public getRecent(): NavigationBucketProps { diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index da5007c58e426..7686a91f5cf95 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -26,22 +26,29 @@ export const NavigationKibanaProvider: FC = ({ children, ...dependencies }) => { + const { core } = dependencies; + const { chrome, http } = core; + const { basePath } = http; + const { navigateToUrl } = core.application; + // FIXME - // const recentItems$ = dependencies.core.chrome.recentlyAccessed.get$(); + // const recentItems$ = chrome.recentlyAccessed.get$(); // const recentItems = useObservable(recentItems$, []); const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; - const navIsOpen = useObservable(dependencies.core.chrome.getProjectNavIsOpen$(), true); - const activeNavItemId = useObservable(dependencies.core.chrome.getActiveNavItemId$()); + const navIsOpen = useObservable(chrome.getProjectNavIsOpen$(), true); + const activeNavItemId = useObservable(chrome.getActiveNavItemId$()); const getLocator = (id: string) => dependencies.share.url.locators.get(id); - const registerNavItemClick = dependencies.core.chrome.registerNavItemClick; + const registerNavItemClick = chrome.registerNavItemClick; const value: NavigationServices = { - navIsOpen, - recentItems, activeNavItemId, + basePath, getLocator, + navIsOpen, + navigateToUrl, + recentItems, registerNavItemClick, }; diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx index 8f6fc1ab53669..bc0ac575a1cc5 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx @@ -15,10 +15,9 @@ import { Navigation } from './navigation'; describe('', () => { test('renders with minimal props', () => { const div = document.createElement('div'); - const { getLocator, registerNavItemClick } = getServicesMock(); + const services = getServicesMock(); const homeHref = '#'; - const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; const platformSections = {}; const solutions = [ { @@ -27,16 +26,9 @@ describe('', () => { icon: 'gear', }, ]; - const activeNavItemId = 'hello.test.1'; render( - + , div diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index 23ddcbf39c00b..46968e24a528f 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -28,16 +28,16 @@ export const Navigation = (props: NavigationProps) => { // const { fontSize: navSectionFontSize } = useEuiFontSize('m'); // const { fontSize: navItemFontSize } = useEuiFontSize('s'); - const { recentItems, ...services } = useNavigation(); + const { recentItems: recentItemsFromService, ...services } = useNavigation(); const { euiTheme } = useEuiTheme(); - let euiSideNavRecentItems: Array> | undefined; - if (recentItems) { - euiSideNavRecentItems = [ + let recentItems: Array> | undefined; + if (recentItemsFromService) { + recentItems = [ { name: '', id: 'recent_items_root', - items: recentItems.map((item) => ({ + items: recentItemsFromService.map((item) => ({ id: item.id, name: item.label, onClick: () => { @@ -49,13 +49,14 @@ export const Navigation = (props: NavigationProps) => { ]; } + const activeNav = services.activeNavItemId ?? props.activeNavItemId; + const nav = new NavigationModel( - services.getLocator, - services.registerNavItemClick, - services.activeNavItemId ?? props.activeNavItemId, - euiSideNavRecentItems, + services, + recentItems, props.platformConfig, - props.solutions + props.solutions, + activeNav ); const recent = nav.getRecent(); @@ -79,7 +80,7 @@ export const Navigation = (props: NavigationProps) => { ) : null} - {euiSideNavRecentItems ? : null} + {recentItems ? : null} {solutions.map((solutionBucket, idx) => { return ; diff --git a/packages/shared-ux/chrome/navigation/tsconfig.json b/packages/shared-ux/chrome/navigation/tsconfig.json index 3de369d8d1b15..7933de14ed95a 100644 --- a/packages/shared-ux/chrome/navigation/tsconfig.json +++ b/packages/shared-ux/chrome/navigation/tsconfig.json @@ -15,6 +15,8 @@ "**/*.tsx" ], "kbn_references": [ + "@kbn/core-application-browser", + "@kbn/core-http-browser", "@kbn/shared-ux-storybook-mock", "@kbn/utility-types" ], diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index b14b5d7fc70b9..82aad0dee9380 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -8,17 +8,26 @@ import type { EuiSideNavItemType, IconType } from '@elastic/eui'; import { Observable } from 'rxjs'; -import { GetLocatorFn, ILocatorDefinition, NavItemClickFn, RecentItem } from './internal'; +import { + BasePathService, + GetLocatorFn, + ILocatorDefinition, + NavigateToUrlFn, + NavItemClickFn, + RecentItem, +} from './internal'; /** * A list of services that are consumed by this component. * @public */ export interface NavigationServices { - navIsOpen: boolean; - recentItems: RecentItem[]; activeNavItemId: string | undefined; + basePath: BasePathService; getLocator: GetLocatorFn; + navIsOpen: boolean; + navigateToUrl: NavigateToUrlFn; + recentItems: RecentItem[]; registerNavItemClick: NavItemClickFn; } @@ -28,20 +37,16 @@ export interface NavigationServices { * @public */ export interface NavigationKibanaDependencies { - share: { - url: { - locators: { get: GetLocatorFn }; - }; - }; + share: { url: { locators: { get: GetLocatorFn } } }; core: { + application: { navigateToUrl: NavigateToUrlFn }; chrome: { - recentlyAccessed: { - get$: () => Observable; - }; - getProjectNavIsOpen$: () => Observable; getActiveNavItemId$: () => Observable; + getProjectNavIsOpen$: () => Observable; + recentlyAccessed: { get$: () => Observable }; registerNavItemClick: (id: string) => void; }; + http: { basePath: BasePathService }; }; } diff --git a/packages/shared-ux/chrome/navigation/types/internal.ts b/packages/shared-ux/chrome/navigation/types/internal.ts index 826d4f5ae2f90..5d2cfaee260b2 100644 --- a/packages/shared-ux/chrome/navigation/types/internal.ts +++ b/packages/shared-ux/chrome/navigation/types/internal.ts @@ -6,19 +6,21 @@ * Side Public License, v 1. */ -import { SerializableRecord } from '@kbn/utility-types'; +import type { ApplicationStart } from '@kbn/core-application-browser'; +import type { IBasePath } from '@kbn/core-http-browser'; +import type { SerializableRecord } from '@kbn/utility-types'; /** * @internal */ -export interface ILocatorPublic { +export interface Locator { navigateSync:

(params: P) => void; } /** * @internal */ -export type GetLocatorFn = (locatorId: string) => ILocatorPublic | undefined; +export type GetLocatorFn = (locatorId: string) => Locator | undefined; /** * @internal @@ -53,3 +55,7 @@ export interface ILocatorDefinition

{ */ params?: P; } + +export type NavigateToUrlFn = ApplicationStart['navigateToUrl']; + +export type BasePathService = Pick; From 4539f645280d7787602e3af4382c542e52cd158a Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 12 Apr 2023 16:39:41 -0700 Subject: [PATCH 15/41] loadingCount --- .../chrome/navigation/mocks/src/jest.ts | 2 + .../chrome/navigation/mocks/src/storybook.ts | 10 ++-- .../navigation/src/model/create_side_nav.ts | 9 +++- .../chrome/navigation/src/services.tsx | 2 + .../navigation/src/ui/navigation.stories.tsx | 36 ++++++++++----- .../chrome/navigation/src/ui/navigation.tsx | 46 ++++++++++++++----- .../chrome/navigation/types/index.ts | 6 ++- .../chrome/navigation/types/internal.ts | 5 +- 8 files changed, 85 insertions(+), 31 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index 8f05e2fd9da93..08c26c5389249 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -14,11 +14,13 @@ export const getServicesMock = (): NavigationServices => { const basePath = { prepend: jest.fn((path: string) => `/base${path}`) }; const getLocator = jest.fn(); const registerNavItemClick = jest.fn(); + const loadingCount = 0; return { activeNavItemId: 'test.hello.lamp', basePath, getLocator, + loadingCount, navIsOpen: true, navigateToUrl, recentItems, diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index 4fca8ea138d1a..5ec618e5a1305 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -16,9 +16,8 @@ import { GetLocatorFn } from '../../types/internal'; type Arguments = NavigationProps & NavigationServices; export type Params = Pick< Arguments, - 'navIsOpen' | 'recentItems' | 'activeNavItemId' | 'platformConfig' | 'solutions' -> & - Arguments['platformConfig']; + 'activeNavItemId' | 'loadingCount' | 'navIsOpen' | 'platformConfig' | 'recentItems' | 'solutions' +>; export class StorybookMock extends AbstractStorybookMock { propArguments = {}; @@ -28,6 +27,10 @@ export class StorybookMock extends AbstractStorybookMock ({ + getRedirectUrl: () => `/app/for/${locatorId}`, navigateSync: (locatorParams?: SerializableRecord) => { navAction(`Locator: ${locatorId} / Params: ${JSON.stringify(locatorParams)}`); }, diff --git a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts index 1442ad5a75647..b97ff7d26c88f 100644 --- a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts +++ b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts @@ -59,14 +59,18 @@ export const createSideNavDataFactory = ( const fullId = [parentIds, id].filter(Boolean).join('.'); + let newHref = href; // allow consumer to pass href, but default to an href formed using the locator if (locator) { const storedLocator: Locator = locator; // never undefined - onClick = () => { + newHref = locator.getRedirectUrl(locatorParams ?? {}); // if consumer passes locator, an href they may have passed gets overwritten + onClick = (event: React.MouseEvent) => { + event.preventDefault(); storedLocator.navigateSync(locatorParams ?? {}); registerNavItemClick(fullId); }; } else if (href) { - onClick = () => { + onClick = (event: React.MouseEvent) => { + event.preventDefault(); navigateToUrl(basePath.prepend(href)); registerNavItemClick(fullId); }; @@ -90,6 +94,7 @@ export const createSideNavDataFactory = ( name, isSelected, onClick, + href: newHref, items: filteredSubNav, ['data-test-subj']: `nav-item-${fullId}`, }; diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index 7686a91f5cf95..5c2c4964acbdd 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -38,6 +38,7 @@ export const NavigationKibanaProvider: FC = ({ const navIsOpen = useObservable(chrome.getProjectNavIsOpen$(), true); const activeNavItemId = useObservable(chrome.getActiveNavItemId$()); + const loadingCount = useObservable(http.getLoadingCount$(), 0); const getLocator = (id: string) => dependencies.share.url.locators.get(id); const registerNavItemClick = chrome.registerNavItemClick; @@ -46,6 +47,7 @@ export const NavigationKibanaProvider: FC = ({ activeNavItemId, basePath, getLocator, + loadingCount, navIsOpen, navigateToUrl, recentItems, diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx index 73f34eef33e63..60e87cca05ada 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx @@ -93,17 +93,8 @@ SingleExpanded.args = { }; SingleExpanded.argTypes = storybookMock.getArgumentTypes(); -export const WithRecentItems: ComponentStory = Template.bind({}); -WithRecentItems.args = { - activeNavItemId: 'example_project.root.get_started', - recentItems: [{ id: 'recent_1', label: 'This is a test recent link', link: 'testo' }], - solutions: [solutionProperties], -}; -WithRecentItems.argTypes = storybookMock.getArgumentTypes(); - -export const ReducedSections: ComponentStory = Template.bind({}); -ReducedSections.args = { - activeNavItemId: 'example_project.root.get_started', +export const ReducedPlatformLinks: ComponentStory = Template.bind({}); +ReducedPlatformLinks.args = { platformConfig: { [Platform.Analytics]: { enabled: false }, [Platform.MachineLearning]: { enabled: false }, @@ -127,4 +118,25 @@ ReducedSections.args = { }, solutions: [solutionProperties], }; -ReducedSections.argTypes = storybookMock.getArgumentTypes(); +ReducedPlatformLinks.argTypes = storybookMock.getArgumentTypes(); + +export const WithRecentItems: ComponentStory = Template.bind({}); +WithRecentItems.args = { + recentItems: [{ id: 'recent_1', label: 'This is a test recent link', link: 'testo' }], + solutions: [solutionProperties], +}; +WithRecentItems.argTypes = storybookMock.getArgumentTypes(); + +export const WithRequestsLoading: ComponentStory = Template.bind({}); +WithRequestsLoading.args = { + loadingCount: 1, + solutions: [solutionProperties], +}; +WithRequestsLoading.argTypes = storybookMock.getArgumentTypes(); + +export const Collapsed: ComponentStory = Template.bind({}); +Collapsed.args = { + navIsOpen: false, + solutions: [solutionProperties], +}; +Collapsed.argTypes = storybookMock.getArgumentTypes(); diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index 46968e24a528f..89acaa7661ec6 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -11,6 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiHeaderLogo, + EuiLoadingSpinner, EuiSideNavItemType, EuiSpacer, useEuiTheme, @@ -28,7 +29,12 @@ export const Navigation = (props: NavigationProps) => { // const { fontSize: navSectionFontSize } = useEuiFontSize('m'); // const { fontSize: navItemFontSize } = useEuiFontSize('s'); - const { recentItems: recentItemsFromService, ...services } = useNavigation(); + const { + recentItems: recentItemsFromService, + loadingCount, + activeNavItemId, + ...services + } = useNavigation(); const { euiTheme } = useEuiTheme(); let recentItems: Array> | undefined; @@ -49,7 +55,7 @@ export const Navigation = (props: NavigationProps) => { ]; } - const activeNav = services.activeNavItemId ?? props.activeNavItemId; + const activeNav = activeNavItemId ?? props.activeNavItemId; const nav = new NavigationModel( services, @@ -63,21 +69,39 @@ export const Navigation = (props: NavigationProps) => { const solutions = nav.getSolutions(); const { analytics, ml, devTools, management } = nav.getPlatform(); + const NavHeader = () => { + const homeUrl = services.basePath.prepend(props.homeHref); + const navigateHome = (event: React.MouseEvent) => { + event.preventDefault(); + services.navigateToUrl(homeUrl); + }; + const logo = + loadingCount === 0 ? ( + + ) : ( + + ); + + return ( + <> + + {logo} + + {services.navIsOpen ? ( + + ) : null} + + ); + }; + return ( - - {services.navIsOpen ? ( - - ) : null} + {recentItems ? : null} diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index 82aad0dee9380..288a2fbad261c 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -25,6 +25,7 @@ export interface NavigationServices { activeNavItemId: string | undefined; basePath: BasePathService; getLocator: GetLocatorFn; + loadingCount: number; navIsOpen: boolean; navigateToUrl: NavigateToUrlFn; recentItems: RecentItem[]; @@ -46,7 +47,10 @@ export interface NavigationKibanaDependencies { recentlyAccessed: { get$: () => Observable }; registerNavItemClick: (id: string) => void; }; - http: { basePath: BasePathService }; + http: { + basePath: BasePathService; + getLoadingCount$(): Observable; + }; }; } diff --git a/packages/shared-ux/chrome/navigation/types/internal.ts b/packages/shared-ux/chrome/navigation/types/internal.ts index 5d2cfaee260b2..f9c5076ecd860 100644 --- a/packages/shared-ux/chrome/navigation/types/internal.ts +++ b/packages/shared-ux/chrome/navigation/types/internal.ts @@ -13,8 +13,9 @@ import type { SerializableRecord } from '@kbn/utility-types'; /** * @internal */ -export interface Locator { - navigateSync:

(params: P) => void; +export interface Locator

{ + getRedirectUrl(params: P): string; + navigateSync: (params: P) => void; } /** From 5f922b632d4ce5899d72500fbac6fb6e0f850ae8 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 12 Apr 2023 18:44:38 -0700 Subject: [PATCH 16/41] simplify nav model methods --- .../chrome/navigation/src/model/model.ts | 7 +------ .../chrome/navigation/src/ui/navigation.tsx | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/model/model.ts b/packages/shared-ux/chrome/navigation/src/model/model.ts index df5c2bdd9f7c4..b7c81fc013781 100644 --- a/packages/shared-ux/chrome/navigation/src/model/model.ts +++ b/packages/shared-ux/chrome/navigation/src/model/model.ts @@ -34,7 +34,7 @@ export class NavigationModel { private recentItems: Array> | undefined, private platformConfig: NavigationProps['platformConfig'] | undefined, private solutions: SolutionProperties[], - private activeNavItemId: string | undefined + activeNavItemId: string | undefined ) { this.createSideNavData = createSideNavDataFactory(deps, activeNavItemId); } @@ -67,7 +67,6 @@ export class NavigationModel { navItemSet[Platform.Analytics], this.platformConfig?.[Platform.Analytics] ), - activeNavItemId: this.activeNavItemId, }, [Platform.MachineLearning]: { id: Platform.MachineLearning, @@ -78,7 +77,6 @@ export class NavigationModel { navItemSet[Platform.MachineLearning], this.platformConfig?.[Platform.MachineLearning] ), - activeNavItemId: this.activeNavItemId, }, [Platform.DevTools]: { id: Platform.DevTools, @@ -89,7 +87,6 @@ export class NavigationModel { navItemSet[Platform.DevTools], this.platformConfig?.[Platform.DevTools] ), - activeNavItemId: this.activeNavItemId, }, [Platform.Management]: { id: Platform.Management, @@ -100,7 +97,6 @@ export class NavigationModel { navItemSet[Platform.Management], this.platformConfig?.[Platform.Management] ), - activeNavItemId: this.activeNavItemId, }, }; } @@ -112,7 +108,6 @@ export class NavigationModel { name: s.name, icon: s.icon, items: this.convertToSideNavItems(s.id, s.items), - activeNavItemId: this.activeNavItemId, })); } diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index 89acaa7661ec6..591f55d396d90 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -17,7 +17,7 @@ import { useEuiTheme, } from '@elastic/eui'; import React from 'react'; -import { NavigationProps } from '../../types'; +import { NavigationBucketProps, NavigationProps } from '../../types'; import { NavigationModel } from '../model'; import { useNavigation } from '../services'; import { ElasticMark } from './elastic_mark'; @@ -94,6 +94,11 @@ export const Navigation = (props: NavigationProps) => { ); }; + // higher-order-component to keep the common props DRY + const NavigationBucketHoc = (outerProps: Omit) => ( + + ); + return ( @@ -104,14 +109,14 @@ export const Navigation = (props: NavigationProps) => { - {recentItems ? : null} + {recentItems ? : null} {solutions.map((solutionBucket, idx) => { - return ; + return ; })} - {nav.isEnabled('analytics') ? : null} - {nav.isEnabled('ml') ? : null} + {nav.isEnabled('analytics') ? : null} + {nav.isEnabled('ml') ? : null} @@ -119,8 +124,8 @@ export const Navigation = (props: NavigationProps) => { - {nav.isEnabled('devTools') ? : null} - {nav.isEnabled('management') ? : null} + {nav.isEnabled('devTools') ? : null} + {nav.isEnabled('management') ? : null} ); From 5c7fb7133ac8c882096b772c90c5e5f276486d79 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 12 Apr 2023 19:04:08 -0700 Subject: [PATCH 17/41] fix anchor nesting --- .../shared-ux/chrome/navigation/src/ui/navigation.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index 591f55d396d90..b64b9883a9bc2 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -77,16 +77,16 @@ export const Navigation = (props: NavigationProps) => { }; const logo = loadingCount === 0 ? ( - + ) : ( - + + + ); return ( <> - - {logo} - + {logo} {services.navIsOpen ? ( ) : null} From 8021047524ac0f97c529425aa67322251e1d6787 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 20 Apr 2023 15:28:00 -0700 Subject: [PATCH 18/41] add link to cloud --- .../chrome/navigation/mocks/src/storybook.ts | 1 + .../chrome/navigation/src/ui/navigation.tsx | 22 +++++++++++++++++++ .../chrome/navigation/types/index.ts | 4 ++++ 3 files changed, 27 insertions(+) diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index 5ec618e5a1305..77b17d466c029 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -69,6 +69,7 @@ export class StorybookMock extends AbstractStorybookMock { ); }; + const LinkToCloud = () => { + // FIXME: link directly to the current project or deployment + switch (props.linkToCloud) { + case 'projects': + return ( + + + + ); + case 'deployments': + return ( + + + + ); + default: + return null; + } + }; + // higher-order-component to keep the common props DRY const NavigationBucketHoc = (outerProps: Omit) => ( @@ -109,6 +129,8 @@ export const Navigation = (props: NavigationProps) => { + + {recentItems ? : null} {solutions.map((solutionBucket, idx) => { diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index 288a2fbad261c..821aff25cd1a0 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -137,6 +137,10 @@ export interface NavigationProps { * Target for the logo icon */ homeHref: string; + /** + * Control of the link that takes the user to their projects or deployments + */ + linkToCloud?: 'projects' | 'deployments'; } export type NavigationBucketProps = (SolutionProperties & From 33512d3e20a641e2e976ba0dafc2b3812bd04227 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 20 Apr 2023 18:17:20 -0700 Subject: [PATCH 19/41] create single locators package --- package.json | 1 + .../navigation/src/model/create_side_nav.ts | 12 +-- .../src/model/platform_nav/_locators.ts | 39 ---------- .../src/model/platform_nav/analytics.ts | 8 +- .../src/model/platform_nav/devtools.ts | 10 +-- .../model/platform_nav/machine_learning.ts | 35 +++++---- .../src/model/platform_nav/management.ts | 76 +++++++++++-------- .../navigation/src/ui/navigation.stories.tsx | 41 +++++++++- .../chrome/navigation/src/ui/navigation.tsx | 9 ++- .../shared-ux/chrome/navigation/tsconfig.json | 1 + packages/shared-ux/locators/README.md | 3 + packages/shared-ux/locators/index.ts | 65 ++++++++++++++++ packages/shared-ux/locators/jest.config.js | 13 ++++ packages/shared-ux/locators/kibana.jsonc | 5 ++ packages/shared-ux/locators/package.json | 6 ++ packages/shared-ux/locators/tsconfig.json | 17 +++++ tsconfig.base.json | 2 + yarn.lock | 4 + 18 files changed, 233 insertions(+), 114 deletions(-) delete mode 100644 packages/shared-ux/chrome/navigation/src/model/platform_nav/_locators.ts create mode 100644 packages/shared-ux/locators/README.md create mode 100644 packages/shared-ux/locators/index.ts create mode 100644 packages/shared-ux/locators/jest.config.js create mode 100644 packages/shared-ux/locators/kibana.jsonc create mode 100644 packages/shared-ux/locators/package.json create mode 100644 packages/shared-ux/locators/tsconfig.json diff --git a/package.json b/package.json index 5c473a14e8adc..a60d7e2c3fa51 100644 --- a/package.json +++ b/package.json @@ -594,6 +594,7 @@ "@kbn/shared-ux-link-redirect-app": "link:packages/shared-ux/link/redirect_app/impl", "@kbn/shared-ux-link-redirect-app-mocks": "link:packages/shared-ux/link/redirect_app/mocks", "@kbn/shared-ux-link-redirect-app-types": "link:packages/shared-ux/link/redirect_app/types", + "@kbn/shared-ux-locators": "link:packages/shared-ux/locators", "@kbn/shared-ux-markdown": "link:packages/shared-ux/markdown/impl", "@kbn/shared-ux-markdown-mocks": "link:packages/shared-ux/markdown/mocks", "@kbn/shared-ux-markdown-types": "link:packages/shared-ux/markdown/types", diff --git a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts index b97ff7d26c88f..7809ee965b9d2 100644 --- a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts +++ b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts @@ -45,16 +45,8 @@ export const createSideNavDataFactory = ( let onClick: OnClickFn | undefined; if (locatorId && !locator) { - // FIXME: we need to return `accum` to skip this link - // return accum; - - // for DEBUG - onClick = () => { - window.alert( - `Locator not found: ` + - JSON.stringify({ locatorId, ...(locatorParams ? { locatorParams } : {}) }) - ); - }; + // return `accum` to skip this link + return accum; } const fullId = [parentIds, id].filter(Boolean).join('.'); diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/_locators.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/_locators.ts deleted file mode 100644 index 4ddba7eb7b70f..0000000000000 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/_locators.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -// TODO move to package -// -export const locatorIds = { - alertingManagement: 'ALERTING_MANAGEMENT_LOCATOR_ID', - console: 'CONSOLE_APP_LOCATOR', - crossClusterReplication: 'CROSS_CLUSTER_REPLICATION_LOCATOR_ID', - dashboard: 'DASHBOARD_APP_LOCATOR', // FIXME: should go to dashboard listing - discover: 'DISCOVER_APP_LOCATOR', - fleet: 'FLEET_LOCATOR_ID', - grokDebugger: 'GROK_DEBUGGER_LOCATOR_ID', - indexManagement: 'INDEX_MANAGEMENT_LOCATOR_ID', - ilm: 'ILM_LOCATOR_ID', - ml: 'ML_APP_LOCATOR', - mlJobsManagement: 'ML_JOBS_MANAGEMENT_APP_LOCATOR', - ingestPipelines: 'INGEST_PIPELINES_APP_LOCATOR', - integrations: 'INTEGRATIONS_LOCATOR_ID', - logstashPipelines: 'LOGSTASH_PIPELINES_LOCATOR_ID', - osquery: 'OSQUERY_LOCATOR_ID', - painlessLab: 'PAINLESS_LAB_LOCATOR_ID', - remoteClusters: 'REMOTE_CLUSTERS_LOCATOR_ID', - reporting: 'REPORTING_LOCATOR_ID', - rollup: 'ROLLUP_LOCATOR_ID', - searchprofiler: 'SEARCH_PROFILER_LOCATOR_ID', - snapshotRestore: 'SNAPSHOT_RESTORE_LOCATOR_ID', - transform: 'TRANSFORM_LOCATOR_ID', - upgradeAssistant: 'UPGRADE_ASSISTANT_LOCATOR_ID', - visualizeLibrary: 'VISUALIZE_APP_LOCATOR', - management: 'MANAGEMENT_APP_LOCATOR', - watcher: 'WATCHER_APP_LOCATOR', - securityManagement: 'SECURITY_MANAGEMENT_LOCATOR_ID', -}; diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts index 318790bbfaf08..91ca577da2606 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ +import { LocatorId } from '@kbn/shared-ux-locators'; import { NavItemProps } from '../../../types'; -import { locatorIds } from './_locators'; export const analyticsItemSet: NavItemProps[] = [ { @@ -17,17 +17,17 @@ export const analyticsItemSet: NavItemProps[] = [ { name: 'Discover', id: 'discover', - locator: { id: locatorIds.discover }, + locator: { id: LocatorId.Discover }, }, { name: 'Dashboard', id: 'dashboard', - locator: { id: locatorIds.dashboard }, + locator: { id: LocatorId.Dashboard }, }, { name: 'Visualize Library', id: 'visualize_library', - locator: { id: locatorIds.visualizeLibrary }, + locator: { id: LocatorId.VisualizeLibrary }, }, ], }, diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts index 65c0fe9e5e82f..71c5c016d39ab 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ +import { LocatorId } from '@kbn/shared-ux-locators'; import { NavItemProps } from '../../../types'; -import { locatorIds } from './_locators'; export const devtoolsItemSet: NavItemProps[] = [ { @@ -17,22 +17,22 @@ export const devtoolsItemSet: NavItemProps[] = [ { name: 'Console', id: 'console', - locator: { id: locatorIds.console }, + locator: { id: LocatorId.ConsoleApp }, }, { name: 'Search profiler', id: 'search_profiler', - locator: { id: locatorIds.searchprofiler }, + locator: { id: LocatorId.SearchProfiler }, }, { name: 'Grok debugger', id: 'grok_debugger', - locator: { id: locatorIds.grokDebugger }, + locator: { id: LocatorId.GrokDebugger }, }, { name: 'Painless lab', id: 'painless_lab', - locator: { id: locatorIds.painlessLab }, + locator: { id: LocatorId.PainlessLab }, }, ], }, diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts index 0546127a134d7..669d8cfb5b8c3 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts @@ -6,11 +6,10 @@ * Side Public License, v 1. */ +import { LocatorId } from '@kbn/shared-ux-locators'; import { NavItemProps } from '../../../types'; -const mlLocator = (params: { page: string; pageState?: string }) => ({ - locator: { id: 'ML_APP_LOCATOR', params }, -}); +const { ML } = LocatorId; export const mlItemSet: NavItemProps[] = [ { @@ -20,12 +19,12 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Overview', id: 'overview', - ...mlLocator({ page: 'overview' }), + locator: { id: ML, params: { page: 'overview' } }, }, { name: 'Notifications', id: 'notifications', - ...mlLocator({ page: 'notifications' }), + locator: { id: ML, params: { page: 'notifications' } }, }, ], }, @@ -36,22 +35,22 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Jobs', id: 'jobs', - ...mlLocator({ page: 'jobs' }), + locator: { id: ML, params: { page: 'jobs' } }, }, { name: 'Anomaly explorer', id: 'explorer', - ...mlLocator({ page: 'explorer' }), + locator: { id: ML, params: { page: 'explorer' } }, }, { name: 'Single metric viewer', id: 'single_metric_viewer', - ...mlLocator({ page: 'timeseriesexplorer' }), + locator: { id: ML, params: { page: 'timeseriesexplorer' } }, }, { name: 'Settings', id: 'settings', - ...mlLocator({ page: 'settings' }), + locator: { id: ML, params: { page: 'settings' } }, }, ], }, @@ -62,17 +61,17 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Jobs', id: 'jobs', - ...mlLocator({ page: 'data_frame_analytics' }), + locator: { id: ML, params: { page: 'data_frame_analytics' } }, }, { name: 'Results explorer', id: 'results_explorer', - ...mlLocator({ page: 'data_frame_analytics/exploration' }), + locator: { id: ML, params: { page: 'data_frame_analytics/exploration' } }, }, { name: 'Analytics map', id: 'analytics_map', - ...mlLocator({ page: 'data_frame_analytics/map' }), + locator: { id: ML, params: { page: 'data_frame_analytics/map' } }, }, ], }, @@ -83,12 +82,12 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Trained models', id: 'trained_models', - ...mlLocator({ page: 'trained_models' }), + locator: { id: ML, params: { page: 'trained_models' } }, }, { name: 'Nodes', id: 'nodes', - ...mlLocator({ page: 'nodes' }), + locator: { id: ML, params: { page: 'nodes' } }, }, ], }, @@ -99,12 +98,12 @@ export const mlItemSet: NavItemProps[] = [ { name: 'File', id: 'file', - ...mlLocator({ page: 'filedatavisualizer' }), + locator: { id: ML, params: { page: 'filedatavisualizer' } }, }, { name: 'Data page', id: 'data_view', - ...mlLocator({ page: 'datavisualizer_index_select' }), + locator: { id: ML, params: { page: 'datavisualizer_index_select' } }, }, ], }, @@ -115,12 +114,12 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Explain log rate spikes', id: 'explain_log_rate_spikes', - ...mlLocator({ page: 'explain_log_rate_spikes_index_select' }), + locator: { id: ML, params: { page: 'explain_log_rate_spikes_index_select' } }, }, { name: 'Log pattern analysis', id: 'log_pattern_analysis', - ...mlLocator({ page: 'log_categorization_index_select' }), + locator: { id: ML, params: { page: 'log_categorization_index_select' } }, }, ], }, diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts index 731ae2ede2f36..81190e83fcb71 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts @@ -6,12 +6,10 @@ * Side Public License, v 1. */ +import { LocatorId } from '@kbn/shared-ux-locators'; import { NavItemProps } from '../../../types'; -import { locatorIds } from './_locators'; -const managementLocator = (params: { sectionId: string; appId?: string }) => ({ - locator: { id: locatorIds.management, params }, -}); +const { Management } = LocatorId; export const managementItemSet: NavItemProps[] = [ { @@ -32,17 +30,17 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Integrations', id: 'integrations', - locator: { id: locatorIds.integrations }, + locator: { id: LocatorId.Integrations }, }, { name: 'Fleet', id: 'fleet', - locator: { id: locatorIds.fleet }, + locator: { id: LocatorId.Fleet }, }, { name: 'Osquery', id: 'osquery', - locator: { id: locatorIds.osquery }, + locator: { id: LocatorId.Osquery }, }, ], }, @@ -57,12 +55,12 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Ingest pipelines', id: 'ingest_pipelines', - locator: { id: locatorIds.ingestPipelines }, + locator: { id: LocatorId.IngestPipelines }, }, { name: 'Logstash pipelines', id: 'logstash_pipelines', - locator: { id: locatorIds.logstashPipelines }, + locator: { id: LocatorId.LogstashPipelines }, }, ], }, @@ -73,37 +71,37 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Index management', id: 'index_management', - locator: { id: locatorIds.indexManagement }, + locator: { id: LocatorId.IndexManagement }, }, { name: 'Index lifecycle policies', id: 'index_lifecycle_policies', - locator: { id: locatorIds.ilm }, + locator: { id: LocatorId.ILM }, }, { name: 'Snapshot and restore', id: 'snapshot_and_restore', - locator: { id: locatorIds.snapshotRestore }, + locator: { id: LocatorId.SnapshotRestore }, }, { name: 'Rollup jobs', id: 'rollup_jobs', - locator: { id: locatorIds.rollup }, + locator: { id: LocatorId.Rollup }, }, { name: 'Transforms', id: 'transforms', - locator: { id: locatorIds.transform }, + locator: { id: LocatorId.Transform }, }, { name: 'Cross-cluster replication', id: 'cross_cluster_replication', - locator: { id: locatorIds.crossClusterReplication }, + locator: { id: LocatorId.CrossClusterReplication }, }, { name: 'Remote clusters', id: 'remote_clusters', - locator: { id: locatorIds.remoteClusters }, + locator: { id: LocatorId.RemoteClusters }, }, ], }, @@ -114,35 +112,35 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Rules', id: 'rules', - locator: { id: locatorIds.alertingManagement, params: { appId: 'triggersActions' } }, + locator: { id: LocatorId.RulesManagement, params: { appId: 'triggersActions' } }, }, { name: 'Cases', id: 'cases', - locator: { id: locatorIds.alertingManagement, params: { appId: 'cases' } }, + locator: { id: LocatorId.CasesManagement, params: { appId: 'cases' } }, }, { name: 'Connectors', id: 'connectors', locator: { - id: locatorIds.alertingManagement, + id: LocatorId.ConnectorsManagement, params: { appId: 'triggersActionsConnectors' }, }, }, { name: 'Reporting', id: 'reporting', - locator: { id: locatorIds.reporting }, + locator: { id: LocatorId.Reporting }, }, { name: 'Machine learning', id: 'machine_learning', - locator: { id: locatorIds.mlJobsManagement }, + locator: { id: LocatorId.MachineLearningManagement }, }, { name: 'Watcher', id: 'watcher', - locator: { id: locatorIds.watcher }, + locator: { id: LocatorId.Watcher }, }, ], }, @@ -153,22 +151,22 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Users', id: 'users', - locator: { id: locatorIds.securityManagement, params: { appId: 'users' } }, + locator: { id: LocatorId.Security, params: { appId: 'users' } }, }, { name: 'Roles', id: 'roles', - locator: { id: locatorIds.securityManagement, params: { appId: 'roles' } }, + locator: { id: LocatorId.Security, params: { appId: 'roles' } }, }, { name: 'Role mappings', id: 'role_mappings', - locator: { id: locatorIds.securityManagement, params: { appId: 'role_mappings' } }, + locator: { id: LocatorId.Security, params: { appId: 'role_mappings' } }, }, { name: 'API keys', id: 'api_keys', - locator: { id: locatorIds.securityManagement, params: { appId: 'api_keys' } }, + locator: { id: LocatorId.Security, params: { appId: 'api_keys' } }, }, ], }, @@ -179,39 +177,51 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Data view', id: 'data_views', - ...managementLocator({ sectionId: 'kibana', appId: 'dataViews' }), + locator: { + id: Management, + params: { sectionId: 'kibana', appId: 'dataViews' }, + }, }, { name: 'Saved objects', id: 'saved_objects', - ...managementLocator({ sectionId: 'kibana', appId: 'objects' }), + locator: { + id: Management, + params: { sectionId: 'kibana', appId: 'objects' }, + }, }, { name: 'Tags', id: 'tags', - ...managementLocator({ sectionId: 'kibana', appId: 'tags' }), + locator: { id: Management, params: { sectionId: 'kibana', appId: 'tags' } }, }, { name: 'Search sessions', id: 'search_sessions', - ...managementLocator({ sectionId: 'kibana', appId: 'search_sessions' }), + locator: { + id: Management, + params: { sectionId: 'kibana', appId: 'search_sessions' }, + }, }, { name: 'Spaces', id: 'spaces', - locator: { id: 'SPACES_MANAGEMENT_APP_LOCATOR' }, + locator: { id: LocatorId.Spaces }, }, { name: 'Advanced settings', id: 'advanced_settings', - ...managementLocator({ sectionId: 'kibana', appId: 'settings' }), + locator: { + id: Management, + params: { sectionId: 'kibana', appId: 'settings' }, + }, }, ], }, { name: 'Upgrade assistant', id: 'upgrade_assistant', - locator: { id: locatorIds.upgradeAssistant }, + locator: { id: LocatorId.UpgradeAssistant }, }, ], }, diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx index 60e87cca05ada..72602b43d381c 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx @@ -6,7 +6,13 @@ * Side Public License, v 1. */ -import { EuiButtonIcon, EuiCollapsibleNav, EuiThemeProvider } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiCollapsibleNav, + EuiPopover, + EuiThemeProvider, +} from '@elastic/eui'; import { action } from '@storybook/addon-actions'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import React from 'react'; @@ -95,6 +101,7 @@ SingleExpanded.argTypes = storybookMock.getArgumentTypes(); export const ReducedPlatformLinks: ComponentStory = Template.bind({}); ReducedPlatformLinks.args = { + activeNavItemId: 'example_project.root.get_started', platformConfig: { [Platform.Analytics]: { enabled: false }, [Platform.MachineLearning]: { enabled: false }, @@ -122,6 +129,7 @@ ReducedPlatformLinks.argTypes = storybookMock.getArgumentTypes(); export const WithRecentItems: ComponentStory = Template.bind({}); WithRecentItems.args = { + activeNavItemId: 'example_project.root.get_started', recentItems: [{ id: 'recent_1', label: 'This is a test recent link', link: 'testo' }], solutions: [solutionProperties], }; @@ -129,13 +137,44 @@ WithRecentItems.argTypes = storybookMock.getArgumentTypes(); export const WithRequestsLoading: ComponentStory = Template.bind({}); WithRequestsLoading.args = { + activeNavItemId: 'example_project.root.get_started', loadingCount: 1, solutions: [solutionProperties], }; WithRequestsLoading.argTypes = storybookMock.getArgumentTypes(); +export const CustomElements: ComponentStory = Template.bind({}); +CustomElements.args = { + activeNavItemId: 'example_project.custom', + solutions: [ + { + ...solutionProperties, + items: [ + { + name: ( + + Custom element + + } + isOpen={true} + anchorPosition="rightCenter" + > + Cool popover content + + ), + id: 'custom', + }, + ], + }, + ], +}; +CustomElements.argTypes = storybookMock.getArgumentTypes(); + export const Collapsed: ComponentStory = Template.bind({}); Collapsed.args = { + activeNavItemId: 'example_project.root.get_started', navIsOpen: false, solutions: [solutionProperties], }; diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index 592c0e7ffe1f4..82fa3498c08ec 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -11,6 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiHeaderLogo, + EuiLink, EuiLoadingSpinner, EuiSideNavItemType, EuiSpacer, @@ -99,15 +100,15 @@ export const Navigation = (props: NavigationProps) => { switch (props.linkToCloud) { case 'projects': return ( - + - + ); case 'deployments': return ( - + - + ); default: return null; diff --git a/packages/shared-ux/chrome/navigation/tsconfig.json b/packages/shared-ux/chrome/navigation/tsconfig.json index 7933de14ed95a..aef86b854a0d8 100644 --- a/packages/shared-ux/chrome/navigation/tsconfig.json +++ b/packages/shared-ux/chrome/navigation/tsconfig.json @@ -18,6 +18,7 @@ "@kbn/core-application-browser", "@kbn/core-http-browser", "@kbn/shared-ux-storybook-mock", + "@kbn/shared-ux-locators", "@kbn/utility-types" ], "exclude": [ diff --git a/packages/shared-ux/locators/README.md b/packages/shared-ux/locators/README.md new file mode 100644 index 0000000000000..e5de7f88be94c --- /dev/null +++ b/packages/shared-ux/locators/README.md @@ -0,0 +1,3 @@ +# @kbn/shared-ux-locators + +A catalog of Locator IDs diff --git a/packages/shared-ux/locators/index.ts b/packages/shared-ux/locators/index.ts new file mode 100644 index 0000000000000..c468caccd5334 --- /dev/null +++ b/packages/shared-ux/locators/index.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * Use these when declaring locator definitions and when using the Locator service to find a locator. + */ +export enum LocatorId { + // Defined + ConsoleApp = 'CONSOLE_APP_LOCATOR', + Discover = 'DISCOVER_APP_LOCATOR', + ILM = 'ILM_LOCATOR_ID', + IngestPipelines = 'INGEST_PIPELINES_APP_LOCATOR', + ML = 'ML_APP_LOCATOR', + Management = 'MANAGEMENT_APP_LOCATOR', + RemoteClusters = 'REMOTE_CLUSTERS_LOCATOR', + SearchProfiler = 'SEARCH_PROFILER_LOCATOR', + SnapshotRestore = 'SNAPSHOT_RESTORE_LOCATOR', + VisualizeLibrary = 'VISUALIZE_APP_LOCATOR', + + // NEW, WIP + RulesManagement = 'RULES_MANAGEMENT_LOCATOR', + CasesManagement = 'CASES_MANAGEMENT_LOCATOR', + ConnectorsManagement = 'CONNECTORS_MANAGEMENT_LOCATOR', + CrossClusterReplication = 'CCR_MANAGEMENT_LOCATOR', + DashboardLanding = 'DASHBOARD_LANDING_APP_LOCATOR', + Fleet = 'FLEET_APP_LOCATOR', + GrokDebugger = 'GROKDEBUGGER_APP_LOCATOR', + IndexManagement = 'INDEX_MANAGEMENT_LOCATOR', + Integrations = 'INTEGRATIONS_APP_LOCATOR', + LogstashPipelines = 'LOGSTASH_MANAGEMENT_LOCATOR', + MachineLearningManagement = 'ML_MANAGEMENT_LOCATOR', + Osquery = 'OSQUERY_APP_LOCATOR', + PainlessLab = 'PAINLESS_LAB_APP_LOCATOR', + Reporting = 'REPORTING_MANAGEMENT_LOCATOR', + Rollup = 'ROLLUP_MANAGEMENT_LOCATOR', + Security = 'SECURITY_MANAGEMENT_LOCATOR', + Transform = 'TRANSFORMS_MANAGEMENT_LOCATOR', + UpgradeAssistant = 'UPGRADE_ASSISTANT_MANAGEMENT_LOCATOR', + Watcher = 'WATCHER_MANAGEMENT_LOCATOR', + Spaces = 'SPACES_MANAGEMENT_LOCATOR', + + // Defined, unused + APM = 'APM_LOCATOR', + Canvas = 'CANVAS_APP_LOCATOR', + Dashboard = 'DASHBOARD_APP_LOCATOR', // goes to "create new dashboard" + DataVisualizer = 'DATA_VISUALIZER_APP_LOCATOR', + DiscoverContext = 'DISCOVER_CONTEXT_APP_LOCATOR', + DiscoverSingleDoc = 'DISCOVER_SINGLE_DOC_LOCATOR', + LegacyShortUrl = 'LEGACY_SHORT_URL_LOCATOR', + Lens = 'LENS_APP_LOCATOR', + LicenseManagement = 'LICENSE_MANAGEMENT_LOCATOR', + Maps = 'MAPS_APP_LOCATOR', + MapsRegionMap = 'MAPS_APP_REGION_MAP_LOCATOR', + MapsTileMap = 'MAPS_APP_TILE_MAP_LOCATOR', + RuleDetails = 'RULE_DETAILS_LOCATOR', + ShortUrlRedirect = 'SHORT_URL_REDIRECT_LOCATOR', + SyntheticsEditMonitor = 'SYNTHETICS_EDIT_MONITOR_LOCATOR', + SyntheticsMonitorDetail = 'SYNTHETICS_MONITOR_DETAIL_LOCATOR', + Uptime = 'UPTIME_OVERVIEW_LOCATOR', +} diff --git a/packages/shared-ux/locators/jest.config.js b/packages/shared-ux/locators/jest.config.js new file mode 100644 index 0000000000000..12000a75b72d4 --- /dev/null +++ b/packages/shared-ux/locators/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../..', + roots: ['/packages/shared-ux/locators'], +}; diff --git a/packages/shared-ux/locators/kibana.jsonc b/packages/shared-ux/locators/kibana.jsonc new file mode 100644 index 0000000000000..1316113a1c35c --- /dev/null +++ b/packages/shared-ux/locators/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/shared-ux-locators", + "owner": "@elastic/appex-sharedux" +} diff --git a/packages/shared-ux/locators/package.json b/packages/shared-ux/locators/package.json new file mode 100644 index 0000000000000..e1f009245bf0c --- /dev/null +++ b/packages/shared-ux/locators/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/shared-ux-locators", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/shared-ux/locators/tsconfig.json b/packages/shared-ux/locators/tsconfig.json new file mode 100644 index 0000000000000..f089084e96629 --- /dev/null +++ b/packages/shared-ux/locators/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 84a6b4443e5cc..5cba9764a8da0 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1180,6 +1180,8 @@ "@kbn/shared-ux-link-redirect-app-mocks/*": ["packages/shared-ux/link/redirect_app/mocks/*"], "@kbn/shared-ux-link-redirect-app-types": ["packages/shared-ux/link/redirect_app/types"], "@kbn/shared-ux-link-redirect-app-types/*": ["packages/shared-ux/link/redirect_app/types/*"], + "@kbn/shared-ux-locators": ["packages/shared-ux/locators"], + "@kbn/shared-ux-locators/*": ["packages/shared-ux/locators/*"], "@kbn/shared-ux-markdown": ["packages/shared-ux/markdown/impl"], "@kbn/shared-ux-markdown/*": ["packages/shared-ux/markdown/impl/*"], "@kbn/shared-ux-markdown-mocks": ["packages/shared-ux/markdown/mocks"], diff --git a/yarn.lock b/yarn.lock index b01f2066a0272..0d9cb5a61c76e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5097,6 +5097,10 @@ version "0.0.0" uid "" +"@kbn/shared-ux-locators@link:packages/shared-ux/locators": + version "0.0.0" + uid "" + "@kbn/shared-ux-markdown-mocks@link:packages/shared-ux/markdown/mocks": version "0.0.0" uid "" From 150066eb2bf272a79be94ea27b207c8e218728b5 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 20 Apr 2023 18:22:37 -0700 Subject: [PATCH 20/41] variable rename and comment --- .../chrome/navigation/src/model/create_side_nav.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts index 7809ee965b9d2..9ab6f70349163 100644 --- a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts +++ b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts @@ -51,10 +51,10 @@ export const createSideNavDataFactory = ( const fullId = [parentIds, id].filter(Boolean).join('.'); - let newHref = href; // allow consumer to pass href, but default to an href formed using the locator + let calculatedHref = href; // allow consumer to pass href, but default to an href formed using the locator if (locator) { const storedLocator: Locator = locator; // never undefined - newHref = locator.getRedirectUrl(locatorParams ?? {}); // if consumer passes locator, an href they may have passed gets overwritten + calculatedHref = locator.getRedirectUrl(locatorParams ?? {}); // NOTE: calculated href overwrites href from props onClick = (event: React.MouseEvent) => { event.preventDefault(); storedLocator.navigateSync(locatorParams ?? {}); @@ -86,7 +86,7 @@ export const createSideNavDataFactory = ( name, isSelected, onClick, - href: newHref, + href: calculatedHref, items: filteredSubNav, ['data-test-subj']: `nav-item-${fullId}`, }; From 24c027ae84c234c1de4f433215a0daf6eccefa0b Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 21 Apr 2023 14:05:49 -0700 Subject: [PATCH 21/41] Remove locators package --- package.json | 1 - packages/shared-ux/locators/README.md | 3 - packages/shared-ux/locators/index.ts | 65 ---------------------- packages/shared-ux/locators/jest.config.js | 13 ----- packages/shared-ux/locators/kibana.jsonc | 5 -- packages/shared-ux/locators/package.json | 6 -- packages/shared-ux/locators/tsconfig.json | 17 ------ tsconfig.base.json | 2 - yarn.lock | 4 -- 9 files changed, 116 deletions(-) delete mode 100644 packages/shared-ux/locators/README.md delete mode 100644 packages/shared-ux/locators/index.ts delete mode 100644 packages/shared-ux/locators/jest.config.js delete mode 100644 packages/shared-ux/locators/kibana.jsonc delete mode 100644 packages/shared-ux/locators/package.json delete mode 100644 packages/shared-ux/locators/tsconfig.json diff --git a/package.json b/package.json index a60d7e2c3fa51..5c473a14e8adc 100644 --- a/package.json +++ b/package.json @@ -594,7 +594,6 @@ "@kbn/shared-ux-link-redirect-app": "link:packages/shared-ux/link/redirect_app/impl", "@kbn/shared-ux-link-redirect-app-mocks": "link:packages/shared-ux/link/redirect_app/mocks", "@kbn/shared-ux-link-redirect-app-types": "link:packages/shared-ux/link/redirect_app/types", - "@kbn/shared-ux-locators": "link:packages/shared-ux/locators", "@kbn/shared-ux-markdown": "link:packages/shared-ux/markdown/impl", "@kbn/shared-ux-markdown-mocks": "link:packages/shared-ux/markdown/mocks", "@kbn/shared-ux-markdown-types": "link:packages/shared-ux/markdown/types", diff --git a/packages/shared-ux/locators/README.md b/packages/shared-ux/locators/README.md deleted file mode 100644 index e5de7f88be94c..0000000000000 --- a/packages/shared-ux/locators/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @kbn/shared-ux-locators - -A catalog of Locator IDs diff --git a/packages/shared-ux/locators/index.ts b/packages/shared-ux/locators/index.ts deleted file mode 100644 index c468caccd5334..0000000000000 --- a/packages/shared-ux/locators/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** - * Use these when declaring locator definitions and when using the Locator service to find a locator. - */ -export enum LocatorId { - // Defined - ConsoleApp = 'CONSOLE_APP_LOCATOR', - Discover = 'DISCOVER_APP_LOCATOR', - ILM = 'ILM_LOCATOR_ID', - IngestPipelines = 'INGEST_PIPELINES_APP_LOCATOR', - ML = 'ML_APP_LOCATOR', - Management = 'MANAGEMENT_APP_LOCATOR', - RemoteClusters = 'REMOTE_CLUSTERS_LOCATOR', - SearchProfiler = 'SEARCH_PROFILER_LOCATOR', - SnapshotRestore = 'SNAPSHOT_RESTORE_LOCATOR', - VisualizeLibrary = 'VISUALIZE_APP_LOCATOR', - - // NEW, WIP - RulesManagement = 'RULES_MANAGEMENT_LOCATOR', - CasesManagement = 'CASES_MANAGEMENT_LOCATOR', - ConnectorsManagement = 'CONNECTORS_MANAGEMENT_LOCATOR', - CrossClusterReplication = 'CCR_MANAGEMENT_LOCATOR', - DashboardLanding = 'DASHBOARD_LANDING_APP_LOCATOR', - Fleet = 'FLEET_APP_LOCATOR', - GrokDebugger = 'GROKDEBUGGER_APP_LOCATOR', - IndexManagement = 'INDEX_MANAGEMENT_LOCATOR', - Integrations = 'INTEGRATIONS_APP_LOCATOR', - LogstashPipelines = 'LOGSTASH_MANAGEMENT_LOCATOR', - MachineLearningManagement = 'ML_MANAGEMENT_LOCATOR', - Osquery = 'OSQUERY_APP_LOCATOR', - PainlessLab = 'PAINLESS_LAB_APP_LOCATOR', - Reporting = 'REPORTING_MANAGEMENT_LOCATOR', - Rollup = 'ROLLUP_MANAGEMENT_LOCATOR', - Security = 'SECURITY_MANAGEMENT_LOCATOR', - Transform = 'TRANSFORMS_MANAGEMENT_LOCATOR', - UpgradeAssistant = 'UPGRADE_ASSISTANT_MANAGEMENT_LOCATOR', - Watcher = 'WATCHER_MANAGEMENT_LOCATOR', - Spaces = 'SPACES_MANAGEMENT_LOCATOR', - - // Defined, unused - APM = 'APM_LOCATOR', - Canvas = 'CANVAS_APP_LOCATOR', - Dashboard = 'DASHBOARD_APP_LOCATOR', // goes to "create new dashboard" - DataVisualizer = 'DATA_VISUALIZER_APP_LOCATOR', - DiscoverContext = 'DISCOVER_CONTEXT_APP_LOCATOR', - DiscoverSingleDoc = 'DISCOVER_SINGLE_DOC_LOCATOR', - LegacyShortUrl = 'LEGACY_SHORT_URL_LOCATOR', - Lens = 'LENS_APP_LOCATOR', - LicenseManagement = 'LICENSE_MANAGEMENT_LOCATOR', - Maps = 'MAPS_APP_LOCATOR', - MapsRegionMap = 'MAPS_APP_REGION_MAP_LOCATOR', - MapsTileMap = 'MAPS_APP_TILE_MAP_LOCATOR', - RuleDetails = 'RULE_DETAILS_LOCATOR', - ShortUrlRedirect = 'SHORT_URL_REDIRECT_LOCATOR', - SyntheticsEditMonitor = 'SYNTHETICS_EDIT_MONITOR_LOCATOR', - SyntheticsMonitorDetail = 'SYNTHETICS_MONITOR_DETAIL_LOCATOR', - Uptime = 'UPTIME_OVERVIEW_LOCATOR', -} diff --git a/packages/shared-ux/locators/jest.config.js b/packages/shared-ux/locators/jest.config.js deleted file mode 100644 index 12000a75b72d4..0000000000000 --- a/packages/shared-ux/locators/jest.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../../..', - roots: ['/packages/shared-ux/locators'], -}; diff --git a/packages/shared-ux/locators/kibana.jsonc b/packages/shared-ux/locators/kibana.jsonc deleted file mode 100644 index 1316113a1c35c..0000000000000 --- a/packages/shared-ux/locators/kibana.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/shared-ux-locators", - "owner": "@elastic/appex-sharedux" -} diff --git a/packages/shared-ux/locators/package.json b/packages/shared-ux/locators/package.json deleted file mode 100644 index e1f009245bf0c..0000000000000 --- a/packages/shared-ux/locators/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@kbn/shared-ux-locators", - "private": true, - "version": "1.0.0", - "license": "SSPL-1.0 OR Elastic License 2.0" -} \ No newline at end of file diff --git a/packages/shared-ux/locators/tsconfig.json b/packages/shared-ux/locators/tsconfig.json deleted file mode 100644 index f089084e96629..0000000000000 --- a/packages/shared-ux/locators/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "target/types", - "types": [ - "jest", - "node" - ] - }, - "include": [ - "**/*.ts" - ], - "exclude": [ - "target/**/*" - ], - "kbn_references": [] -} diff --git a/tsconfig.base.json b/tsconfig.base.json index 5cba9764a8da0..84a6b4443e5cc 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1180,8 +1180,6 @@ "@kbn/shared-ux-link-redirect-app-mocks/*": ["packages/shared-ux/link/redirect_app/mocks/*"], "@kbn/shared-ux-link-redirect-app-types": ["packages/shared-ux/link/redirect_app/types"], "@kbn/shared-ux-link-redirect-app-types/*": ["packages/shared-ux/link/redirect_app/types/*"], - "@kbn/shared-ux-locators": ["packages/shared-ux/locators"], - "@kbn/shared-ux-locators/*": ["packages/shared-ux/locators/*"], "@kbn/shared-ux-markdown": ["packages/shared-ux/markdown/impl"], "@kbn/shared-ux-markdown/*": ["packages/shared-ux/markdown/impl/*"], "@kbn/shared-ux-markdown-mocks": ["packages/shared-ux/markdown/mocks"], diff --git a/yarn.lock b/yarn.lock index 0d9cb5a61c76e..b01f2066a0272 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5097,10 +5097,6 @@ version "0.0.0" uid "" -"@kbn/shared-ux-locators@link:packages/shared-ux/locators": - version "0.0.0" - uid "" - "@kbn/shared-ux-markdown-mocks@link:packages/shared-ux/markdown/mocks": version "0.0.0" uid "" From c32666a712844b2cd8f82e5623d8b4be779827da Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 21 Apr 2023 14:06:15 -0700 Subject: [PATCH 22/41] Remove reference to locators --- .../shared-ux/chrome/navigation/README.mdx | 2 +- .../chrome/navigation/mocks/src/jest.ts | 2 -- .../chrome/navigation/mocks/src/mocks.ts | 5 +--- .../chrome/navigation/mocks/src/storybook.ts | 20 +++++-------- .../navigation/src/model/create_side_nav.ts | 29 +++---------------- .../chrome/navigation/src/model/index.ts | 8 +---- .../chrome/navigation/src/services.tsx | 2 -- .../navigation/src/ui/navigation.stories.tsx | 7 ++--- .../shared-ux/chrome/navigation/tsconfig.json | 1 - .../chrome/navigation/types/index.ts | 20 +++---------- .../chrome/navigation/types/internal.ts | 29 ------------------- 11 files changed, 20 insertions(+), 105 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/README.mdx b/packages/shared-ux/chrome/navigation/README.mdx index 1542dd3dc1ed5..cf03dbdc4c1b1 100644 --- a/packages/shared-ux/chrome/navigation/README.mdx +++ b/packages/shared-ux/chrome/navigation/README.mdx @@ -2,7 +2,7 @@ id: sharedUX/Chrome/Navigation slug: /shared-ux/chrome/navigation title: Kibana Chrome Navigation -description: Navigation container to render items containing Locator IDs +description: Navigation container to render items for cross-app linking tags: ['shared-ux', 'component', 'chrome', 'navigation'] date: 2023-02-28 --- diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index 08c26c5389249..d4e5ddc649540 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -12,14 +12,12 @@ export const getServicesMock = (): NavigationServices => { const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; const navigateToUrl = jest.fn().mockResolvedValue(undefined); const basePath = { prepend: jest.fn((path: string) => `/base${path}`) }; - const getLocator = jest.fn(); const registerNavItemClick = jest.fn(); const loadingCount = 0; return { activeNavItemId: 'test.hello.lamp', basePath, - getLocator, loadingCount, navIsOpen: true, navigateToUrl, diff --git a/packages/shared-ux/chrome/navigation/mocks/src/mocks.ts b/packages/shared-ux/chrome/navigation/mocks/src/mocks.ts index 1198fe0242eb8..8349bd7f9f7a6 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/mocks.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/mocks.ts @@ -8,13 +8,10 @@ import { SolutionProperties } from '../../types'; -const MOCK_LOCATOR_ID = 'MOCK_LOCATOR_ID'; - -export const mocks: SolutionProperties & { locatorId: string } = { +export const mocks: SolutionProperties = { id: 'example_project', icon: 'logoObservability', name: 'Example Project', - locatorId: MOCK_LOCATOR_ID, items: [ { id: 'root', diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index 77b17d466c029..5cdc3db09143c 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -7,11 +7,9 @@ */ import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock'; -import { SerializableRecord } from '@kbn/utility-types'; import { action } from '@storybook/addon-actions'; import { BehaviorSubject } from 'rxjs'; import { NavigationProps, NavigationServices } from '../../types'; -import { GetLocatorFn } from '../../types/internal'; type Arguments = NavigationProps & NavigationServices; export type Params = Pick< @@ -39,15 +37,12 @@ export class StorybookMock extends AbstractStorybookMock ({ - getRedirectUrl: () => `/app/for/${locatorId}`, - navigateSync: (locatorParams?: SerializableRecord) => { - navAction(`Locator: ${locatorId} / Params: ${JSON.stringify(locatorParams)}`); - }, - }); + const navigateToUrl = (url: string) => { + navAction(url); + return Promise.resolve(); + }; + const registerNavItemClickAction = action('Register click'); const activeNavItemId$ = new BehaviorSubject(undefined); const registerNavItemClick = (id: string) => { activeNavItemId$.next(id); @@ -56,9 +51,8 @@ export class StorybookMock extends AbstractStorybookMock '' }, - navigateToUrl: () => Promise.resolve(), - getLocator, + basePath: { prepend: (suffix: string) => `/basepath${suffix}` }, + navigateToUrl, navIsOpen, recentItems, registerNavItemClick, diff --git a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts index 9ab6f70349163..b3743aa231b3f 100644 --- a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts +++ b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts @@ -9,7 +9,6 @@ import type { EuiSideNavItemType } from '@elastic/eui'; import type { NavigationModelDeps } from '.'; import type { NavItemProps, PlatformSectionConfig } from '../../types'; -import type { Locator } from '../../types/internal'; type MyEuiSideNavItem = EuiSideNavItemType; type OnClickFn = MyEuiSideNavItem['onClick']; @@ -22,45 +21,25 @@ export const createSideNavDataFactory = ( deps: NavigationModelDeps, activeNavItemId: string | undefined ) => { - const { basePath, navigateToUrl, getLocator, registerNavItemClick } = deps; + const { basePath, navigateToUrl, registerNavItemClick } = deps; const createSideNavData = ( parentIds: string | number = '', navItems: NavItemProps[], platformSectionConfig?: PlatformSectionConfig ): Array> => navItems.reduce((accum, item) => { - const { id, name, items: subNav, locator: locatorDefinition, href } = item; + const { id, name, items: subNav, href } = item; const config = platformSectionConfig?.properties?.[id]; if (config?.enabled === false) { // return accumulated set without the item that is not enabled return accum; } - const { id: locatorId, params: locatorParams } = locatorDefinition ?? {}; - let locator: Locator | undefined; - if (locatorId) { - locator = getLocator(locatorId); - } - let onClick: OnClickFn | undefined; - if (locatorId && !locator) { - // return `accum` to skip this link - return accum; - } - const fullId = [parentIds, id].filter(Boolean).join('.'); - let calculatedHref = href; // allow consumer to pass href, but default to an href formed using the locator - if (locator) { - const storedLocator: Locator = locator; // never undefined - calculatedHref = locator.getRedirectUrl(locatorParams ?? {}); // NOTE: calculated href overwrites href from props - onClick = (event: React.MouseEvent) => { - event.preventDefault(); - storedLocator.navigateSync(locatorParams ?? {}); - registerNavItemClick(fullId); - }; - } else if (href) { + if (href) { onClick = (event: React.MouseEvent) => { event.preventDefault(); navigateToUrl(basePath.prepend(href)); @@ -86,7 +65,7 @@ export const createSideNavDataFactory = ( name, isSelected, onClick, - href: calculatedHref, + href, items: filteredSubNav, ['data-test-subj']: `nav-item-${fullId}`, }; diff --git a/packages/shared-ux/chrome/navigation/src/model/index.ts b/packages/shared-ux/chrome/navigation/src/model/index.ts index 1dc717b09429f..e8736753780e8 100644 --- a/packages/shared-ux/chrome/navigation/src/model/index.ts +++ b/packages/shared-ux/chrome/navigation/src/model/index.ts @@ -6,21 +6,15 @@ * Side Public License, v 1. */ +import { BasePathService, NavigateToUrlFn, NavItemClickFn } from '../../types/internal'; import { analyticsItemSet } from './platform_nav/analytics'; import { devtoolsItemSet } from './platform_nav/devtools'; import { mlItemSet } from './platform_nav/machine_learning'; import { managementItemSet } from './platform_nav/management'; -import { - BasePathService, - GetLocatorFn, - NavigateToUrlFn, - NavItemClickFn, -} from '../../types/internal'; export interface NavigationModelDeps { basePath: BasePathService; navigateToUrl: NavigateToUrlFn; - getLocator: GetLocatorFn; registerNavItemClick: NavItemClickFn; } diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index 5c2c4964acbdd..74102469ca7e3 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -40,13 +40,11 @@ export const NavigationKibanaProvider: FC = ({ const activeNavItemId = useObservable(chrome.getActiveNavItemId$()); const loadingCount = useObservable(http.getLoadingCount$(), 0); - const getLocator = (id: string) => dependencies.share.url.locators.get(id); const registerNavItemClick = chrome.registerNavItemClick; const value: NavigationServices = { activeNavItemId, basePath, - getLocator, loadingCount, navIsOpen, navigateToUrl, diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx index 72602b43d381c..4692306b67a01 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx @@ -16,15 +16,13 @@ import { import { action } from '@storybook/addon-actions'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import React from 'react'; -import { mocks, NavigationStorybookMock } from '../../mocks'; +import { mocks as solutionProperties, NavigationStorybookMock } from '../../mocks'; import mdx from '../../README.mdx'; import { NavigationProps, NavigationServices } from '../../types'; import { Platform } from '../model'; import { NavigationProvider } from '../services'; import { Navigation as Component } from './navigation'; -const { locatorId, ...solutionProperties } = mocks; - const storybookMock = new NavigationStorybookMock(); let colorMode = ''; colorMode = 'LIGHT'; @@ -82,8 +80,7 @@ const Template = (args: NavigationProps & NavigationServices) => { export default { title: 'Chrome/Navigation', - description: - 'An accordion-like component that renders a nested array of navigation items that use Locator information.', + description: 'Navigation container to render items for cross-app linking', parameters: { docs: { page: mdx, diff --git a/packages/shared-ux/chrome/navigation/tsconfig.json b/packages/shared-ux/chrome/navigation/tsconfig.json index aef86b854a0d8..7933de14ed95a 100644 --- a/packages/shared-ux/chrome/navigation/tsconfig.json +++ b/packages/shared-ux/chrome/navigation/tsconfig.json @@ -18,7 +18,6 @@ "@kbn/core-application-browser", "@kbn/core-http-browser", "@kbn/shared-ux-storybook-mock", - "@kbn/shared-ux-locators", "@kbn/utility-types" ], "exclude": [ diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index 821aff25cd1a0..66f434d8b986d 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -8,14 +8,7 @@ import type { EuiSideNavItemType, IconType } from '@elastic/eui'; import { Observable } from 'rxjs'; -import { - BasePathService, - GetLocatorFn, - ILocatorDefinition, - NavigateToUrlFn, - NavItemClickFn, - RecentItem, -} from './internal'; +import { BasePathService, NavigateToUrlFn, NavItemClickFn, RecentItem } from './internal'; /** * A list of services that are consumed by this component. @@ -24,7 +17,6 @@ import { export interface NavigationServices { activeNavItemId: string | undefined; basePath: BasePathService; - getLocator: GetLocatorFn; loadingCount: number; navIsOpen: boolean; navigateToUrl: NavigateToUrlFn; @@ -38,7 +30,6 @@ export interface NavigationServices { * @public */ export interface NavigationKibanaDependencies { - share: { url: { locators: { get: GetLocatorFn } } }; core: { application: { navigateToUrl: NavigateToUrlFn }; chrome: { @@ -60,15 +51,12 @@ export interface NavigationKibanaDependencies { */ export type NavItemProps = Pick, 'id' | 'name'> & { /** - * Nav Items that could contain locators + * Nav Items */ items?: Array>; /** - * ID of a registered LocatorDefinition - */ - locator?: ILocatorDefinition; - /** - * Href for a link destination (for links within a project) + * Href for a link destination + * Example: /app/fleet */ href?: string; }; diff --git a/packages/shared-ux/chrome/navigation/types/internal.ts b/packages/shared-ux/chrome/navigation/types/internal.ts index f9c5076ecd860..41770532b7b72 100644 --- a/packages/shared-ux/chrome/navigation/types/internal.ts +++ b/packages/shared-ux/chrome/navigation/types/internal.ts @@ -8,20 +8,6 @@ import type { ApplicationStart } from '@kbn/core-application-browser'; import type { IBasePath } from '@kbn/core-http-browser'; -import type { SerializableRecord } from '@kbn/utility-types'; - -/** - * @internal - */ -export interface Locator

{ - getRedirectUrl(params: P): string; - navigateSync: (params: P) => void; -} - -/** - * @internal - */ -export type GetLocatorFn = (locatorId: string) => Locator | undefined; /** * @internal @@ -42,21 +28,6 @@ export interface RecentItem { id: string; } -/** - * Locator info used for navigating around Kibana - * @internal - */ -export interface ILocatorDefinition

{ - /** - * ID of a registered LocatorDefinition - */ - id: string; - /** - * Navigational params in the form understood by the locator's plugin. - */ - params?: P; -} - export type NavigateToUrlFn = ApplicationStart['navigateToUrl']; export type BasePathService = Pick; From 817ec1f9804eb73af5dc1ccfc8f766fa2c530da7 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 21 Apr 2023 14:06:27 -0700 Subject: [PATCH 23/41] replace configured locator objects with href strings --- .../src/model/platform_nav/analytics.ts | 7 +- .../src/model/platform_nav/devtools.ts | 9 +-- .../model/platform_nav/machine_learning.ts | 33 ++++---- .../src/model/platform_nav/management.ts | 78 +++++++------------ 4 files changed, 52 insertions(+), 75 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts index 91ca577da2606..586ea872326e7 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { LocatorId } from '@kbn/shared-ux-locators'; import { NavItemProps } from '../../../types'; export const analyticsItemSet: NavItemProps[] = [ @@ -17,17 +16,17 @@ export const analyticsItemSet: NavItemProps[] = [ { name: 'Discover', id: 'discover', - locator: { id: LocatorId.Discover }, + href: '/app/discover', }, { name: 'Dashboard', id: 'dashboard', - locator: { id: LocatorId.Dashboard }, + href: '/app/dashboard', }, { name: 'Visualize Library', id: 'visualize_library', - locator: { id: LocatorId.VisualizeLibrary }, + href: '/app/visualize', }, ], }, diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts index 71c5c016d39ab..049a537c96cc6 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/devtools.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { LocatorId } from '@kbn/shared-ux-locators'; import { NavItemProps } from '../../../types'; export const devtoolsItemSet: NavItemProps[] = [ @@ -17,22 +16,22 @@ export const devtoolsItemSet: NavItemProps[] = [ { name: 'Console', id: 'console', - locator: { id: LocatorId.ConsoleApp }, + href: '/app/dev_tools#/console', }, { name: 'Search profiler', id: 'search_profiler', - locator: { id: LocatorId.SearchProfiler }, + href: '/app/dev_tools#/searchprofiler', }, { name: 'Grok debugger', id: 'grok_debugger', - locator: { id: LocatorId.GrokDebugger }, + href: '/app/dev_tools#/grokdebugger', }, { name: 'Painless lab', id: 'painless_lab', - locator: { id: LocatorId.PainlessLab }, + href: '/app/dev_tools#/painless_lab', }, ], }, diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts index 669d8cfb5b8c3..0d77a370e9412 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts @@ -6,11 +6,8 @@ * Side Public License, v 1. */ -import { LocatorId } from '@kbn/shared-ux-locators'; import { NavItemProps } from '../../../types'; -const { ML } = LocatorId; - export const mlItemSet: NavItemProps[] = [ { name: '', @@ -19,12 +16,12 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Overview', id: 'overview', - locator: { id: ML, params: { page: 'overview' } }, + href: '/app/ml/overview', }, { name: 'Notifications', id: 'notifications', - locator: { id: ML, params: { page: 'notifications' } }, + href: '/app/ml/notifications', }, ], }, @@ -35,22 +32,22 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Jobs', id: 'jobs', - locator: { id: ML, params: { page: 'jobs' } }, + href: '/app/ml/jobs', }, { name: 'Anomaly explorer', id: 'explorer', - locator: { id: ML, params: { page: 'explorer' } }, + href: '/app/ml/explorer', }, { name: 'Single metric viewer', id: 'single_metric_viewer', - locator: { id: ML, params: { page: 'timeseriesexplorer' } }, + href: '/app/ml/timeseriesexplorer', }, { name: 'Settings', id: 'settings', - locator: { id: ML, params: { page: 'settings' } }, + href: '/app/ml/settings', }, ], }, @@ -61,17 +58,17 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Jobs', id: 'jobs', - locator: { id: ML, params: { page: 'data_frame_analytics' } }, + href: '/app/ml/data_frame_analytics', }, { name: 'Results explorer', id: 'results_explorer', - locator: { id: ML, params: { page: 'data_frame_analytics/exploration' } }, + href: '/app/ml/data_frame_analytics/exploration', }, { name: 'Analytics map', id: 'analytics_map', - locator: { id: ML, params: { page: 'data_frame_analytics/map' } }, + href: '/app/ml/data_frame_analytics/map', }, ], }, @@ -82,12 +79,12 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Trained models', id: 'trained_models', - locator: { id: ML, params: { page: 'trained_models' } }, + href: '/app/ml/trained_models', }, { name: 'Nodes', id: 'nodes', - locator: { id: ML, params: { page: 'nodes' } }, + href: '/app/ml/nodes', }, ], }, @@ -98,12 +95,12 @@ export const mlItemSet: NavItemProps[] = [ { name: 'File', id: 'file', - locator: { id: ML, params: { page: 'filedatavisualizer' } }, + href: '/app/ml/filedatavisualizer', }, { name: 'Data page', id: 'data_view', - locator: { id: ML, params: { page: 'datavisualizer_index_select' } }, + href: '/app/ml/datavisualizer_index_select', }, ], }, @@ -114,12 +111,12 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Explain log rate spikes', id: 'explain_log_rate_spikes', - locator: { id: ML, params: { page: 'explain_log_rate_spikes_index_select' } }, + href: '/app/ml/explain_log_rate_spikes_index_select', }, { name: 'Log pattern analysis', id: 'log_pattern_analysis', - locator: { id: ML, params: { page: 'log_categorization_index_select' } }, + href: '/app/ml/log_categorization_index_select', }, ], }, diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts index 81190e83fcb71..94bfad3f8e2fc 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts @@ -6,11 +6,8 @@ * Side Public License, v 1. */ -import { LocatorId } from '@kbn/shared-ux-locators'; import { NavItemProps } from '../../../types'; -const { Management } = LocatorId; - export const managementItemSet: NavItemProps[] = [ { name: '', @@ -19,7 +16,7 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Stack monitoring', id: 'stack_monitoring', - locator: { id: 'STACK_MONITORING_APP_LOCATOR' }, + href: '/app/monitoring', }, ], }, @@ -30,17 +27,17 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Integrations', id: 'integrations', - locator: { id: LocatorId.Integrations }, + href: '/app/integrations', }, { name: 'Fleet', id: 'fleet', - locator: { id: LocatorId.Fleet }, + href: '/app/fleet', }, { name: 'Osquery', id: 'osquery', - locator: { id: LocatorId.Osquery }, + href: '/app/osquery', }, ], }, @@ -55,12 +52,12 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Ingest pipelines', id: 'ingest_pipelines', - locator: { id: LocatorId.IngestPipelines }, + href: '/app/management/ingest/ingest_pipelines', }, { name: 'Logstash pipelines', id: 'logstash_pipelines', - locator: { id: LocatorId.LogstashPipelines }, + href: '/app/management/ingest/pipelines', }, ], }, @@ -71,37 +68,37 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Index management', id: 'index_management', - locator: { id: LocatorId.IndexManagement }, + href: '/app/management/data/index_management', }, { name: 'Index lifecycle policies', id: 'index_lifecycle_policies', - locator: { id: LocatorId.ILM }, + // locator: { id: LocatorId.ILM }, }, { name: 'Snapshot and restore', id: 'snapshot_and_restore', - locator: { id: LocatorId.SnapshotRestore }, + // locator: { id: LocatorId.SnapshotRestore }, }, { name: 'Rollup jobs', id: 'rollup_jobs', - locator: { id: LocatorId.Rollup }, + // locator: { id: LocatorId.Rollup }, }, { name: 'Transforms', id: 'transforms', - locator: { id: LocatorId.Transform }, + href: '/app/management/data/transform', }, { name: 'Cross-cluster replication', id: 'cross_cluster_replication', - locator: { id: LocatorId.CrossClusterReplication }, + // locator: { id: LocatorId.CrossClusterReplication }, }, { name: 'Remote clusters', id: 'remote_clusters', - locator: { id: LocatorId.RemoteClusters }, + // locator: { id: LocatorId.RemoteClusters }, }, ], }, @@ -112,35 +109,32 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Rules', id: 'rules', - locator: { id: LocatorId.RulesManagement, params: { appId: 'triggersActions' } }, + href: '/app/management/insightsAndAlerting/triggersActions/rules', }, { name: 'Cases', id: 'cases', - locator: { id: LocatorId.CasesManagement, params: { appId: 'cases' } }, + href: '/app/management/insightsAndAlerting/cases', }, { name: 'Connectors', id: 'connectors', - locator: { - id: LocatorId.ConnectorsManagement, - params: { appId: 'triggersActionsConnectors' }, - }, + href: '/app/management/insightsAndAlerting/triggersActionsConnectors/connectors', }, { name: 'Reporting', id: 'reporting', - locator: { id: LocatorId.Reporting }, + // locator: { id: LocatorId.Reporting }, }, { name: 'Machine learning', id: 'machine_learning', - locator: { id: LocatorId.MachineLearningManagement }, + href: '/app/management/insightsAndAlerting/jobsListLink', }, { name: 'Watcher', id: 'watcher', - locator: { id: LocatorId.Watcher }, + // locator: { id: LocatorId.Watcher }, }, ], }, @@ -151,22 +145,22 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Users', id: 'users', - locator: { id: LocatorId.Security, params: { appId: 'users' } }, + href: '/app/management/security/users', }, { name: 'Roles', id: 'roles', - locator: { id: LocatorId.Security, params: { appId: 'roles' } }, + href: '/app/management/security/roles', }, { name: 'Role mappings', id: 'role_mappings', - locator: { id: LocatorId.Security, params: { appId: 'role_mappings' } }, + href: '/app/management/security/role_mappings', }, { name: 'API keys', id: 'api_keys', - locator: { id: LocatorId.Security, params: { appId: 'api_keys' } }, + href: '/app/management/security/api_keys', }, ], }, @@ -177,51 +171,39 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Data view', id: 'data_views', - locator: { - id: Management, - params: { sectionId: 'kibana', appId: 'dataViews' }, - }, + href: '/app/management/kibana/dataViews', }, { name: 'Saved objects', id: 'saved_objects', - locator: { - id: Management, - params: { sectionId: 'kibana', appId: 'objects' }, - }, + href: '/app/management/kibana/objects', }, { name: 'Tags', id: 'tags', - locator: { id: Management, params: { sectionId: 'kibana', appId: 'tags' } }, + href: '/app/management/kibana/tags', }, { name: 'Search sessions', id: 'search_sessions', - locator: { - id: Management, - params: { sectionId: 'kibana', appId: 'search_sessions' }, - }, + href: '/app/management/kibana/search_sessions', }, { name: 'Spaces', id: 'spaces', - locator: { id: LocatorId.Spaces }, + href: '/app/management/kibana/spaces', }, { name: 'Advanced settings', id: 'advanced_settings', - locator: { - id: Management, - params: { sectionId: 'kibana', appId: 'settings' }, - }, + href: '/app/management/kibana/settings', }, ], }, { name: 'Upgrade assistant', id: 'upgrade_assistant', - locator: { id: LocatorId.UpgradeAssistant }, + // locator: { id: LocatorId.UpgradeAssistant }, }, ], }, From 2c7f3db0a683c8c6e07a5b7025e01c9ca1f4f7c8 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 21 Apr 2023 21:38:42 +0000 Subject: [PATCH 24/41] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/shared-ux/chrome/navigation/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/shared-ux/chrome/navigation/tsconfig.json b/packages/shared-ux/chrome/navigation/tsconfig.json index 7933de14ed95a..01a08fab03b4a 100644 --- a/packages/shared-ux/chrome/navigation/tsconfig.json +++ b/packages/shared-ux/chrome/navigation/tsconfig.json @@ -18,7 +18,6 @@ "@kbn/core-application-browser", "@kbn/core-http-browser", "@kbn/shared-ux-storybook-mock", - "@kbn/utility-types" ], "exclude": [ "target/**/*" From 3ff81bab4fc2367069aa6e14181878f560966d42 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 21 Apr 2023 23:13:24 -0700 Subject: [PATCH 25/41] simple unit tests --- .../chrome/navigation/mocks/index.ts | 6 +- .../chrome/navigation/mocks/src/jest.ts | 53 +++++++- .../chrome/navigation/mocks/src/mocks.ts | 59 --------- .../navigation/src/model/create_side_nav.ts | 4 +- .../navigation/src/ui/navigation.stories.tsx | 14 +-- .../navigation/src/ui/navigation.test.tsx | 116 ++++++++++++++++-- .../chrome/navigation/src/ui/navigation.tsx | 31 +++-- .../shared-ux/chrome/navigation/tsconfig.json | 5 +- .../chrome/navigation/types/index.ts | 2 +- 9 files changed, 195 insertions(+), 95 deletions(-) delete mode 100644 packages/shared-ux/chrome/navigation/mocks/src/mocks.ts diff --git a/packages/shared-ux/chrome/navigation/mocks/index.ts b/packages/shared-ux/chrome/navigation/mocks/index.ts index be262b787aced..a72e07ce52132 100644 --- a/packages/shared-ux/chrome/navigation/mocks/index.ts +++ b/packages/shared-ux/chrome/navigation/mocks/index.ts @@ -6,7 +6,9 @@ * Side Public License, v 1. */ -export { getServicesMock as getNavigationServicesMock } from './src/jest'; -export { mocks } from './src/mocks'; +export { + getServicesMock as getNavigationServicesMock, + getSolutionPropertiesMock, +} from './src/jest'; export { StorybookMock as NavigationStorybookMock } from './src/storybook'; export type { Params as NavigationStorybookParams } from './src/storybook'; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index d4e5ddc649540..00be975cf5349 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { NavigationServices } from '../../types'; +import { NavigationServices, SolutionProperties } from '../../types'; export const getServicesMock = (): NavigationServices => { const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; @@ -16,7 +16,6 @@ export const getServicesMock = (): NavigationServices => { const loadingCount = 0; return { - activeNavItemId: 'test.hello.lamp', basePath, loadingCount, navIsOpen: true, @@ -25,3 +24,53 @@ export const getServicesMock = (): NavigationServices => { registerNavItemClick, }; }; + +export const getSolutionPropertiesMock = (): SolutionProperties => ({ + id: 'example_project', + icon: 'logoObservability', + name: 'Example project', + items: [ + { + id: 'root', + name: '', + items: [ + { + id: 'get_started', + name: 'Get started', + href: '/app/example_project/get_started', + }, + { + id: 'alerts', + name: 'Alerts', + href: '/app/example_project/alerts', + }, + { + id: 'cases', + name: 'Cases', + href: '/app/example_project/cases', + }, + ], + }, + { + id: 'example_settings', + name: 'Settings', + items: [ + { + id: 'logs', + name: 'Logs', + href: '/app/management/logs', + }, + { + id: 'signals', + name: 'Signals', + href: '/app/management/signals', + }, + { + id: 'tracing', + name: 'Tracing', + href: '/app/management/tracing', + }, + ], + }, + ], +}); diff --git a/packages/shared-ux/chrome/navigation/mocks/src/mocks.ts b/packages/shared-ux/chrome/navigation/mocks/src/mocks.ts deleted file mode 100644 index 8349bd7f9f7a6..0000000000000 --- a/packages/shared-ux/chrome/navigation/mocks/src/mocks.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { SolutionProperties } from '../../types'; - -export const mocks: SolutionProperties = { - id: 'example_project', - icon: 'logoObservability', - name: 'Example Project', - items: [ - { - id: 'root', - name: '', - items: [ - { - id: 'get_started', - name: 'Get started', - href: '/app/example_project/get_started', - }, - { - id: 'alerts', - name: 'Alerts', - href: '/app/example_project/alerts', - }, - { - id: 'cases', - name: 'Cases', - href: '/app/example_project/cases', - }, - ], - }, - { - id: 'example_settings', - name: 'Settings', - items: [ - { - id: 'logs', - name: 'Logs', - href: '/app/management/logs', - }, - { - id: 'signals', - name: 'Signals', - href: '/app/management/signals', - }, - { - id: 'tracing', - name: 'Tracing', - href: '/app/management/tracing', - }, - ], - }, - ], -}; diff --git a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts index b3743aa231b3f..45eac74cf9964 100644 --- a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts +++ b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts @@ -55,9 +55,11 @@ export const createSideNavDataFactory = ( } let isSelected: boolean = false; + let subjId = fullId; if (!subNav && fullId === activeNavItemId) { // if there are no subnav items and ID is current, mark the item as selected isSelected = true; + subjId += '-selected'; } const next: MyEuiSideNavItem = { @@ -67,7 +69,7 @@ export const createSideNavDataFactory = ( onClick, href, items: filteredSubNav, - ['data-test-subj']: `nav-item-${fullId}`, + ['data-test-subj']: `nav-item-${subjId}`, }; return [...accum, next]; }, []); diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx index 4692306b67a01..1a2618a2745a0 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx @@ -16,7 +16,7 @@ import { import { action } from '@storybook/addon-actions'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import React from 'react'; -import { mocks as solutionProperties, NavigationStorybookMock } from '../../mocks'; +import { getSolutionPropertiesMock, NavigationStorybookMock } from '../../mocks'; import mdx from '../../README.mdx'; import { NavigationProps, NavigationServices } from '../../types'; import { Platform } from '../model'; @@ -92,7 +92,7 @@ export default { export const SingleExpanded: ComponentStory = Template.bind({}); SingleExpanded.args = { activeNavItemId: 'example_project.root.get_started', - solutions: [solutionProperties], + solutions: [getSolutionPropertiesMock()], }; SingleExpanded.argTypes = storybookMock.getArgumentTypes(); @@ -120,7 +120,7 @@ ReducedPlatformLinks.args = { }, }, }, - solutions: [solutionProperties], + solutions: [getSolutionPropertiesMock()], }; ReducedPlatformLinks.argTypes = storybookMock.getArgumentTypes(); @@ -128,7 +128,7 @@ export const WithRecentItems: ComponentStory = Template.bind({} WithRecentItems.args = { activeNavItemId: 'example_project.root.get_started', recentItems: [{ id: 'recent_1', label: 'This is a test recent link', link: 'testo' }], - solutions: [solutionProperties], + solutions: [getSolutionPropertiesMock()], }; WithRecentItems.argTypes = storybookMock.getArgumentTypes(); @@ -136,7 +136,7 @@ export const WithRequestsLoading: ComponentStory = Template.bin WithRequestsLoading.args = { activeNavItemId: 'example_project.root.get_started', loadingCount: 1, - solutions: [solutionProperties], + solutions: [getSolutionPropertiesMock()], }; WithRequestsLoading.argTypes = storybookMock.getArgumentTypes(); @@ -145,7 +145,7 @@ CustomElements.args = { activeNavItemId: 'example_project.custom', solutions: [ { - ...solutionProperties, + ...getSolutionPropertiesMock(), items: [ { name: ( @@ -173,6 +173,6 @@ export const Collapsed: ComponentStory = Template.bind({}); Collapsed.args = { activeNavItemId: 'example_project.root.get_started', navIsOpen: false, - solutions: [solutionProperties], + solutions: [getSolutionPropertiesMock()], }; Collapsed.argTypes = storybookMock.getArgumentTypes(); diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx index bc0ac575a1cc5..075925f05de6a 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx @@ -6,32 +6,122 @@ * Side Public License, v 1. */ +import { render } from '@testing-library/react'; import React from 'react'; -import { render } from 'react-dom'; import { getServicesMock } from '../../mocks/src/jest'; +import { PlatformConfigSet, SolutionProperties } from '../../types'; +import { Platform } from '../model'; import { NavigationProvider } from '../services'; import { Navigation } from './navigation'; describe('', () => { - test('renders with minimal props', () => { - const div = document.createElement('div'); - const services = getServicesMock(); + const services = getServicesMock(); - const homeHref = '#'; - const platformSections = {}; - const solutions = [ + const homeHref = '#'; + let platformSections: PlatformConfigSet | undefined; + let solutions: SolutionProperties[]; + + beforeEach(() => { + platformSections = { analytics: {}, ml: {}, devTools: {}, management: {} }; + solutions = [{ id: 'navigation_testing', name: 'Navigation testing', icon: 'gear' }]; + }); + + test('renders the header logo and top-level navigation buckets', async () => { + const { findByTestId, findByText } = render( + + + + ); + + expect(await findByText('Navigation testing')).toBeVisible(); + + expect(await findByTestId('nav-header-logo')).toBeVisible(); + expect(await findByTestId('nav-bucket-navigation_testing')).toBeVisible(); + expect(await findByTestId('nav-bucket-analytics')).toBeVisible(); + expect(await findByTestId('nav-bucket-ml')).toBeVisible(); + expect(await findByTestId('nav-bucket-devTools')).toBeVisible(); + expect(await findByTestId('nav-bucket-management')).toBeVisible(); + }); + + test('includes link to deployments', async () => { + const { findByText } = render( + + + + ); + + expect(await findByText('My deployments')).toBeVisible(); + }); + + test('platform links can be disabled', async () => { + platformSections = { + [Platform.Analytics]: { enabled: false }, + [Platform.MachineLearning]: { enabled: false }, + [Platform.DevTools]: { enabled: false }, + [Platform.Management]: { enabled: false }, + }; + + const { findByTestId, queryByTestId } = render( + + + + ); + + expect(await findByTestId('nav-header-logo')).toBeVisible(); + expect(queryByTestId('nav-bucket-analytics')).not.toBeInTheDocument(); + expect(queryByTestId('nav-bucket-ml')).not.toBeInTheDocument(); + expect(queryByTestId('nav-bucket-devTools')).not.toBeInTheDocument(); + expect(queryByTestId('nav-bucket-management')).not.toBeInTheDocument(); + }); + + test('sets the specified nav item to active', async () => { + solutions[0].items = [ { - id: 'navigation_testing', - name: 'Navigation testing', - icon: 'gear', + id: 'root', + name: '', + items: [ + { + id: 'city', + name: 'City', + }, + { + id: 'town', + name: 'Town', + }, + ], }, ]; - render( + const { findByTestId } = render( + + + + ); + + const label = await findByTestId('nav-item-navigation_testing.root.city-selected'); + expect(label).toHaveTextContent('City'); + expect(label).toBeVisible(); + }); + + test('shows loading state', async () => { + services.loadingCount = 5; + + const { findByTestId } = render( - , - div + ); + + expect(await findByTestId('nav-header-loading-spinner')).toBeVisible(); }); }); diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index 82fa3498c08ec..b7a9b79547950 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -78,10 +78,20 @@ export const Navigation = (props: NavigationProps) => { }; const logo = loadingCount === 0 ? ( - + ) : ( - + ); @@ -100,13 +110,21 @@ export const Navigation = (props: NavigationProps) => { switch (props.linkToCloud) { case 'projects': return ( - + ); case 'deployments': return ( - + ); @@ -123,10 +141,7 @@ export const Navigation = (props: NavigationProps) => { return ( - + diff --git a/packages/shared-ux/chrome/navigation/tsconfig.json b/packages/shared-ux/chrome/navigation/tsconfig.json index 01a08fab03b4a..b7f65599303af 100644 --- a/packages/shared-ux/chrome/navigation/tsconfig.json +++ b/packages/shared-ux/chrome/navigation/tsconfig.json @@ -7,6 +7,8 @@ "node", "react", "@emotion/react/types/css-prop", + "@testing-library/jest-dom", + "@testing-library/react", "@kbn/ambient-ui-types" ] }, @@ -17,10 +19,9 @@ "kbn_references": [ "@kbn/core-application-browser", "@kbn/core-http-browser", - "@kbn/shared-ux-storybook-mock", + "@kbn/shared-ux-storybook-mock" ], "exclude": [ "target/**/*" ] } - diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index 66f434d8b986d..4a3ee53bbb903 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -15,7 +15,7 @@ import { BasePathService, NavigateToUrlFn, NavItemClickFn, RecentItem } from './ * @public */ export interface NavigationServices { - activeNavItemId: string | undefined; + activeNavItemId?: string; basePath: BasePathService; loadingCount: number; navIsOpen: boolean; From 1df05eef628509190dc9af93c46134159d840e4a Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Sat, 22 Apr 2023 00:26:03 -0700 Subject: [PATCH 26/41] Final nav items config fixes --- .../src/model/platform_nav/machine_learning.ts | 6 +++--- .../src/model/platform_nav/management.ts | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts index 0d77a370e9412..692bb704dca17 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/machine_learning.ts @@ -98,7 +98,7 @@ export const mlItemSet: NavItemProps[] = [ href: '/app/ml/filedatavisualizer', }, { - name: 'Data page', + name: 'Data view', id: 'data_view', href: '/app/ml/datavisualizer_index_select', }, @@ -111,12 +111,12 @@ export const mlItemSet: NavItemProps[] = [ { name: 'Explain log rate spikes', id: 'explain_log_rate_spikes', - href: '/app/ml/explain_log_rate_spikes_index_select', + href: '/app/ml/aiops/explain_log_rate_spikes_index_select', }, { name: 'Log pattern analysis', id: 'log_pattern_analysis', - href: '/app/ml/log_categorization_index_select', + href: '/app/ml/aiops/log_categorization_index_select', }, ], }, diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts index 94bfad3f8e2fc..c1b09258d1c3b 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/management.ts @@ -73,17 +73,17 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Index lifecycle policies', id: 'index_lifecycle_policies', - // locator: { id: LocatorId.ILM }, + href: '/app/management/data/index_lifecycle_management', }, { name: 'Snapshot and restore', id: 'snapshot_and_restore', - // locator: { id: LocatorId.SnapshotRestore }, + href: 'app/management/data/snapshot_restore', }, { name: 'Rollup jobs', id: 'rollup_jobs', - // locator: { id: LocatorId.Rollup }, + href: '/app/management/data/rollup_jobs', }, { name: 'Transforms', @@ -93,12 +93,12 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Cross-cluster replication', id: 'cross_cluster_replication', - // locator: { id: LocatorId.CrossClusterReplication }, + href: '/app/management/data/cross_cluster_replication', }, { name: 'Remote clusters', id: 'remote_clusters', - // locator: { id: LocatorId.RemoteClusters }, + href: '/app/management/data/remote_clusters', }, ], }, @@ -124,7 +124,7 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Reporting', id: 'reporting', - // locator: { id: LocatorId.Reporting }, + href: '/app/management/insightsAndAlerting/reporting', }, { name: 'Machine learning', @@ -134,7 +134,7 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Watcher', id: 'watcher', - // locator: { id: LocatorId.Watcher }, + href: '/app/management/insightsAndAlerting/watcher', }, ], }, @@ -203,7 +203,7 @@ export const managementItemSet: NavItemProps[] = [ { name: 'Upgrade assistant', id: 'upgrade_assistant', - // locator: { id: LocatorId.UpgradeAssistant }, + href: '/app/management/stack/upgrade_assistant', }, ], }, From 044d7683cb8eab6fb4167fb025004d70eedaf42b Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Mon, 24 Apr 2023 22:33:36 -0700 Subject: [PATCH 27/41] remove stale fixme comment --- packages/shared-ux/chrome/navigation/src/ui/navigation.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index b7a9b79547950..3be7356e3c872 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -106,7 +106,6 @@ export const Navigation = (props: NavigationProps) => { }; const LinkToCloud = () => { - // FIXME: link directly to the current project or deployment switch (props.linkToCloud) { case 'projects': return ( From 36c17e0e3718ca3f051f7909e62a7f4080d21cf2 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Mon, 24 Apr 2023 22:38:21 -0700 Subject: [PATCH 28/41] remove descoped recent items integration --- .../chrome/navigation/src/model/model.ts | 10 -------- .../chrome/navigation/src/services.tsx | 7 +----- .../chrome/navigation/src/ui/navigation.tsx | 24 ------------------- 3 files changed, 1 insertion(+), 40 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/model/model.ts b/packages/shared-ux/chrome/navigation/src/model/model.ts index b7c81fc013781..0d3e3266be780 100644 --- a/packages/shared-ux/chrome/navigation/src/model/model.ts +++ b/packages/shared-ux/chrome/navigation/src/model/model.ts @@ -31,7 +31,6 @@ export class NavigationModel { constructor( deps: NavigationModelDeps, - private recentItems: Array> | undefined, private platformConfig: NavigationProps['platformConfig'] | undefined, private solutions: SolutionProperties[], activeNavItemId: string | undefined @@ -39,15 +38,6 @@ export class NavigationModel { this.createSideNavData = createSideNavDataFactory(deps, activeNavItemId); } - public getRecent(): NavigationBucketProps { - return { - id: 'recent', - icon: 'clock', - name: 'Recent', - items: this.recentItems, - }; - } - private convertToSideNavItems( id: string, items: NavItemProps[] | undefined, diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index 74102469ca7e3..18b723d0cda19 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -31,11 +31,6 @@ export const NavigationKibanaProvider: FC = ({ const { basePath } = http; const { navigateToUrl } = core.application; - // FIXME - // const recentItems$ = chrome.recentlyAccessed.get$(); - // const recentItems = useObservable(recentItems$, []); - const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; - const navIsOpen = useObservable(chrome.getProjectNavIsOpen$(), true); const activeNavItemId = useObservable(chrome.getActiveNavItemId$()); const loadingCount = useObservable(http.getLoadingCount$(), 0); @@ -48,7 +43,7 @@ export const NavigationKibanaProvider: FC = ({ loadingCount, navIsOpen, navigateToUrl, - recentItems, + recentItems: [], // currently not implemented: https://github.com/elastic/kibana/issues/154488 registerNavItemClick, }; diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index b7a9b79547950..a1a5592e7ef8a 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -13,7 +13,6 @@ import { EuiHeaderLogo, EuiLink, EuiLoadingSpinner, - EuiSideNavItemType, EuiSpacer, useEuiTheme, } from '@elastic/eui'; @@ -31,42 +30,21 @@ export const Navigation = (props: NavigationProps) => { // const { fontSize: navItemFontSize } = useEuiFontSize('s'); const { - recentItems: recentItemsFromService, loadingCount, activeNavItemId, ...services } = useNavigation(); const { euiTheme } = useEuiTheme(); - let recentItems: Array> | undefined; - if (recentItemsFromService) { - recentItems = [ - { - name: '', - id: 'recent_items_root', - items: recentItemsFromService.map((item) => ({ - id: item.id, - name: item.label, - onClick: () => { - // FIXME not implemented - // console.log(`Go to ${item.link}`); - }, - })), - }, - ]; - } - const activeNav = activeNavItemId ?? props.activeNavItemId; const nav = new NavigationModel( services, - recentItems, props.platformConfig, props.solutions, activeNav ); - const recent = nav.getRecent(); const solutions = nav.getSolutions(); const { analytics, ml, devTools, management } = nav.getPlatform(); @@ -147,8 +125,6 @@ export const Navigation = (props: NavigationProps) => { - {recentItems ? : null} - {solutions.map((solutionBucket, idx) => { return ; })} From e1dfb328361d022f07863aef478676da47e24793 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Mon, 24 Apr 2023 22:41:43 -0700 Subject: [PATCH 29/41] fix commented code for investigating dark bg --- .../chrome/navigation/src/ui/navigation.tsx | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index 82bc80db0e04a..47921c1848383 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -25,25 +25,12 @@ import './header_logo.scss'; import { NavigationBucket } from './navigation_bucket'; export const Navigation = (props: NavigationProps) => { - // const { euiTheme } = useEuiTheme(); - // const { fontSize: navSectionFontSize } = useEuiFontSize('m'); - // const { fontSize: navItemFontSize } = useEuiFontSize('s'); - - const { - loadingCount, - activeNavItemId, - ...services - } = useNavigation(); + const { loadingCount, activeNavItemId, ...services } = useNavigation(); const { euiTheme } = useEuiTheme(); const activeNav = activeNavItemId ?? props.activeNavItemId; - const nav = new NavigationModel( - services, - props.platformConfig, - props.solutions, - activeNav - ); + const nav = new NavigationModel(services, props.platformConfig, props.solutions, activeNav); const solutions = nav.getSolutions(); const { analytics, ml, devTools, management } = nav.getPlatform(); From 902759ab646736de1f18af33fd5b91074bc87da3 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Mon, 24 Apr 2023 22:44:32 -0700 Subject: [PATCH 30/41] more recent items cleanup --- packages/shared-ux/chrome/navigation/src/services.tsx | 1 - packages/shared-ux/chrome/navigation/types/index.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index 18b723d0cda19..cda95ad05e230 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -43,7 +43,6 @@ export const NavigationKibanaProvider: FC = ({ loadingCount, navIsOpen, navigateToUrl, - recentItems: [], // currently not implemented: https://github.com/elastic/kibana/issues/154488 registerNavItemClick, }; diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index 4a3ee53bbb903..196f8297b02b2 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -20,7 +20,6 @@ export interface NavigationServices { loadingCount: number; navIsOpen: boolean; navigateToUrl: NavigateToUrlFn; - recentItems: RecentItem[]; registerNavItemClick: NavItemClickFn; } From e45301d7b16eaa681b4c1d9e876b7e82bc11b49f Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 25 Apr 2023 07:00:53 -0700 Subject: [PATCH 31/41] more ts fix --- packages/shared-ux/chrome/navigation/mocks/src/jest.ts | 2 -- .../shared-ux/chrome/navigation/mocks/src/storybook.ts | 5 ++--- .../chrome/navigation/src/ui/navigation.stories.tsx | 8 -------- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index 00be975cf5349..6af9404792e99 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -9,7 +9,6 @@ import { NavigationServices, SolutionProperties } from '../../types'; export const getServicesMock = (): NavigationServices => { - const recentItems = [{ label: 'This is a test', id: 'test', link: 'legendOfZelda' }]; const navigateToUrl = jest.fn().mockResolvedValue(undefined); const basePath = { prepend: jest.fn((path: string) => `/base${path}`) }; const registerNavItemClick = jest.fn(); @@ -20,7 +19,6 @@ export const getServicesMock = (): NavigationServices => { loadingCount, navIsOpen: true, navigateToUrl, - recentItems, registerNavItemClick, }; }; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index 5cdc3db09143c..5c8faaa186802 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -14,7 +14,7 @@ import { NavigationProps, NavigationServices } from '../../types'; type Arguments = NavigationProps & NavigationServices; export type Params = Pick< Arguments, - 'activeNavItemId' | 'loadingCount' | 'navIsOpen' | 'platformConfig' | 'recentItems' | 'solutions' + 'activeNavItemId' | 'loadingCount' | 'navIsOpen' | 'platformConfig' | 'solutions' >; export class StorybookMock extends AbstractStorybookMock { @@ -34,7 +34,7 @@ export class StorybookMock extends AbstractStorybookMock { @@ -54,7 +54,6 @@ export class StorybookMock extends AbstractStorybookMock `/basepath${suffix}` }, navigateToUrl, navIsOpen, - recentItems, registerNavItemClick, }; } diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx index 1a2618a2745a0..1ec82e4ede257 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx @@ -124,14 +124,6 @@ ReducedPlatformLinks.args = { }; ReducedPlatformLinks.argTypes = storybookMock.getArgumentTypes(); -export const WithRecentItems: ComponentStory = Template.bind({}); -WithRecentItems.args = { - activeNavItemId: 'example_project.root.get_started', - recentItems: [{ id: 'recent_1', label: 'This is a test recent link', link: 'testo' }], - solutions: [getSolutionPropertiesMock()], -}; -WithRecentItems.argTypes = storybookMock.getArgumentTypes(); - export const WithRequestsLoading: ComponentStory = Template.bind({}); WithRequestsLoading.args = { activeNavItemId: 'example_project.root.get_started', From 07f26d82340d753a385135e92bac59e82065d307 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 25 Apr 2023 07:02:32 -0700 Subject: [PATCH 32/41] fix todo --- packages/shared-ux/chrome/navigation/README.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared-ux/chrome/navigation/README.mdx b/packages/shared-ux/chrome/navigation/README.mdx index cf03dbdc4c1b1..c2e04ebbeb98f 100644 --- a/packages/shared-ux/chrome/navigation/README.mdx +++ b/packages/shared-ux/chrome/navigation/README.mdx @@ -11,7 +11,7 @@ date: 2023-02-28 Empty package generated by @kbn/generate @kbn/shared-ux-chrome-navigation -TODO tsullivan +Navigation container to render items for cross-app linking ## API From 99af20a7188282e547f712561125368dc9dbc4da Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 25 Apr 2023 11:28:57 -0700 Subject: [PATCH 33/41] add initial translatable strings --- .../chrome/navigation/src/ui/i18n_strings.ts | 24 +++++++++++++++++++ .../chrome/navigation/src/ui/navigation.tsx | 9 ++++--- 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts diff --git a/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts b/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts new file mode 100644 index 0000000000000..13378546848fb --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +export const getI18nStrings = () => { + const prefix = 'sharedUXPackages.chrome.sideNavigation'; + return { + headerLogoAriaText: i18n.translate(`${prefix}.headerLogo.ariaText`, { + defaultMessage: 'Go to home page', + }), + linkToCloudProjects: i18n.translate(`${prefix}.linkToCloud.projects`, { + defaultMessage: 'My projects', + }), + linkToCloudDeployments: i18n.translate(`${prefix}.linkToCloud.deployments`, { + defaultMessage: 'My deployments', + }), + }; +}; diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index 47921c1848383..d981e18741a0b 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -17,6 +17,7 @@ import { useEuiTheme, } from '@elastic/eui'; import React from 'react'; +import { getI18nStrings } from './i18n_strings'; import { NavigationBucketProps, NavigationProps } from '../../types'; import { NavigationModel } from '../model'; import { useNavigation } from '../services'; @@ -35,6 +36,8 @@ export const Navigation = (props: NavigationProps) => { const solutions = nav.getSolutions(); const { analytics, ml, devTools, management } = nav.getPlatform(); + const strings = getI18nStrings(); + const NavHeader = () => { const homeUrl = services.basePath.prepend(props.homeHref); const navigateHome = (event: React.MouseEvent) => { @@ -45,7 +48,7 @@ export const Navigation = (props: NavigationProps) => { loadingCount === 0 ? ( @@ -79,7 +82,7 @@ export const Navigation = (props: NavigationProps) => { color="text" data-test-subj="nav-header-link-to-projects" > - + ); case 'deployments': @@ -89,7 +92,7 @@ export const Navigation = (props: NavigationProps) => { color="text" data-test-subj="nav-header-link-to-deployments" > - + ); default: From ceab9d705bf2d77422777f4deaae94031e211da2 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 25 Apr 2023 18:49:10 +0000 Subject: [PATCH 34/41] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/shared-ux/chrome/navigation/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/shared-ux/chrome/navigation/tsconfig.json b/packages/shared-ux/chrome/navigation/tsconfig.json index b7f65599303af..05fd9d0346421 100644 --- a/packages/shared-ux/chrome/navigation/tsconfig.json +++ b/packages/shared-ux/chrome/navigation/tsconfig.json @@ -19,7 +19,8 @@ "kbn_references": [ "@kbn/core-application-browser", "@kbn/core-http-browser", - "@kbn/shared-ux-storybook-mock" + "@kbn/shared-ux-storybook-mock", + "@kbn/i18n" ], "exclude": [ "target/**/*" From 62f40d239efdf083b9d7caa3a8c940a436aae5b9 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 25 Apr 2023 14:06:33 -0700 Subject: [PATCH 35/41] fix i18n issue --- .../chrome/navigation/src/ui/i18n_strings.ts | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts b/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts index 13378546848fb..9f6f3fbadca30 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts +++ b/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts @@ -8,17 +8,23 @@ import { i18n } from '@kbn/i18n'; -export const getI18nStrings = () => { - const prefix = 'sharedUXPackages.chrome.sideNavigation'; - return { - headerLogoAriaText: i18n.translate(`${prefix}.headerLogo.ariaText`, { +export const getI18nStrings = () => ({ + headerLogoAriaLabel: i18n.translate( + 'sharedUXPackages.chrome.sideNavigation.headerLogo.ariaLabel', + { defaultMessage: 'Go to home page', - }), - linkToCloudProjects: i18n.translate(`${prefix}.linkToCloud.projects`, { + } + ), + linkToCloudProjects: i18n.translate( + 'sharedUXPackages.chrome.sideNavigation.linkToCloud.projects', + { defaultMessage: 'My projects', - }), - linkToCloudDeployments: i18n.translate(`${prefix}.linkToCloud.deployments`, { + } + ), + linkToCloudDeployments: i18n.translate( + 'sharedUXPackages.chrome.sideNavigation.linkToCloud.deployments', + { defaultMessage: 'My deployments', - }), - }; -}; + } + ), +}); From a3acb9005a1f956f8517739d0d058dabeeabab5c Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 26 Apr 2023 14:15:31 -0700 Subject: [PATCH 36/41] fix ts --- packages/shared-ux/chrome/navigation/src/ui/navigation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index d981e18741a0b..60f3af10a6b54 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -48,7 +48,7 @@ export const Navigation = (props: NavigationProps) => { loadingCount === 0 ? ( From d1cd5512b8bbb4eaf29297a48fda046f474bb4d9 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 26 Apr 2023 17:51:32 -0700 Subject: [PATCH 37/41] Remove services that will not be needed for future iteration --- packages/shared-ux/chrome/navigation/mocks/src/jest.ts | 2 -- .../shared-ux/chrome/navigation/mocks/src/storybook.ts | 9 --------- .../chrome/navigation/src/model/create_side_nav.ts | 3 +-- .../shared-ux/chrome/navigation/src/model/index.ts | 3 +-- packages/shared-ux/chrome/navigation/src/services.tsx | 10 ++-------- packages/shared-ux/chrome/navigation/types/index.ts | 6 +----- packages/shared-ux/chrome/navigation/types/internal.ts | 5 ----- 7 files changed, 5 insertions(+), 33 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index 6af9404792e99..166dc73b6290c 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -11,7 +11,6 @@ import { NavigationServices, SolutionProperties } from '../../types'; export const getServicesMock = (): NavigationServices => { const navigateToUrl = jest.fn().mockResolvedValue(undefined); const basePath = { prepend: jest.fn((path: string) => `/base${path}`) }; - const registerNavItemClick = jest.fn(); const loadingCount = 0; return { @@ -19,7 +18,6 @@ export const getServicesMock = (): NavigationServices => { loadingCount, navIsOpen: true, navigateToUrl, - registerNavItemClick, }; }; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index 5c8faaa186802..27e27393dc5bb 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -8,7 +8,6 @@ import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock'; import { action } from '@storybook/addon-actions'; -import { BehaviorSubject } from 'rxjs'; import { NavigationProps, NavigationServices } from '../../types'; type Arguments = NavigationProps & NavigationServices; @@ -42,19 +41,11 @@ export class StorybookMock extends AbstractStorybookMock(undefined); - const registerNavItemClick = (id: string) => { - activeNavItemId$.next(id); - registerNavItemClickAction(id); - }; - return { ...params, basePath: { prepend: (suffix: string) => `/basepath${suffix}` }, navigateToUrl, navIsOpen, - registerNavItemClick, }; } diff --git a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts index 45eac74cf9964..aac2fdf346ffc 100644 --- a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts +++ b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts @@ -21,7 +21,7 @@ export const createSideNavDataFactory = ( deps: NavigationModelDeps, activeNavItemId: string | undefined ) => { - const { basePath, navigateToUrl, registerNavItemClick } = deps; + const { basePath, navigateToUrl } = deps; const createSideNavData = ( parentIds: string | number = '', navItems: NavItemProps[], @@ -43,7 +43,6 @@ export const createSideNavDataFactory = ( onClick = (event: React.MouseEvent) => { event.preventDefault(); navigateToUrl(basePath.prepend(href)); - registerNavItemClick(fullId); }; } diff --git a/packages/shared-ux/chrome/navigation/src/model/index.ts b/packages/shared-ux/chrome/navigation/src/model/index.ts index e8736753780e8..8e8d94e995019 100644 --- a/packages/shared-ux/chrome/navigation/src/model/index.ts +++ b/packages/shared-ux/chrome/navigation/src/model/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { BasePathService, NavigateToUrlFn, NavItemClickFn } from '../../types/internal'; +import { BasePathService, NavigateToUrlFn } from '../../types/internal'; import { analyticsItemSet } from './platform_nav/analytics'; import { devtoolsItemSet } from './platform_nav/devtools'; import { mlItemSet } from './platform_nav/machine_learning'; @@ -15,7 +15,6 @@ import { managementItemSet } from './platform_nav/management'; export interface NavigationModelDeps { basePath: BasePathService; navigateToUrl: NavigateToUrlFn; - registerNavItemClick: NavItemClickFn; } /** diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index cda95ad05e230..8235963c18681 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -27,23 +27,17 @@ export const NavigationKibanaProvider: FC = ({ ...dependencies }) => { const { core } = dependencies; - const { chrome, http } = core; + const { http } = core; const { basePath } = http; const { navigateToUrl } = core.application; - const navIsOpen = useObservable(chrome.getProjectNavIsOpen$(), true); - const activeNavItemId = useObservable(chrome.getActiveNavItemId$()); const loadingCount = useObservable(http.getLoadingCount$(), 0); - const registerNavItemClick = chrome.registerNavItemClick; - const value: NavigationServices = { - activeNavItemId, basePath, loadingCount, - navIsOpen, navigateToUrl, - registerNavItemClick, + navIsOpen: true, }; return ( diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index 196f8297b02b2..bd7aaddcb67c6 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -8,7 +8,7 @@ import type { EuiSideNavItemType, IconType } from '@elastic/eui'; import { Observable } from 'rxjs'; -import { BasePathService, NavigateToUrlFn, NavItemClickFn, RecentItem } from './internal'; +import { BasePathService, NavigateToUrlFn, RecentItem } from './internal'; /** * A list of services that are consumed by this component. @@ -20,7 +20,6 @@ export interface NavigationServices { loadingCount: number; navIsOpen: boolean; navigateToUrl: NavigateToUrlFn; - registerNavItemClick: NavItemClickFn; } /** @@ -32,10 +31,7 @@ export interface NavigationKibanaDependencies { core: { application: { navigateToUrl: NavigateToUrlFn }; chrome: { - getActiveNavItemId$: () => Observable; - getProjectNavIsOpen$: () => Observable; recentlyAccessed: { get$: () => Observable }; - registerNavItemClick: (id: string) => void; }; http: { basePath: BasePathService; diff --git a/packages/shared-ux/chrome/navigation/types/internal.ts b/packages/shared-ux/chrome/navigation/types/internal.ts index 41770532b7b72..6808c391073a9 100644 --- a/packages/shared-ux/chrome/navigation/types/internal.ts +++ b/packages/shared-ux/chrome/navigation/types/internal.ts @@ -9,11 +9,6 @@ import type { ApplicationStart } from '@kbn/core-application-browser'; import type { IBasePath } from '@kbn/core-http-browser'; -/** - * @internal - */ -export type NavItemClickFn = (id: string) => void; - /** * @internal */ From f9d53f8b5db164a5563b63757f1d1b152e63af92 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 27 Apr 2023 09:45:45 -0700 Subject: [PATCH 38/41] Implement dark background --- .../navigation/src/ui/navigation.stories.tsx | 98 ++++++++++--------- .../chrome/navigation/src/ui/navigation.tsx | 4 +- .../navigation/src/ui/navigation_bucket.tsx | 7 +- 3 files changed, 60 insertions(+), 49 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx index 1ec82e4ede257..838ad84689e07 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx @@ -12,10 +12,11 @@ import { EuiCollapsibleNav, EuiPopover, EuiThemeProvider, + useEuiTheme, } from '@elastic/eui'; -import { action } from '@storybook/addon-actions'; import { ComponentMeta, ComponentStory } from '@storybook/react'; -import React from 'react'; +import React, { useCallback, useState } from 'react'; +import { css } from '@emotion/react'; import { getSolutionPropertiesMock, NavigationStorybookMock } from '../../mocks'; import mdx from '../../README.mdx'; import { NavigationProps, NavigationServices } from '../../types'; @@ -24,57 +25,66 @@ import { NavigationProvider } from '../services'; import { Navigation as Component } from './navigation'; const storybookMock = new NavigationStorybookMock(); -let colorMode = ''; -colorMode = 'LIGHT'; + +const SIZE_OPEN = 248; +const SIZE_CLOSED = 40; const Template = (args: NavigationProps & NavigationServices) => { const services = storybookMock.getServices(args); const props = storybookMock.getProps(args); - const { navIsOpen } = services; - const collapseAction = action('setNavIsOpen'); + const { euiTheme, colorMode } = useEuiTheme(); + + const [isOpen, setIsOpen] = useState(true); + + const toggleOpen = useCallback(() => { + setIsOpen(!isOpen); + }, [isOpen, setIsOpen]); + + const collabsibleNavCSS = css` + border-inline-end-width: 1, + background: ${euiTheme.colors.darkestShade}, + display: flex, + flex-direction: row, + `; - const setNavIsOpen = (value: boolean) => { - collapseAction(value); + const CollapseButton = () => { + const buttonCSS = css` + margin-left: -32px; + margin-top: 12px; + position: fixed; + z-index: 1000; + `; + return ( + + + + ); }; return ( - - - { - setNavIsOpen(!navIsOpen); - }} - /> - - - } - onClose={() => { - setNavIsOpen(false); - }} - > - - - - + + } + > + {isOpen && ( + + + + )} + + ); }; diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index 60f3af10a6b54..8dd19b9f3dff0 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -14,7 +14,6 @@ import { EuiLink, EuiLoadingSpinner, EuiSpacer, - useEuiTheme, } from '@elastic/eui'; import React from 'react'; import { getI18nStrings } from './i18n_strings'; @@ -27,7 +26,6 @@ import { NavigationBucket } from './navigation_bucket'; export const Navigation = (props: NavigationProps) => { const { loadingCount, activeNavItemId, ...services } = useNavigation(); - const { euiTheme } = useEuiTheme(); const activeNav = activeNavItemId ?? props.activeNavItemId; @@ -108,7 +106,7 @@ export const Navigation = (props: NavigationProps) => { return ( - + diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx index cbcf82e81a894..f3d9433bea2d4 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { EuiCollapsibleNavGroup, EuiIcon, EuiSideNav } from '@elastic/eui'; +import { EuiCollapsibleNavGroup, EuiIcon, EuiSideNav, EuiText } from '@elastic/eui'; import React from 'react'; import { NavigationBucketProps } from '../../types'; import { useNavigation } from '../services'; @@ -19,6 +19,7 @@ export const NavigationBucket = (opts: NavigationBucketProps) => { if (navIsOpen) { return ( { initialIsOpen={activeNavItemId?.startsWith(id + '.')} data-test-subj={`nav-bucket-${id}`} > - + + + ); } From 76427ee7d9fb4d32210a5bbb7e7e32c139f7b6ea Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 27 Apr 2023 10:25:31 -0700 Subject: [PATCH 39/41] use light mode for now --- .../chrome/navigation/src/ui/navigation.stories.tsx | 9 ++------- .../shared-ux/chrome/navigation/src/ui/navigation.tsx | 4 +++- .../chrome/navigation/src/ui/navigation_bucket.tsx | 1 - 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx index 838ad84689e07..a69e383909fbf 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx @@ -12,7 +12,6 @@ import { EuiCollapsibleNav, EuiPopover, EuiThemeProvider, - useEuiTheme, } from '@elastic/eui'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import React, { useCallback, useState } from 'react'; @@ -33,8 +32,6 @@ const Template = (args: NavigationProps & NavigationServices) => { const services = storybookMock.getServices(args); const props = storybookMock.getProps(args); - const { euiTheme, colorMode } = useEuiTheme(); - const [isOpen, setIsOpen] = useState(true); const toggleOpen = useCallback(() => { @@ -43,7 +40,6 @@ const Template = (args: NavigationProps & NavigationServices) => { const collabsibleNavCSS = css` border-inline-end-width: 1, - background: ${euiTheme.colors.darkestShade}, display: flex, flex-direction: row, `; @@ -51,7 +47,6 @@ const Template = (args: NavigationProps & NavigationServices) => { const CollapseButton = () => { const buttonCSS = css` margin-left: -32px; - margin-top: 12px; position: fixed; z-index: 1000; `; @@ -59,7 +54,7 @@ const Template = (args: NavigationProps & NavigationServices) => { @@ -67,7 +62,7 @@ const Template = (args: NavigationProps & NavigationServices) => { }; return ( - + { const { loadingCount, activeNavItemId, ...services } = useNavigation(); + const { euiTheme } = useEuiTheme(); const activeNav = activeNavItemId ?? props.activeNavItemId; @@ -106,7 +108,7 @@ export const Navigation = (props: NavigationProps) => { return ( - + diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx index f3d9433bea2d4..0b0d6b5b223ab 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx @@ -19,7 +19,6 @@ export const NavigationBucket = (opts: NavigationBucketProps) => { if (navIsOpen) { return ( Date: Thu, 27 Apr 2023 10:26:52 -0700 Subject: [PATCH 40/41] remove in-progress work on collapsed view --- .../chrome/navigation/src/ui/navigation.stories.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx index a69e383909fbf..20c0c2201acfb 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx @@ -165,11 +165,3 @@ CustomElements.args = { ], }; CustomElements.argTypes = storybookMock.getArgumentTypes(); - -export const Collapsed: ComponentStory = Template.bind({}); -Collapsed.args = { - activeNavItemId: 'example_project.root.get_started', - navIsOpen: false, - solutions: [getSolutionPropertiesMock()], -}; -Collapsed.argTypes = storybookMock.getArgumentTypes(); From 5fbb3fc29925b5a1b1c7ca00965f3821aa3ef887 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 27 Apr 2023 14:15:12 -0700 Subject: [PATCH 41/41] fix link to dashboards app --- .../chrome/navigation/src/model/platform_nav/analytics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts b/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts index 586ea872326e7..4e52e8c161bbd 100644 --- a/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts +++ b/packages/shared-ux/chrome/navigation/src/model/platform_nav/analytics.ts @@ -21,7 +21,7 @@ export const analyticsItemSet: NavItemProps[] = [ { name: 'Dashboard', id: 'dashboard', - href: '/app/dashboard', + href: '/app/dashboards', }, { name: 'Visualize Library',