Skip to content

Commit

Permalink
[SharedUx] Chrome/Navigation package (#152510)
Browse files Browse the repository at this point in the history
**The API for the link definition is subject to change.** This PR
provides linking functionality and structure that will give solution
devs a starting point for side nav in their projects. The API uses
simple hrefs for now, because it's the easiest thing to start with.
Planning forward, we are thinking of a different navigation model that
separates nav structure from presentation of each nav item - this will
allow reuse of the structure without reusing the presentation.

## Isolated dependencies
In order for this component to be usable in `main`, a bit further work
is currently required in the ChromeStart service and the Serverless
plugin. These links are examples of a usable implementation that link to
a POC branch:
* Light mode for the side nav container:
tsullivan@0a32333cdf0
* Extensions to Serverless plugin and the ChromeStart service:
tsullivan@16b0aad610f

## Summary

Introduces a component to host the side navigation in Kibana. Solution
teams can insert their own content, and have other small options to
customize the presentation: see the storybook demos for more.

Closes #154479
Closes #154484
Closes #154485
~~Closes #154489
Closes #154481
Closes #154480
Closes #154486
Closes #154487

<img width="1462" alt="image"
src="https://user-images.githubusercontent.com/908371/233739506-7b88646e-3c19-44f0-afbf-ca1a23d32c57.png">

## Developer documentation

See the Storybook demos:
* run: `yarn storybook shared_ux`
* Find the `Chrome > Navigation` section in the Storybook app

### Checklist

Delete any items that are not applicable to this PR.
- [x] Home icon links to Project's "home" - or the customer user setting
- [x] Home icon shows loading indicator
- [x] All the Platform links navigate to the correct place
- ~~Platform links are not shown if the underlying plugin is disabled~~
- [x] Nav items define their links using `href` only
- [x] All href links work
- [x] Nav menu item to link to Cloud deployment
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
tsullivan and kibanamachine authored Apr 28, 2023
1 parent 11a80ce commit 604a02f
Show file tree
Hide file tree
Showing 31 changed files with 1,637 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,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
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,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",
Expand Down
26 changes: 26 additions & 0 deletions packages/shared-ux/chrome/navigation/README.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
id: sharedUX/Chrome/Navigation
slug: /shared-ux/chrome/navigation
title: Kibana Chrome Navigation
description: Navigation container to render items for cross-app linking
tags: ['shared-ux', 'component', 'chrome', 'navigation']
date: 2023-02-28
---

## Description

Empty package generated by @kbn/generate
@kbn/shared-ux-chrome-navigation
Navigation container to render items for cross-app linking

## 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.
11 changes: 11 additions & 0 deletions packages/shared-ux/chrome/navigation/index.ts
Original file line number Diff line number Diff line change
@@ -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 { NavigationKibanaProvider, NavigationProvider } from './src/services';
export { Navigation } from './src/ui/navigation';
export type { NavigationProps, NavigationServices, NavItemProps } from './types';
13 changes: 13 additions & 0 deletions packages/shared-ux/chrome/navigation/jest.config.js
Original file line number Diff line number Diff line change
@@ -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: ['<rootDir>/packages/shared-ux/chrome/navigation'],
};
5 changes: 5 additions & 0 deletions packages/shared-ux/chrome/navigation/kibana.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/shared-ux-chrome-navigation",
"owner": "@elastic/appex-sharedux"
}
14 changes: 14 additions & 0 deletions packages/shared-ux/chrome/navigation/mocks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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,
getSolutionPropertiesMock,
} from './src/jest';
export { StorybookMock as NavigationStorybookMock } from './src/storybook';
export type { Params as NavigationStorybookParams } from './src/storybook';
72 changes: 72 additions & 0 deletions packages/shared-ux/chrome/navigation/mocks/src/jest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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, SolutionProperties } from '../../types';

export const getServicesMock = (): NavigationServices => {
const navigateToUrl = jest.fn().mockResolvedValue(undefined);
const basePath = { prepend: jest.fn((path: string) => `/base${path}`) };
const loadingCount = 0;

return {
basePath,
loadingCount,
navIsOpen: true,
navigateToUrl,
};
};

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',
},
],
},
],
});
59 changes: 59 additions & 0 deletions packages/shared-ux/chrome/navigation/mocks/src/storybook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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 { action } from '@storybook/addon-actions';
import { NavigationProps, NavigationServices } from '../../types';

type Arguments = NavigationProps & NavigationServices;
export type Params = Pick<
Arguments,
'activeNavItemId' | 'loadingCount' | 'navIsOpen' | 'platformConfig' | 'solutions'
>;

export class StorybookMock extends AbstractStorybookMock<NavigationProps, NavigationServices> {
propArguments = {};

serviceArguments = {
navIsOpen: {
control: 'boolean',
defaultValue: true,
},
loadingCount: {
control: 'number',
defaultValue: 0,
},
};

dependencies = [];

getServices(params: Params): NavigationServices {
const { navIsOpen } = params;

const navAction = action('Navigate to');
const navigateToUrl = (url: string) => {
navAction(url);
return Promise.resolve();
};

return {
...params,
basePath: { prepend: (suffix: string) => `/basepath${suffix}` },
navigateToUrl,
navIsOpen,
};
}

getProps(params: Params): NavigationProps {
return {
...params,
homeHref: '#',
linkToCloud: 'projects',
};
}
}
6 changes: 6 additions & 0 deletions packages/shared-ux/chrome/navigation/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
77 changes: 77 additions & 0 deletions packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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';

type MyEuiSideNavItem = EuiSideNavItemType<unknown>;
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 } = deps;
const createSideNavData = (
parentIds: string | number = '',
navItems: NavItemProps[],
platformSectionConfig?: PlatformSectionConfig
): Array<EuiSideNavItemType<unknown>> =>
navItems.reduce<MyEuiSideNavItem[]>((accum, 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;
}

let onClick: OnClickFn | undefined;

const fullId = [parentIds, id].filter(Boolean).join('.');

if (href) {
onClick = (event: React.MouseEvent) => {
event.preventDefault();
navigateToUrl(basePath.prepend(href));
};
}

let filteredSubNav: MyEuiSideNavItem[] | undefined;
if (subNav) {
// recursion
const nextConfig = platformSectionConfig?.properties?.[id];
filteredSubNav = createSideNavData(fullId, subNav, nextConfig);
}

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 = {
id: fullId,
name,
isSelected,
onClick,
href,
items: filteredSubNav,
['data-test-subj']: `nav-item-${subjId}`,
};
return [...accum, next];
}, []);

return createSideNavData;
};
41 changes: 41 additions & 0 deletions packages/shared-ux/chrome/navigation/src/model/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 { BasePathService, NavigateToUrlFn } 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';

export interface NavigationModelDeps {
basePath: BasePathService;
navigateToUrl: NavigateToUrlFn;
}

/**
* @public
*/
export enum Platform {
Recents = 'recents',
Analytics = 'analytics',
MachineLearning = 'ml',
DevTools = 'devTools',
Management = 'management',
}

/**
* @public
*/
export const navItemSet = {
[Platform.Analytics]: analyticsItemSet,
[Platform.MachineLearning]: mlItemSet,
[Platform.DevTools]: devtoolsItemSet,
[Platform.Management]: managementItemSet,
};

export { NavigationModel } from './model';
Loading

0 comments on commit 604a02f

Please sign in to comment.