From ec620e7fb304da02492f32c137f1f90e5e6baea7 Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Fri, 30 Jun 2023 11:27:56 +0200 Subject: [PATCH] [Deployment Management] Add cards navigation in management landing page for serverless (#160096) --- .../steps/storybooks/build_and_upload.ts | 1 + .github/CODEOWNERS | 2 + .i18nrc.json | 2 +- docs/developer/plugin-list.asciidoc | 2 +- package.json | 2 + .../cards_navigation/README.mdx | 41 +++++ .../kbn-management/cards_navigation/index.ts | 12 ++ .../cards_navigation/jest.config.js | 13 ++ .../cards_navigation/kibana.jsonc | 5 + .../cards_navigation/mocks/mocks.ts | 90 +++++++++ .../cards_navigation/mocks/storybook.mock.ts | 19 ++ .../cards_navigation/package.json | 6 + .../src/cards_navigation.stories.tsx | 36 ++++ .../src/cards_navigation.test.tsx | 73 ++++++++ .../cards_navigation/src/cards_navigation.tsx | 146 +++++++++++++++ .../cards_navigation/src/consts.tsx | 171 ++++++++++++++++++ .../cards_navigation/src/index.ts | 13 ++ .../cards_navigation/src/types.ts | 42 +++++ .../cards_navigation/tsconfig.json | 22 +++ .../storybook/config/README.mdx | 5 + .../storybook/config/constants.ts | 13 ++ .../kbn-management/storybook/config/index.ts | 9 + .../storybook/config/kibana.jsonc | 6 + .../kbn-management/storybook/config/main.ts | 17 ++ .../storybook/config/manager.ts | 23 +++ .../storybook/config/package.json | 6 + .../storybook/config/preview.ts | 22 +++ .../storybook/config/tsconfig.json | 19 ++ src/dev/storybook/aliases.ts | 1 + src/plugins/management/README.md | 40 +++- .../components/landing/landing.test.tsx | 90 +++++++++ .../public/components/landing/landing.tsx | 20 +- .../management_app/management_app.tsx | 67 ++++--- .../management_app/management_context.tsx | 30 +++ .../management_app/management_router.tsx | 17 +- src/plugins/management/public/mocks/index.ts | 1 + src/plugins/management/public/plugin.ts | 10 +- src/plugins/management/public/types.ts | 14 ++ src/plugins/management/tsconfig.json | 5 +- tsconfig.base.json | 4 + .../serverless_observability/kibana.jsonc | 2 +- .../serverless_observability/public/plugin.ts | 7 +- .../serverless_observability/public/types.ts | 3 + .../serverless_observability/tsconfig.json | 2 + .../serverless_search/public/plugin.ts | 7 +- .../plugins/serverless_search/tsconfig.json | 1 + yarn.lock | 8 + 47 files changed, 1097 insertions(+), 50 deletions(-) create mode 100644 packages/kbn-management/cards_navigation/README.mdx create mode 100644 packages/kbn-management/cards_navigation/index.ts create mode 100644 packages/kbn-management/cards_navigation/jest.config.js create mode 100644 packages/kbn-management/cards_navigation/kibana.jsonc create mode 100644 packages/kbn-management/cards_navigation/mocks/mocks.ts create mode 100644 packages/kbn-management/cards_navigation/mocks/storybook.mock.ts create mode 100644 packages/kbn-management/cards_navigation/package.json create mode 100644 packages/kbn-management/cards_navigation/src/cards_navigation.stories.tsx create mode 100644 packages/kbn-management/cards_navigation/src/cards_navigation.test.tsx create mode 100644 packages/kbn-management/cards_navigation/src/cards_navigation.tsx create mode 100644 packages/kbn-management/cards_navigation/src/consts.tsx create mode 100644 packages/kbn-management/cards_navigation/src/index.ts create mode 100644 packages/kbn-management/cards_navigation/src/types.ts create mode 100644 packages/kbn-management/cards_navigation/tsconfig.json create mode 100644 packages/kbn-management/storybook/config/README.mdx create mode 100644 packages/kbn-management/storybook/config/constants.ts create mode 100755 packages/kbn-management/storybook/config/index.ts create mode 100644 packages/kbn-management/storybook/config/kibana.jsonc create mode 100644 packages/kbn-management/storybook/config/main.ts create mode 100644 packages/kbn-management/storybook/config/manager.ts create mode 100644 packages/kbn-management/storybook/config/package.json create mode 100644 packages/kbn-management/storybook/config/preview.ts create mode 100644 packages/kbn-management/storybook/config/tsconfig.json create mode 100644 src/plugins/management/public/components/landing/landing.test.tsx create mode 100644 src/plugins/management/public/components/management_app/management_context.tsx diff --git a/.buildkite/scripts/steps/storybooks/build_and_upload.ts b/.buildkite/scripts/steps/storybooks/build_and_upload.ts index 0d6cef4a5a0d..82640969a832 100644 --- a/.buildkite/scripts/steps/storybooks/build_and_upload.ts +++ b/.buildkite/scripts/steps/storybooks/build_and_upload.ts @@ -35,6 +35,7 @@ const STORYBOOKS = [ 'expression_reveal_image', 'expression_shape', 'expression_tagcloud', + 'management', 'fleet', 'grouping', 'home', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2e1eeba92427..7ed90e7930b3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -471,7 +471,9 @@ packages/kbn-logging-mocks @elastic/kibana-core x-pack/plugins/logstash @elastic/logstash packages/kbn-managed-vscode-config @elastic/kibana-operations packages/kbn-managed-vscode-config-cli @elastic/kibana-operations +packages/kbn-management/cards_navigation @elastic/platform-deployment-management src/plugins/management @elastic/platform-deployment-management +packages/kbn-management/storybook/config @elastic/platform-deployment-management test/plugin_functional/plugins/management_test_plugin @elastic/kibana-app-services packages/kbn-mapbox-gl @elastic/kibana-gis x-pack/examples/third_party_maps_source_example @elastic/kibana-gis diff --git a/.i18nrc.json b/.i18nrc.json index 630143ef91cb..db0b984b8eb2 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -71,7 +71,7 @@ "kibanaOverview": "src/plugins/kibana_overview", "lists": "packages/kbn-securitysolution-list-utils/src", "exceptionList-components": "packages/kbn-securitysolution-exception-list-components/src", - "management": ["src/legacy/core_plugins/management", "src/plugins/management"], + "management": ["src/legacy/core_plugins/management", "src/plugins/management", "packages/kbn-management"], "monaco": "packages/kbn-monaco/src", "navigation": "src/plugins/navigation", "newsfeed": "src/plugins/newsfeed", diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 81e2e1b76815..919703efff78 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -236,7 +236,7 @@ in Kibana, e.g. visualizations. It has the form of a flyout panel. |{kib-repo}blob/{branch}/src/plugins/management/README.md[management] |This plugins contains the "Stack Management" page framework. It offers navigation and an API -to link individual managment section into it. This plugin does not contain any individual +to link individual management section into it. This plugin does not contain any individual management section itself. diff --git a/package.json b/package.json index e08696494f8b..d1c7fd0f769f 100644 --- a/package.json +++ b/package.json @@ -488,6 +488,7 @@ "@kbn/logging": "link:packages/kbn-logging", "@kbn/logging-mocks": "link:packages/kbn-logging-mocks", "@kbn/logstash-plugin": "link:x-pack/plugins/logstash", + "@kbn/management-cards-navigation": "link:packages/kbn-management/cards_navigation", "@kbn/management-plugin": "link:src/plugins/management", "@kbn/management-test-plugin": "link:test/plugin_functional/plugins/management_test_plugin", "@kbn/mapbox-gl": "link:packages/kbn-mapbox-gl", @@ -1143,6 +1144,7 @@ "@kbn/lint-ts-projects-cli": "link:packages/kbn-lint-ts-projects-cli", "@kbn/managed-vscode-config": "link:packages/kbn-managed-vscode-config", "@kbn/managed-vscode-config-cli": "link:packages/kbn-managed-vscode-config-cli", + "@kbn/management-storybook-config": "link:packages/kbn-management/storybook/config", "@kbn/optimizer": "link:packages/kbn-optimizer", "@kbn/optimizer-webpack-helpers": "link:packages/kbn-optimizer-webpack-helpers", "@kbn/peggy": "link:packages/kbn-peggy", diff --git a/packages/kbn-management/cards_navigation/README.mdx b/packages/kbn-management/cards_navigation/README.mdx new file mode 100644 index 000000000000..a8d64031646a --- /dev/null +++ b/packages/kbn-management/cards_navigation/README.mdx @@ -0,0 +1,41 @@ +--- +id: kbn-management/components/CardsNavigation +slug: /kbn-management/components/cards_navigation +title: Cards Navigation +description: A component that allows the users to navigate to other management apps +tags: ['management', 'component'] +date: 2023-04-23 +--- + +This component is simply in charge of rendering a list of links to other management apps. It also +makes sure that the apps are enabled before doing so and it also aggregates them into predefined +categories. + +### Adding new items to the navigation + +For adding a new item to the navigation all you have to do is edit the `cards_navigation/src/consts.tsx` +file and add two things: + +* Add the app id into the `appIds` enum (make sure that the app_id value matches the one from the plugin) +* Add a new entry to the `appDefinitions` object. In here you can specify the category where you want it to be, icon and description. + +### Removing an item from the navigation + +If an item needs to be hidden from the navigation you can specify that by using the `hideLinksTo` prop: + +```typescript + + +``` + +In case an app needs to be removed all together from all the solutions you can remove its +definition from the `consts.tsx` file. The app might still be visible in the side nav, so if you +want to remove all links to it from management but without disabling the plugin you will have +to remove it from the side nav too. + +Bare in mind that if the app is disabled then it will be hidden anyway from the cards navigation +and from the sidenav. \ No newline at end of file diff --git a/packages/kbn-management/cards_navigation/index.ts b/packages/kbn-management/cards_navigation/index.ts new file mode 100644 index 000000000000..b6d71a5d3638 --- /dev/null +++ b/packages/kbn-management/cards_navigation/index.ts @@ -0,0 +1,12 @@ +/* + * 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 type { AppId, CardsNavigationComponentProps } from './src'; + +export { appIds } from './src'; +export { CardsNavigation } from './src'; diff --git a/packages/kbn-management/cards_navigation/jest.config.js b/packages/kbn-management/cards_navigation/jest.config.js new file mode 100644 index 000000000000..896bd4b1b706 --- /dev/null +++ b/packages/kbn-management/cards_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/kbn-management/cards_navigation'], +}; diff --git a/packages/kbn-management/cards_navigation/kibana.jsonc b/packages/kbn-management/cards_navigation/kibana.jsonc new file mode 100644 index 000000000000..9e83acc2ae5d --- /dev/null +++ b/packages/kbn-management/cards_navigation/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/management-cards-navigation", + "owner": "@elastic/platform-deployment-management" +} diff --git a/packages/kbn-management/cards_navigation/mocks/mocks.ts b/packages/kbn-management/cards_navigation/mocks/mocks.ts new file mode 100644 index 000000000000..a41f624a4a01 --- /dev/null +++ b/packages/kbn-management/cards_navigation/mocks/mocks.ts @@ -0,0 +1,90 @@ +/* + * 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 APP_BASE_PATH = 'http://localhost:9001'; + +export const sectionsMock = [ + { + id: 'data', + title: 'Data', + apps: [ + { + id: 'ingest_pipelines', + title: 'Ingest pipelines', + enabled: true, + basePath: '/app/management/ingest/pipelines', + }, + { + id: 'pipelines', + title: 'Pipelines', + enabled: true, + basePath: '/app/management/ingest/pipelines_logstash', + }, + { + id: 'index_management', + title: 'Index Management', + enabled: true, + basePath: '/app/management/ingest/pipelines_logstash', + }, + { + id: 'transform', + title: 'Transforms', + enabled: true, + basePath: '/app/management/ingest/pipelines_logstash', + }, + { + id: 'jobsListLink', + title: 'Machine Learning', + enabled: true, + basePath: '/app/management/ingest/pipelines_logstash', + }, + { + id: 'data_view', + title: 'Data View', + enabled: true, + basePath: '/app/management/ingest/pipelines_logstash', + }, + ], + }, + { + id: 'content', + title: 'Content', + apps: [ + { + id: 'objects', + title: 'Saved Objects', + enabled: true, + basePath: '/app/management/ingest/pipelines_logstash', + }, + { + id: 'tags', + title: 'Tags', + enabled: true, + basePath: '/app/management/ingest/pipelines_logstash', + }, + { + id: 'filesManagement', + title: 'Files Management', + enabled: true, + basePath: '/app/management/ingest/pipelines_logstash', + }, + ], + }, + { + id: 'other', + title: 'Other', + apps: [ + { + id: 'api_keys', + title: 'API Keys', + enabled: true, + basePath: '/app/management/ingest/pipelines_logstash', + }, + ], + }, +]; diff --git a/packages/kbn-management/cards_navigation/mocks/storybook.mock.ts b/packages/kbn-management/cards_navigation/mocks/storybook.mock.ts new file mode 100644 index 000000000000..473caf4a11d5 --- /dev/null +++ b/packages/kbn-management/cards_navigation/mocks/storybook.mock.ts @@ -0,0 +1,19 @@ +/* + * 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 { action } from '@storybook/addon-actions'; +import { APP_BASE_PATH, sectionsMock } from './mocks'; + +export const mockProps = { + appBasePath: APP_BASE_PATH, + sections: sectionsMock, + onCardClick: (e: any) => { + e.preventDefault(); + action('Navigate to: ', e.target.href); + }, +}; diff --git a/packages/kbn-management/cards_navigation/package.json b/packages/kbn-management/cards_navigation/package.json new file mode 100644 index 000000000000..40579d279fb6 --- /dev/null +++ b/packages/kbn-management/cards_navigation/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/management-cards-navigation", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/kbn-management/cards_navigation/src/cards_navigation.stories.tsx b/packages/kbn-management/cards_navigation/src/cards_navigation.stories.tsx new file mode 100644 index 000000000000..df56d2f25c6c --- /dev/null +++ b/packages/kbn-management/cards_navigation/src/cards_navigation.stories.tsx @@ -0,0 +1,36 @@ +/* + * 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 { CardsNavigation as Component } from './cards_navigation'; +import { mockProps } from '../mocks/storybook.mock'; + +import mdx from '../README.mdx'; + +export default { + title: 'Developer/Cards Navigation', + description: '', + parameters: { + docs: { + page: mdx, + }, + }, +}; + +export const CardsNavigationWillAllLinks = () => { + return ; +}; + +export const CardsNavigationWithSomeLinks = () => { + return ; +}; + +export const CardsNavigationWithHiddenLinks = () => { + return ; +}; diff --git a/packages/kbn-management/cards_navigation/src/cards_navigation.test.tsx b/packages/kbn-management/cards_navigation/src/cards_navigation.test.tsx new file mode 100644 index 000000000000..4f4dba3b0f8f --- /dev/null +++ b/packages/kbn-management/cards_navigation/src/cards_navigation.test.tsx @@ -0,0 +1,73 @@ +/* + * 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, screen, cleanup } from '@testing-library/react'; + +import { APP_BASE_PATH, sectionsMock } from '../mocks/mocks'; +import { CardsNavigation } from './cards_navigation'; +import { CardsNavigationComponentProps } from './types'; + +const renderCardsNavigationComponent = (props: CardsNavigationComponentProps) => { + return [render()]; +}; + +describe('Cards Navigation', () => { + describe('Component', () => { + test('is rendered', () => { + expect(() => + render() + ).not.toThrowError(); + }); + }); + + describe('States', () => { + beforeEach(() => { + cleanup(); + }); + + test('it renders categories and cards', () => { + renderCardsNavigationComponent({ sections: sectionsMock, appBasePath: APP_BASE_PATH }); + + const dataCategory = screen.queryByTestId('category-data'); + const dataPipelinesApp = screen.queryByTestId('app-card-pipelines'); + + expect(dataCategory).not.toBeNull(); + expect(dataPipelinesApp).not.toBeNull(); + }); + + test('it doesnt show empty categories', () => { + renderCardsNavigationComponent({ + sections: [ + { + id: 'data', + title: 'Data', + apps: [], + }, + ], + appBasePath: APP_BASE_PATH, + }); + + const dataCategory = screen.queryByTestId('category-data'); + + expect(dataCategory).toBeNull(); + }); + + test('it allows to disable certain apps', () => { + renderCardsNavigationComponent({ + sections: sectionsMock, + appBasePath: APP_BASE_PATH, + hideLinksTo: ['pipelines'], + }); + + const dataPipelinesApp = screen.queryByTestId('app-card-pipelines'); + + expect(dataPipelinesApp).toBeNull(); + }); + }); +}); diff --git a/packages/kbn-management/cards_navigation/src/cards_navigation.tsx b/packages/kbn-management/cards_navigation/src/cards_navigation.tsx new file mode 100644 index 000000000000..4d57a6843a3c --- /dev/null +++ b/packages/kbn-management/cards_navigation/src/cards_navigation.tsx @@ -0,0 +1,146 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { flatMap } from 'lodash'; +import { + EuiPageSection, + EuiPageHeader, + EuiSpacer, + EuiFlexGrid, + EuiFlexItem, + EuiCard, + EuiText, + EuiHorizontalRule, +} from '@elastic/eui'; +import { CardsNavigationComponentProps, AppRegistrySections, Application, AppProps } from './types'; +import { appCategories, appDefinitions, getAppIdsByCategory } from './consts'; +import type { AppId } from './consts'; + +// Retrieve the data we need from a given app from the management app registry +const getDataFromManagementApp = (app: Application) => { + return { + id: app.id, + title: app.title, + href: app.basePath, + }; +}; + +// Given a category and a list of apps, build an array of apps that belong to that category +const getAppsForCategory = (category: string, filteredApps: { [key: string]: Application }) => { + return getAppIdsByCategory(category) + .map((appId: AppId) => { + if (!filteredApps[appId]) { + return null; + } + + return { + ...getDataFromManagementApp(filteredApps[appId]), + ...appDefinitions[appId], + }; + }) + .filter(Boolean) as AppProps[]; +}; + +const getEnabledAppsByCategory = (sections: AppRegistrySections[], hideLinksTo: string[]) => { + // Flatten all apps into a single array + const flattenApps = flatMap(sections, (section) => section.apps) + // Remove all apps that the consumer wants to disable. + .filter((app) => !hideLinksTo.includes(app.id)); + // Filter out apps that are not enabled and create an object with the + // app id as the key so we can easily do app look up by id. + const filteredApps: { [key: string]: Application } = flattenApps.reduce( + (obj, item: Application) => { + return item.enabled ? { ...obj, [item.id]: item } : obj; + }, + {} + ); + + // Build list of categories with apps that are enabled + return [ + { + id: appCategories.DATA, + title: i18n.translate('management.landing.withCardNavigation.dataTitle', { + defaultMessage: 'Data', + }), + apps: getAppsForCategory(appCategories.DATA, filteredApps), + }, + { + id: appCategories.CONTENT, + title: i18n.translate('management.landing.withCardNavigation.contentTitle', { + defaultMessage: 'Content', + }), + apps: getAppsForCategory(appCategories.CONTENT, filteredApps), + }, + { + id: appCategories.OTHER, + title: i18n.translate('management.landing.withCardNavigation.otherTitle', { + defaultMessage: 'Other', + }), + apps: getAppsForCategory(appCategories.OTHER, filteredApps), + }, + // Filter out categories that don't have any apps since they dont need to be rendered + ].filter((category) => category.apps.length > 0); +}; + +export const CardsNavigation = ({ + sections, + appBasePath, + onCardClick, + hideLinksTo = [], +}: CardsNavigationComponentProps) => { + const appsByCategory = getEnabledAppsByCategory(sections, hideLinksTo); + + return ( + + + + {appsByCategory.map((category, index) => ( +
+ {index === 0 ? ( + + ) : ( + <> + + + + )} + +

{category.title}

+
+ + + {category.apps.map((app: AppProps) => ( + + + + ))} + +
+ ))} +
+ ); +}; diff --git a/packages/kbn-management/cards_navigation/src/consts.tsx b/packages/kbn-management/cards_navigation/src/consts.tsx new file mode 100644 index 000000000000..b01c6e1e024a --- /dev/null +++ b/packages/kbn-management/cards_navigation/src/consts.tsx @@ -0,0 +1,171 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EuiIcon } from '@elastic/eui'; + +import { AppDefinition } from './types'; + +export enum appIds { + INGEST_PIPELINES = 'ingest_pipelines', + PIPELINES = 'pipelines', + INDEX_MANAGEMENT = 'index_management', + TRANSFORM = 'transform', + ML = 'jobsListLink', + DATA_VIEW = 'data_view', + SAVED_OBJECTS = 'objects', + TAGS = 'tags', + FILES_MANAGEMENT = 'filesManagement', + API_KEYS = 'api_keys', + DATA_VIEWS = 'dataViews', + REPORTING = 'reporting', + CONNECTORS = 'triggersActionsConnectors', + RULES = 'triggersActions', + MAINTENANCE_WINDOWS = 'maintenanceWindows', +} + +// Create new type that is a union of all the appId values +export type AppId = `${appIds}`; + +export const appCategories = { + DATA: 'data', + CONTENT: 'content', + OTHER: 'other', +}; + +export const appDefinitions: Record = { + [appIds.INGEST_PIPELINES]: { + category: appCategories.DATA, + description: i18n.translate( + 'management.landing.withCardNavigation.ingestPipelinesDescription', + { + defaultMessage: + 'Use pipelines to remove or transform fields, extract values from text, and enrich your data before indexing.', + } + ), + icon: , + }, + [appIds.PIPELINES]: { + category: appCategories.DATA, + description: i18n.translate('management.landing.withCardNavigation.ingestDescription', { + defaultMessage: 'Manage Logstash event processing and see the result visually.', + }), + icon: , + }, + [appIds.INDEX_MANAGEMENT]: { + category: appCategories.DATA, + description: i18n.translate( + 'management.landing.withCardNavigation.indexmanagementDescription', + { + defaultMessage: 'Update your Elasticsearch indices individually or in bulk.', + } + ), + icon: , + }, + [appIds.TRANSFORM]: { + category: appCategories.DATA, + description: i18n.translate('management.landing.withCardNavigation.transformDescription', { + defaultMessage: + 'Transforms pivot indices into summarized, entity-centric indices, or create an indexed view of the latest documents.', + }), + icon: , + }, + [appIds.ML]: { + category: appCategories.DATA, + description: i18n.translate('management.landing.withCardNavigation.mlDescription', { + defaultMessage: + 'View, export, and import machine learning analytics and anomaly detection items.', + }), + icon: , + }, + [appIds.DATA_VIEW]: { + category: appCategories.DATA, + description: i18n.translate('management.landing.withCardNavigation.dataViewsDescription', { + defaultMessage: + 'Create and manage the data views that help you retrieve your data from Elasticsearch.', + }), + icon: , + }, + [appIds.SAVED_OBJECTS]: { + category: appCategories.CONTENT, + description: i18n.translate('management.landing.withCardNavigation.objectsDescription', { + defaultMessage: + 'Manage and share your saved objects. To edit the underlying data of an object, go to its associated application.', + }), + icon: , + }, + [appIds.TAGS]: { + category: appCategories.CONTENT, + description: i18n.translate('management.landing.withCardNavigation.tagsDescription', { + defaultMessage: 'Use tags to categorize and easily find your objects.', + }), + icon: , + }, + [appIds.FILES_MANAGEMENT]: { + category: appCategories.CONTENT, + description: i18n.translate('management.landing.withCardNavigation.fileManagementDescription', { + defaultMessage: 'Any files created will be listed here.', + }), + icon: , + }, + [appIds.API_KEYS]: { + category: appCategories.OTHER, + description: i18n.translate('management.landing.withCardNavigation.apiKeysDescription', { + defaultMessage: 'Allow applications to access Elastic on your behalf.', + }), + icon: , + }, + [appIds.DATA_VIEWS]: { + category: appCategories.DATA, + description: i18n.translate('management.landing.withCardNavigation.dataViewsDescription', { + defaultMessage: + 'Create and manage the data views that help you retrieve your data from Elasticsearch.', + }), + icon: , + }, + [appIds.CONNECTORS]: { + category: appCategories.OTHER, + description: i18n.translate('management.landing.withCardNavigation.connectorsDescription', { + defaultMessage: 'Connect third-party software with your alerting data.', + }), + icon: , + }, + [appIds.RULES]: { + category: appCategories.OTHER, + description: i18n.translate('management.landing.withCardNavigation.rulesDescription', { + defaultMessage: 'Detect conditions using rules.', + }), + icon: , + }, + [appIds.MAINTENANCE_WINDOWS]: { + category: appCategories.OTHER, + description: i18n.translate( + 'management.landing.withCardNavigation.maintenanceWindowsDescription', + { + defaultMessage: 'Suppress rule notifications for scheduled periods of time.', + } + ), + icon: , + }, + [appIds.REPORTING]: { + category: appCategories.CONTENT, + description: i18n.translate('management.landing.withCardNavigation.reportingDescription', { + defaultMessage: 'Get reports generated in applications.', + }), + icon: , + }, +}; + +// Compose a list of app ids that belong to a given category +export const getAppIdsByCategory = (category: string) => { + const appKeys = Object.keys(appDefinitions) as AppId[]; + return appKeys.filter((appId: AppId) => { + return appDefinitions[appId].category === category; + }); +}; diff --git a/packages/kbn-management/cards_navigation/src/index.ts b/packages/kbn-management/cards_navigation/src/index.ts new file mode 100644 index 000000000000..6644159aee9e --- /dev/null +++ b/packages/kbn-management/cards_navigation/src/index.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 type { CardsNavigationComponentProps } from './types'; +export type { AppId } from './consts'; + +export { appIds } from './consts'; +export { CardsNavigation } from './cards_navigation'; diff --git a/packages/kbn-management/cards_navigation/src/types.ts b/packages/kbn-management/cards_navigation/src/types.ts new file mode 100644 index 000000000000..7b31dc25ef5b --- /dev/null +++ b/packages/kbn-management/cards_navigation/src/types.ts @@ -0,0 +1,42 @@ +/* + * 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 { AppId } from './consts'; + +export interface Application { + id: string; + title: string; + basePath: string; + enabled: boolean; +} + +export interface AppRegistrySections { + id?: string; + title?: string; + apps: Application[]; +} + +export interface CardsNavigationComponentProps { + sections: AppRegistrySections[]; + appBasePath: string; + onCardClick?: (e: React.MouseEvent) => void; + hideLinksTo?: AppId[]; +} + +export interface ManagementAppProps { + id: string; + title: string; + href: string; +} + +export interface AppDefinition { + category: string; + description: string; + icon: React.ReactElement; +} + +export type AppProps = ManagementAppProps & AppDefinition; diff --git a/packages/kbn-management/cards_navigation/tsconfig.json b/packages/kbn-management/cards_navigation/tsconfig.json new file mode 100644 index 000000000000..d73041d8c35a --- /dev/null +++ b/packages/kbn-management/cards_navigation/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + "@kbn/ambient-ui-types" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/i18n", + ] +} diff --git a/packages/kbn-management/storybook/config/README.mdx b/packages/kbn-management/storybook/config/README.mdx new file mode 100644 index 000000000000..261b3fa4261b --- /dev/null +++ b/packages/kbn-management/storybook/config/README.mdx @@ -0,0 +1,5 @@ +# kbn-management storybook config + +This directory contains the configuration for the Storybook deployment for all kbn-management component packages. + +For more information, refer to the [Storybook documentation](https://storybook.js.org/docs/react/configure/overview) and the `@kbn/storybook` package. \ No newline at end of file diff --git a/packages/kbn-management/storybook/config/constants.ts b/packages/kbn-management/storybook/config/constants.ts new file mode 100644 index 000000000000..cbdff0022870 --- /dev/null +++ b/packages/kbn-management/storybook/config/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. + */ + +/** The title of the Storybook. */ +export const TITLE = 'kbn-management storybook'; + +/** The remote URL of the root from which Storybook loads stories for kbn-management. */ +export const URL = 'https://github.com/elastic/kibana/tree/main/packages/kbn-management'; diff --git a/packages/kbn-management/storybook/config/index.ts b/packages/kbn-management/storybook/config/index.ts new file mode 100755 index 000000000000..5a73da614bf2 --- /dev/null +++ b/packages/kbn-management/storybook/config/index.ts @@ -0,0 +1,9 @@ +/* + * 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 { TITLE, URL } from './constants'; diff --git a/packages/kbn-management/storybook/config/kibana.jsonc b/packages/kbn-management/storybook/config/kibana.jsonc new file mode 100644 index 000000000000..0af9dae356b6 --- /dev/null +++ b/packages/kbn-management/storybook/config/kibana.jsonc @@ -0,0 +1,6 @@ +{ + "type": "shared-common", + "id": "@kbn/management-storybook-config", + "owner": "@elastic/platform-deployment-management", + "devOnly": true +} diff --git a/packages/kbn-management/storybook/config/main.ts b/packages/kbn-management/storybook/config/main.ts new file mode 100644 index 000000000000..47a47a5a802b --- /dev/null +++ b/packages/kbn-management/storybook/config/main.ts @@ -0,0 +1,17 @@ +/* + * 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 { defaultConfig } from '@kbn/storybook'; + +module.exports = { + ...defaultConfig, + stories: ['../../**/*.stories.+(tsx|mdx)'], + reactOptions: { + strictMode: true, + }, +}; diff --git a/packages/kbn-management/storybook/config/manager.ts b/packages/kbn-management/storybook/config/manager.ts new file mode 100644 index 000000000000..fb973258b905 --- /dev/null +++ b/packages/kbn-management/storybook/config/manager.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 { addons } from '@storybook/addons'; +import { create } from '@storybook/theming'; +import { PANEL_ID as selectedPanel } from '@storybook/addon-actions'; + +import { TITLE as brandTitle, URL as brandUrl } from './constants'; + +addons.setConfig({ + theme: create({ + base: 'light', + brandTitle, + brandUrl, + }), + selectedPanel, + showPanel: true.valueOf, +}); diff --git a/packages/kbn-management/storybook/config/package.json b/packages/kbn-management/storybook/config/package.json new file mode 100644 index 000000000000..f054517bd15c --- /dev/null +++ b/packages/kbn-management/storybook/config/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/management-storybook-config", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/kbn-management/storybook/config/preview.ts b/packages/kbn-management/storybook/config/preview.ts new file mode 100644 index 000000000000..ee65b88614fb --- /dev/null +++ b/packages/kbn-management/storybook/config/preview.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/no-namespace,@typescript-eslint/no-empty-interface */ +declare global { + namespace NodeJS { + interface Global {} + interface InspectOptions {} + type ConsoleConstructor = console.ConsoleConstructor; + } +} + +/* eslint-enable */ +import jest from 'jest-mock'; + +/* @ts-expect-error TS doesn't see jest as a property of window, and I don't want to edit our global config. */ +window.jest = jest; diff --git a/packages/kbn-management/storybook/config/tsconfig.json b/packages/kbn-management/storybook/config/tsconfig.json new file mode 100644 index 000000000000..52ae9f82c90f --- /dev/null +++ b/packages/kbn-management/storybook/config/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/storybook" + ] +} diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index ea169de90e01..7dea24c44ee0 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -46,6 +46,7 @@ export const storybookAliases = { infra: 'x-pack/plugins/infra/.storybook', kibana_react: 'src/plugins/kibana_react/.storybook', lists: 'x-pack/plugins/lists/.storybook', + management: 'packages/kbn-management/storybook/config', observability: 'x-pack/plugins/observability/.storybook', presentation: 'src/plugins/presentation_util/storybook', random_sampling: 'x-pack/packages/kbn-random-sampling/.storybook', diff --git a/src/plugins/management/README.md b/src/plugins/management/README.md index 097ecd95ea8f..354fe766d473 100644 --- a/src/plugins/management/README.md +++ b/src/plugins/management/README.md @@ -1,5 +1,41 @@ # Management Plugin This plugins contains the "Stack Management" page framework. It offers navigation and an API -to link individual managment section into it. This plugin does not contain any individual -management section itself. \ No newline at end of file +to link individual management section into it. This plugin does not contain any individual +management section itself. + +## Cards navigation + +This plugin offers a special version of its landing page with a special feature called "cards navigation". +This feature can be enabled by calling the `setupCardsNavigation` method from the `management` plugin from +your plugin's `setup` method: + +``` + management.setupCardsNavigation({ enabled: true }); +``` + +The cards that will be shown are defined in the `packages/kbn-management/cards_navigation/src/consts.tsx` file +and they are grouped into categories. These cards are computed based on the `SectionsService` that is provided +in the `management` plugin. + +### Adding a new card to the navigation + +For adding a new item to the navigation all you have to do is edit the `packages/kbn-management/cards_navigation/src/consts.tsx` +file and add two things: + +* Add the app id into the `appIds` enum (make sure that the app_id value matches the one from the plugin) +* Add a new entry to the `appDefinitions` object. In here you can specify the category where you want it to be, icon and description. + + +### Removing an item from the navigation + +If card needs to be hidden from the navigation you can specify that by using the `hideLinksTo` prop: + +``` + management.setupCardsNavigation({ + enabled: true, + hideLinksTo: [appIds.MAINTENANCE_WINDOWS], + }); +``` + +More specifics about the `setupCardsNavigation` can be found in `packages/kbn-management/cards_navigation/readme.mdx`. \ No newline at end of file diff --git a/src/plugins/management/public/components/landing/landing.test.tsx b/src/plugins/management/public/components/landing/landing.test.tsx new file mode 100644 index 000000000000..b98ccbfca211 --- /dev/null +++ b/src/plugins/management/public/components/landing/landing.test.tsx @@ -0,0 +1,90 @@ +/* + * 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 { merge } from 'lodash'; +import { registerTestBed, AsyncTestBedConfig, TestBed } from '@kbn/test-jest-helpers'; + +import { AppContextProvider } from '../management_app/management_context'; +import { ManagementLandingPage } from './landing'; + +const sectionsMock = [ + { + id: 'data', + title: 'Data', + apps: [ + { + id: 'ingest_pipelines', + title: 'Ingest pipelines', + enabled: true, + basePath: '/app/management/ingest/pipelines', + }, + ], + }, +]; + +const testBedConfig: AsyncTestBedConfig = { + memoryRouter: { + initialEntries: [`/management_landing`], + componentRoutePath: '/management_landing', + }, + doMountAsync: true, +}; + +export const WithAppDependencies = + (Comp: any, overrides: Record = {}) => + (props: Record) => { + const contextDependencies = { + appBasePath: 'http://localhost:9001', + kibanaVersion: '8.10.0', + cardsNavigationConfig: { enabled: true }, + sections: sectionsMock, + }; + + return ( + // @ts-ignore + + + + ); + }; + +export const setupLandingPage = async (overrides?: Record): Promise => { + const initTestBed = registerTestBed( + WithAppDependencies(ManagementLandingPage, overrides), + testBedConfig + ); + const testBed = await initTestBed(); + + return { + ...testBed, + }; +}; + +describe('Landing Page', () => { + let testBed: TestBed; + + describe('Can be configured through cardsNavigationConfig', () => { + beforeEach(async () => { + testBed = await setupLandingPage(); + }); + + test('Shows cards navigation when feature is enabled', async () => { + const { exists } = testBed; + expect(exists('cards-navigation-page')).toBe(true); + }); + + test('Hide cards navigation when feature is disabled', async () => { + testBed = await setupLandingPage({ cardsNavigationConfig: { enabled: false } }); + const { exists } = testBed; + + expect(exists('cards-navigation-page')).toBe(false); + expect(exists('managementHome')).toBe(true); + }); + }); +}); diff --git a/src/plugins/management/public/components/landing/landing.tsx b/src/plugins/management/public/components/landing/landing.tsx index 10b29045b126..9e48a641fca4 100644 --- a/src/plugins/management/public/components/landing/landing.tsx +++ b/src/plugins/management/public/components/landing/landing.tsx @@ -10,24 +10,38 @@ import React, { useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiHorizontalRule } from '@elastic/eui'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; +import { EuiPageBody } from '@elastic/eui'; +import { CardsNavigation } from '@kbn/management-cards-navigation'; +import { useAppContext } from '../management_app/management_context'; interface ManagementLandingPageProps { - version: string; onAppMounted: (id: string) => void; setBreadcrumbs: () => void; } export const ManagementLandingPage = ({ - version, setBreadcrumbs, onAppMounted, }: ManagementLandingPageProps) => { + const { appBasePath, sections, kibanaVersion, cardsNavigationConfig } = useAppContext(); setBreadcrumbs(); useEffect(() => { onAppMounted(''); }, [onAppMounted]); + if (cardsNavigationConfig?.enabled) { + return ( + + + + ); + } + return ( } diff --git a/src/plugins/management/public/components/management_app/management_app.tsx b/src/plugins/management/public/components/management_app/management_app.tsx index 50ab45a867c9..133d19250085 100644 --- a/src/plugins/management/public/components/management_app/management_app.tsx +++ b/src/plugins/management/public/components/management_app/management_app.tsx @@ -13,10 +13,13 @@ import { BehaviorSubject } from 'rxjs'; import { I18nProvider } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { AppMountParameters, ChromeBreadcrumb, ScopedHistory } from '@kbn/core/public'; +import { CoreStart } from '@kbn/core/public'; +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import { reactRouterNavigate, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { KibanaPageTemplate, KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template'; import useObservable from 'react-use/lib/useObservable'; +import { AppContextProvider } from './management_context'; import { ManagementSection, MANAGEMENT_BREADCRUMB, @@ -24,7 +27,7 @@ import { } from '../../utils'; import { ManagementRouter } from './management_router'; import { managementSidebarNav } from '../management_sidebar_nav/management_sidebar_nav'; -import { SectionsServiceStart } from '../../types'; +import { SectionsServiceStart, NavigationCardsSubject } from '../../types'; interface ManagementAppProps { appBasePath: string; @@ -36,15 +39,23 @@ interface ManagementAppProps { export interface ManagementAppDependencies { sections: SectionsServiceStart; kibanaVersion: string; + coreStart: CoreStart; setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; isSidebarEnabled$: BehaviorSubject; + cardsNavigationConfig$: BehaviorSubject; } -export const ManagementApp = ({ dependencies, history, theme$ }: ManagementAppProps) => { - const { setBreadcrumbs, isSidebarEnabled$ } = dependencies; +export const ManagementApp = ({ + dependencies, + history, + theme$, + appBasePath, +}: ManagementAppProps) => { + const { setBreadcrumbs, isSidebarEnabled$, cardsNavigationConfig$ } = dependencies; const [selectedId, setSelectedId] = useState(''); const [sections, setSections] = useState(); const isSidebarEnabled = useObservable(isSidebarEnabled$); + const cardsNavigationConfig = useObservable(cardsNavigationConfig$); const onAppMounted = useCallback((id: string) => { setSelectedId(id); @@ -95,26 +106,36 @@ export const ManagementApp = ({ dependencies, history, theme$ }: ManagementAppPr } : undefined; + const contextDependencies = { + appBasePath, + sections, + cardsNavigationConfig, + kibanaVersion: dependencies.kibanaVersion, + }; + return ( - - - - - - - + + + + + + + + + + + ); }; diff --git a/src/plugins/management/public/components/management_app/management_context.tsx b/src/plugins/management/public/components/management_app/management_context.tsx new file mode 100644 index 000000000000..c096a99412b7 --- /dev/null +++ b/src/plugins/management/public/components/management_app/management_context.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, { createContext, useContext } from 'react'; +import { AppDependencies } from '../../types'; + +export const AppContext = createContext(undefined); + +export const AppContextProvider = ({ + children, + value, +}: { + children: React.ReactNode; + value: AppDependencies; +}) => { + return {children}; +}; + +export const useAppContext = () => { + const ctx = useContext(AppContext); + if (!ctx) { + throw new Error('useAppContext must be called from inside AppContext'); + } + return ctx; +}; diff --git a/src/plugins/management/public/components/management_app/management_router.tsx b/src/plugins/management/public/components/management_app/management_router.tsx index 0e982ee2af82..ad9a6e41989c 100644 --- a/src/plugins/management/public/components/management_app/management_router.tsx +++ b/src/plugins/management/public/components/management_app/management_router.tsx @@ -12,27 +12,18 @@ import { Router, Routes, Route } from '@kbn/shared-ux-router'; import { AppMountParameters, ChromeBreadcrumb, ScopedHistory } from '@kbn/core/public'; import { ManagementAppWrapper } from '../management_app_wrapper'; import { ManagementLandingPage } from '../landing'; -import { ManagementAppDependencies } from './management_app'; import { ManagementSection } from '../../utils'; interface ManagementRouterProps { history: AppMountParameters['history']; theme$: AppMountParameters['theme$']; - dependencies: ManagementAppDependencies; setBreadcrumbs: (crumbs?: ChromeBreadcrumb[], appHistory?: ScopedHistory) => void; onAppMounted: (id: string) => void; sections: ManagementSection[]; } export const ManagementRouter = memo( - ({ - dependencies, - history, - setBreadcrumbs, - onAppMounted, - sections, - theme$, - }: ManagementRouterProps) => ( + ({ history, setBreadcrumbs, onAppMounted, sections, theme$ }: ManagementRouterProps) => ( {sections.map((section) => @@ -62,11 +53,7 @@ export const ManagementRouter = memo( ( - + )} /> diff --git a/src/plugins/management/public/mocks/index.ts b/src/plugins/management/public/mocks/index.ts index ec4ec2fa7883..93ccefbe5130 100644 --- a/src/plugins/management/public/mocks/index.ts +++ b/src/plugins/management/public/mocks/index.ts @@ -43,6 +43,7 @@ const createSetupContract = (): ManagementSetup => ({ const createStartContract = (): ManagementStart => ({ setIsSidebarEnabled: jest.fn(), + setupCardsNavigation: jest.fn(), }); export const managementPluginMock = { diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index 578ae13a79c3..712799de2f65 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -22,7 +22,7 @@ import { AppNavLinkStatus, AppDeepLink, } from '@kbn/core/public'; -import { ManagementSetup, ManagementStart } from './types'; +import { ManagementSetup, ManagementStart, NavigationCardsSubject } from './types'; import { MANAGEMENT_APP_ID } from '../common/contants'; import { ManagementAppLocatorDefinition } from '../common/locator'; @@ -72,6 +72,10 @@ export class ManagementPlugin private hasAnyEnabledApps = true; private isSidebarEnabled$ = new BehaviorSubject(true); + private cardsNavigationConfig$ = new BehaviorSubject({ + enabled: false, + hideLinksTo: [], + }); constructor(private initializerContext: PluginInitializerContext) {} @@ -116,8 +120,10 @@ export class ManagementPlugin return renderApp(params, { sections: getSectionsServiceStartPrivate(), kibanaVersion, + coreStart, setBreadcrumbs: coreStart.chrome.setBreadcrumbs, isSidebarEnabled$: managementPlugin.isSidebarEnabled$, + cardsNavigationConfig$: managementPlugin.cardsNavigationConfig$, }); }, }); @@ -146,6 +152,8 @@ export class ManagementPlugin return { setIsSidebarEnabled: (isSidebarEnabled: boolean) => this.isSidebarEnabled$.next(isSidebarEnabled), + setupCardsNavigation: ({ enabled, hideLinksTo }) => + this.cardsNavigationConfig$.next({ enabled, hideLinksTo }), }; } } diff --git a/src/plugins/management/public/types.ts b/src/plugins/management/public/types.ts index 0f632b818a95..e2b5353d8995 100644 --- a/src/plugins/management/public/types.ts +++ b/src/plugins/management/public/types.ts @@ -10,6 +10,7 @@ import { Observable } from 'rxjs'; import { ScopedHistory, Capabilities } from '@kbn/core/public'; import type { LocatorPublic } from '@kbn/share-plugin/common'; import { ChromeBreadcrumb, CoreTheme } from '@kbn/core/public'; +import type { AppId } from '@kbn/management-cards-navigation'; import { ManagementSection, RegisterManagementSectionArgs } from './utils'; import type { ManagementAppLocatorParams } from '../common/locator'; @@ -29,6 +30,7 @@ export interface DefinedSections { export interface ManagementStart { setIsSidebarEnabled: (enabled: boolean) => void; + setupCardsNavigation: ({ enabled, hideLinksTo }: NavigationCardsSubject) => void; } export interface ManagementSectionsStartPrivate { @@ -78,3 +80,15 @@ export interface CreateManagementItemArgs { capabilitiesId?: string; // overrides app id redirectFrom?: string; // redirects from an old app id to the current app id } + +export interface NavigationCardsSubject { + enabled: boolean; + hideLinksTo?: AppId[]; +} + +export interface AppDependencies { + appBasePath: string; + kibanaVersion: string; + sections: ManagementSection[]; + cardsNavigationConfig?: NavigationCardsSubject; +} diff --git a/src/plugins/management/tsconfig.json b/src/plugins/management/tsconfig.json index 10fd10492bea..019867f38738 100644 --- a/src/plugins/management/tsconfig.json +++ b/src/plugins/management/tsconfig.json @@ -19,7 +19,10 @@ "@kbn/i18n", "@kbn/i18n-react", "@kbn/shared-ux-page-kibana-template", - "@kbn/shared-ux-router" + "@kbn/shared-ux-router", + "@kbn/management-cards-navigation", + "@kbn/shared-ux-link-redirect-app", + "@kbn/test-jest-helpers" ], "exclude": [ "target/**/*", diff --git a/tsconfig.base.json b/tsconfig.base.json index 5a11f3eadf32..b1a515ff31d5 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -936,8 +936,12 @@ "@kbn/managed-vscode-config/*": ["packages/kbn-managed-vscode-config/*"], "@kbn/managed-vscode-config-cli": ["packages/kbn-managed-vscode-config-cli"], "@kbn/managed-vscode-config-cli/*": ["packages/kbn-managed-vscode-config-cli/*"], + "@kbn/management-cards-navigation": ["packages/kbn-management/cards_navigation"], + "@kbn/management-cards-navigation/*": ["packages/kbn-management/cards_navigation/*"], "@kbn/management-plugin": ["src/plugins/management"], "@kbn/management-plugin/*": ["src/plugins/management/*"], + "@kbn/management-storybook-config": ["packages/kbn-management/storybook/config"], + "@kbn/management-storybook-config/*": ["packages/kbn-management/storybook/config/*"], "@kbn/management-test-plugin": ["test/plugin_functional/plugins/management_test_plugin"], "@kbn/management-test-plugin/*": ["test/plugin_functional/plugins/management_test_plugin/*"], "@kbn/mapbox-gl": ["packages/kbn-mapbox-gl"], diff --git a/x-pack/plugins/serverless_observability/kibana.jsonc b/x-pack/plugins/serverless_observability/kibana.jsonc index 3c493b23f515..af3fca8d8193 100644 --- a/x-pack/plugins/serverless_observability/kibana.jsonc +++ b/x-pack/plugins/serverless_observability/kibana.jsonc @@ -8,7 +8,7 @@ "server": true, "browser": true, "configPath": ["xpack", "serverless", "observability"], - "requiredPlugins": ["serverless", "observabilityShared", "kibanaReact"], + "requiredPlugins": ["serverless", "observabilityShared", "kibanaReact", "management"], "optionalPlugins": [], "requiredBundles": [] } diff --git a/x-pack/plugins/serverless_observability/public/plugin.ts b/x-pack/plugins/serverless_observability/public/plugin.ts index ee774980f7c2..4a0841c98ad9 100644 --- a/x-pack/plugins/serverless_observability/public/plugin.ts +++ b/x-pack/plugins/serverless_observability/public/plugin.ts @@ -6,6 +6,7 @@ */ import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { appIds } from '@kbn/management-cards-navigation'; import { getObservabilitySideNavComponent } from './components/side_navigation'; import { ServerlessObservabilityPluginSetup, @@ -28,10 +29,14 @@ export class ServerlessObservabilityPlugin core: CoreStart, setupDeps: ServerlessObservabilityPluginStartDependencies ): ServerlessObservabilityPluginStart { - const { observabilityShared, serverless } = setupDeps; + const { observabilityShared, serverless, management } = setupDeps; observabilityShared.setIsSidebarEnabled(false); serverless.setProjectHome('/app/observability/landing'); serverless.setSideNavComponent(getObservabilitySideNavComponent(core, { serverless })); + management.setupCardsNavigation({ + enabled: true, + hideLinksTo: [appIds.RULES], + }); return {}; } diff --git a/x-pack/plugins/serverless_observability/public/types.ts b/x-pack/plugins/serverless_observability/public/types.ts index 417a9c1701c8..299880b2cde4 100644 --- a/x-pack/plugins/serverless_observability/public/types.ts +++ b/x-pack/plugins/serverless_observability/public/types.ts @@ -10,6 +10,7 @@ import { ObservabilitySharedPluginSetup, ObservabilitySharedPluginStart, } from '@kbn/observability-shared-plugin/public'; +import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ServerlessObservabilityPluginSetup {} @@ -20,9 +21,11 @@ export interface ServerlessObservabilityPluginStart {} export interface ServerlessObservabilityPluginSetupDependencies { observabilityShared: ObservabilitySharedPluginSetup; serverless: ServerlessPluginSetup; + management: ManagementSetup; } export interface ServerlessObservabilityPluginStartDependencies { observabilityShared: ObservabilitySharedPluginStart; serverless: ServerlessPluginStart; + management: ManagementStart; } diff --git a/x-pack/plugins/serverless_observability/tsconfig.json b/x-pack/plugins/serverless_observability/tsconfig.json index 907663286811..5617f6827819 100644 --- a/x-pack/plugins/serverless_observability/tsconfig.json +++ b/x-pack/plugins/serverless_observability/tsconfig.json @@ -17,10 +17,12 @@ "kbn_references": [ "@kbn/core", "@kbn/config-schema", + "@kbn/management-plugin", "@kbn/serverless", "@kbn/observability-shared-plugin", "@kbn/kibana-react-plugin", "@kbn/shared-ux-chrome-navigation", "@kbn/i18n", + "@kbn/management-cards-navigation", ] } diff --git a/x-pack/plugins/serverless_search/public/plugin.ts b/x-pack/plugins/serverless_search/public/plugin.ts index 5287d23712a4..13d00a7ccdf5 100644 --- a/x-pack/plugins/serverless_search/public/plugin.ts +++ b/x-pack/plugins/serverless_search/public/plugin.ts @@ -7,6 +7,7 @@ import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; +import { appIds } from '@kbn/management-cards-navigation'; import { createServerlessSearchSideNavComponent as createComponent } from './layout/nav'; import { docLinks } from '../common/doc_links'; import { @@ -68,10 +69,14 @@ export class ServerlessSearchPlugin public start( core: CoreStart, - { serverless }: ServerlessSearchPluginStartDependencies + { serverless, management }: ServerlessSearchPluginStartDependencies ): ServerlessSearchPluginStart { serverless.setProjectHome('/app/elasticsearch'); serverless.setSideNavComponent(createComponent(core, { serverless })); + management.setupCardsNavigation({ + enabled: true, + hideLinksTo: [appIds.MAINTENANCE_WINDOWS], + }); return {}; } diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json index e62623958886..1a52fe618756 100644 --- a/x-pack/plugins/serverless_search/tsconfig.json +++ b/x-pack/plugins/serverless_search/tsconfig.json @@ -27,6 +27,7 @@ "@kbn/security-plugin", "@kbn/cloud-plugin", "@kbn/share-plugin", + "@kbn/management-cards-navigation", "@kbn/core-elasticsearch-server", ] } diff --git a/yarn.lock b/yarn.lock index bcc6e233f478..7909e5466ce7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4630,10 +4630,18 @@ version "0.0.0" uid "" +"@kbn/management-cards-navigation@link:packages/kbn-management/cards_navigation": + version "0.0.0" + uid "" + "@kbn/management-plugin@link:src/plugins/management": version "0.0.0" uid "" +"@kbn/management-storybook-config@link:packages/kbn-management/storybook/config": + version "0.0.0" + uid "" + "@kbn/management-test-plugin@link:test/plugin_functional/plugins/management_test_plugin": version "0.0.0" uid ""