diff --git a/src/plugins/management/README.md b/src/plugins/management/README.md index 354fe766d473a..828715235b1b9 100644 --- a/src/plugins/management/README.md +++ b/src/plugins/management/README.md @@ -38,4 +38,14 @@ If card needs to be hidden from the navigation you can specify that by using the }); ``` -More specifics about the `setupCardsNavigation` can be found in `packages/kbn-management/cards_navigation/readme.mdx`. \ No newline at end of file +More specifics about the `setupCardsNavigation` can be found in `packages/kbn-management/cards_navigation/readme.mdx`. + +## Landing page redirect + +If the consumer wants to have a separate landing page for the management section, they can use the `setLandingPageRedirect` +method to specify the path to the landing page: + + +``` + management.setLandingPageRedirect('/app/security/management'); +``` \ No newline at end of file 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 133d19250085a..538da6045255a 100644 --- a/src/plugins/management/public/components/management_app/management_app.tsx +++ b/src/plugins/management/public/components/management_app/management_app.tsx @@ -43,6 +43,7 @@ export interface ManagementAppDependencies { setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; isSidebarEnabled$: BehaviorSubject; cardsNavigationConfig$: BehaviorSubject; + landingPageRedirect$: BehaviorSubject; } export const ManagementApp = ({ @@ -51,11 +52,13 @@ export const ManagementApp = ({ theme$, appBasePath, }: ManagementAppProps) => { - const { setBreadcrumbs, isSidebarEnabled$, cardsNavigationConfig$ } = dependencies; + const { setBreadcrumbs, isSidebarEnabled$, cardsNavigationConfig$, landingPageRedirect$ } = + dependencies; const [selectedId, setSelectedId] = useState(''); const [sections, setSections] = useState(); const isSidebarEnabled = useObservable(isSidebarEnabled$); const cardsNavigationConfig = useObservable(cardsNavigationConfig$); + const landingPageRedirect = useObservable(landingPageRedirect$); const onAppMounted = useCallback((id: string) => { setSelectedId(id); @@ -131,6 +134,9 @@ export const ManagementApp = ({ setBreadcrumbs={setBreadcrumbsScoped} onAppMounted={onAppMounted} sections={sections} + landingPageRedirect={landingPageRedirect} + navigateToUrl={dependencies.coreStart.application.navigateToUrl} + basePath={dependencies.coreStart.http.basePath} /> 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 ad9a6e41989ce..9335af3bf5b78 100644 --- a/src/plugins/management/public/components/management_app/management_router.tsx +++ b/src/plugins/management/public/components/management_app/management_router.tsx @@ -6,10 +6,12 @@ * Side Public License, v 1. */ -import React, { memo } from 'react'; +import React, { memo, useEffect } from 'react'; import { Redirect } from 'react-router-dom'; import { Router, Routes, Route } from '@kbn/shared-ux-router'; import { AppMountParameters, ChromeBreadcrumb, ScopedHistory } from '@kbn/core/public'; +import type { ApplicationStart } from '@kbn/core-application-browser'; +import type { HttpStart } from '@kbn/core-http-browser'; import { ManagementAppWrapper } from '../management_app_wrapper'; import { ManagementLandingPage } from '../landing'; import { ManagementSection } from '../../utils'; @@ -20,43 +22,65 @@ interface ManagementRouterProps { setBreadcrumbs: (crumbs?: ChromeBreadcrumb[], appHistory?: ScopedHistory) => void; onAppMounted: (id: string) => void; sections: ManagementSection[]; + landingPageRedirect: string | undefined; + navigateToUrl: ApplicationStart['navigateToUrl']; + basePath: HttpStart['basePath']; } export const ManagementRouter = memo( - ({ history, setBreadcrumbs, onAppMounted, sections, theme$ }: ManagementRouterProps) => ( - - - {sections.map((section) => - section - .getAppsEnabled() - .map((app) => ( - ( - - )} - /> - )) - )} - {sections.map((section) => - section - .getAppsEnabled() - .filter((app) => app.redirectFrom) - .map((app) => ) - )} - ( - + ({ + history, + setBreadcrumbs, + onAppMounted, + sections, + theme$, + landingPageRedirect, + navigateToUrl, + basePath, + }: ManagementRouterProps) => { + // Redirect the user to the configured landing page if there is one + useEffect(() => { + if (landingPageRedirect) { + navigateToUrl(basePath.prepend(landingPageRedirect)); + } + }, [landingPageRedirect, navigateToUrl, basePath]); + + return ( + + + {sections.map((section) => + section + .getAppsEnabled() + .map((app) => ( + ( + + )} + /> + )) + )} + {sections.map((section) => + section + .getAppsEnabled() + .filter((app) => app.redirectFrom) + .map((app) => ) )} - /> - - - ) + + ( + + )} + /> + + + ); + } ); diff --git a/src/plugins/management/public/mocks/index.ts b/src/plugins/management/public/mocks/index.ts index 93ccefbe5130f..bc6bdcdc46814 100644 --- a/src/plugins/management/public/mocks/index.ts +++ b/src/plugins/management/public/mocks/index.ts @@ -44,6 +44,7 @@ const createSetupContract = (): ManagementSetup => ({ const createStartContract = (): ManagementStart => ({ setIsSidebarEnabled: jest.fn(), setupCardsNavigation: jest.fn(), + setLandingPageRedirect: jest.fn(), }); export const managementPluginMock = { diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index 712799de2f65c..445a9b3a3db0a 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -72,6 +72,7 @@ export class ManagementPlugin private hasAnyEnabledApps = true; private isSidebarEnabled$ = new BehaviorSubject(true); + private landingPageRedirect$ = new BehaviorSubject(undefined); private cardsNavigationConfig$ = new BehaviorSubject({ enabled: false, hideLinksTo: [], @@ -124,6 +125,7 @@ export class ManagementPlugin setBreadcrumbs: coreStart.chrome.setBreadcrumbs, isSidebarEnabled$: managementPlugin.isSidebarEnabled$, cardsNavigationConfig$: managementPlugin.cardsNavigationConfig$, + landingPageRedirect$: managementPlugin.landingPageRedirect$, }); }, }); @@ -154,6 +156,8 @@ export class ManagementPlugin this.isSidebarEnabled$.next(isSidebarEnabled), setupCardsNavigation: ({ enabled, hideLinksTo }) => this.cardsNavigationConfig$.next({ enabled, hideLinksTo }), + setLandingPageRedirect: (landingPageRedirect: string) => + this.landingPageRedirect$.next(landingPageRedirect), }; } } diff --git a/src/plugins/management/public/types.ts b/src/plugins/management/public/types.ts index e2b5353d8995c..52e66698629fd 100644 --- a/src/plugins/management/public/types.ts +++ b/src/plugins/management/public/types.ts @@ -30,6 +30,7 @@ export interface DefinedSections { export interface ManagementStart { setIsSidebarEnabled: (enabled: boolean) => void; + setLandingPageRedirect: (landingPageRedirect: string) => void; setupCardsNavigation: ({ enabled, hideLinksTo }: NavigationCardsSubject) => void; } diff --git a/src/plugins/management/tsconfig.json b/src/plugins/management/tsconfig.json index 019867f387387..aeb231f2ea9ed 100644 --- a/src/plugins/management/tsconfig.json +++ b/src/plugins/management/tsconfig.json @@ -22,7 +22,9 @@ "@kbn/shared-ux-router", "@kbn/management-cards-navigation", "@kbn/shared-ux-link-redirect-app", - "@kbn/test-jest-helpers" + "@kbn/test-jest-helpers", + "@kbn/core-application-browser", + "@kbn/core-http-browser" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/serverless_security/kibana.jsonc b/x-pack/plugins/serverless_security/kibana.jsonc index daa7490a38651..306918ed70697 100644 --- a/x-pack/plugins/serverless_security/kibana.jsonc +++ b/x-pack/plugins/serverless_security/kibana.jsonc @@ -14,6 +14,7 @@ ], "requiredPlugins": [ "kibanaReact", + "management", "ml", "security", "securitySolution", diff --git a/x-pack/plugins/serverless_security/public/common/services.mock.tsx b/x-pack/plugins/serverless_security/public/common/services.mock.tsx index 70f024842a340..a3aba806af18c 100644 --- a/x-pack/plugins/serverless_security/public/common/services.mock.tsx +++ b/x-pack/plugins/serverless_security/public/common/services.mock.tsx @@ -12,6 +12,7 @@ import { serverlessMock } from '@kbn/serverless/public/mocks'; import { securityMock } from '@kbn/security-plugin/public/mocks'; import { securitySolutionMock } from '@kbn/security-solution-plugin/public/mocks'; import { BehaviorSubject } from 'rxjs'; +import { managementPluginMock } from '@kbn/management-plugin/public/mocks'; import type { ProjectNavigationLink } from './navigation/links'; import type { Services } from './services'; @@ -23,6 +24,7 @@ export const servicesMocks: Services = { security: securityMock.createStart(), securitySolution: securitySolutionMock.createStart(), getProjectNavLinks$: jest.fn(() => new BehaviorSubject(mockProjectNavLinks())), + management: managementPluginMock.createStartContract(), }; export const KibanaServicesProvider = React.memo(({ children }) => ( diff --git a/x-pack/plugins/serverless_security/public/plugin.ts b/x-pack/plugins/serverless_security/public/plugin.ts index 356aea4f2c345..acc7854a363b0 100644 --- a/x-pack/plugins/serverless_security/public/plugin.ts +++ b/x-pack/plugins/serverless_security/public/plugin.ts @@ -48,7 +48,7 @@ export class ServerlessSecurityPlugin core: CoreStart, startDeps: ServerlessSecurityPluginStartDependencies ): ServerlessSecurityPluginStart { - const { securitySolution, serverless } = startDeps; + const { securitySolution, serverless, management } = startDeps; const { productTypes } = this.config; const services = createServices(core, startDeps); @@ -62,6 +62,8 @@ export class ServerlessSecurityPlugin subscribeNavigationTree(services); subscribeBreadcrumbs(services); + management.setLandingPageRedirect('/app/security/manage'); + return {}; } diff --git a/x-pack/plugins/serverless_security/public/types.ts b/x-pack/plugins/serverless_security/public/types.ts index 3954183cc9749..73052810127ad 100644 --- a/x-pack/plugins/serverless_security/public/types.ts +++ b/x-pack/plugins/serverless_security/public/types.ts @@ -11,6 +11,7 @@ import type { PluginStart as SecuritySolutionPluginStart, } from '@kbn/security-solution-plugin/public'; import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; +import { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public'; import type { SecurityProductTypes } from '../common/config'; // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -23,12 +24,14 @@ export interface ServerlessSecurityPluginSetupDependencies { security: SecurityPluginSetup; securitySolution: SecuritySolutionPluginSetup; serverless: ServerlessPluginSetup; + management: ManagementSetup; } export interface ServerlessSecurityPluginStartDependencies { security: SecurityPluginStart; securitySolution: SecuritySolutionPluginStart; serverless: ServerlessPluginStart; + management: ManagementStart; } export interface ServerlessSecurityPublicConfig { diff --git a/x-pack/plugins/serverless_security/tsconfig.json b/x-pack/plugins/serverless_security/tsconfig.json index 69d976d324105..2d2d3b8d34e6a 100644 --- a/x-pack/plugins/serverless_security/tsconfig.json +++ b/x-pack/plugins/serverless_security/tsconfig.json @@ -17,6 +17,7 @@ "kbn_references": [ "@kbn/core", "@kbn/config-schema", + "@kbn/management-plugin", "@kbn/security-plugin", "@kbn/security-solution-plugin", "@kbn/serverless", diff --git a/x-pack/test_serverless/functional/config.base.ts b/x-pack/test_serverless/functional/config.base.ts index 81a3f197a0ec0..23739a9615e69 100644 --- a/x-pack/test_serverless/functional/config.base.ts +++ b/x-pack/test_serverless/functional/config.base.ts @@ -52,6 +52,9 @@ export function createTestConfig(options: CreateTestConfigOptions) { observability: { pathname: '/app/observability', }, + management: { + pathname: '/app/management', + }, }, // choose where screenshots should be saved screenshots: { diff --git a/x-pack/test_serverless/functional/test_suites/security/index.ts b/x-pack/test_serverless/functional/test_suites/security/index.ts index 470f1e98082e8..4d31c06188bcf 100644 --- a/x-pack/test_serverless/functional/test_suites/security/index.ts +++ b/x-pack/test_serverless/functional/test_suites/security/index.ts @@ -10,5 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('serverless security UI', function () { loadTestFile(require.resolve('./landing_page')); + loadTestFile(require.resolve('./management')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/security/management.ts b/x-pack/test_serverless/functional/test_suites/security/management.ts new file mode 100644 index 0000000000000..f610e7b25ca73 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/management.ts @@ -0,0 +1,25 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getPageObject }: FtrProviderContext) { + const PageObject = getPageObject('common'); + + describe('Management', function () { + it('redirects from common management url to security specific page', async () => { + const SUB_URL = ''; + await PageObject.navigateToUrl('management', SUB_URL, { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + + await PageObject.waitUntilUrlIncludes('/security/manage'); + }); + }); +}