[Deployment Management] Add landing page redirect feature and implement in security solution (#161060)

This commit is contained in:
Ignacio Rivas 2023-07-04 15:43:47 +02:00 committed by GitHub
parent 472d843fbc
commit 2a71469894
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 126 additions and 40 deletions

View file

@ -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`.
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');
```

View file

@ -43,6 +43,7 @@ export interface ManagementAppDependencies {
setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void;
isSidebarEnabled$: BehaviorSubject<boolean>;
cardsNavigationConfig$: BehaviorSubject<NavigationCardsSubject>;
landingPageRedirect$: BehaviorSubject<string | undefined>;
}
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<string>('');
const [sections, setSections] = useState<ManagementSection[]>();
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}
/>
</KibanaPageTemplate>
</KibanaThemeProvider>

View file

@ -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) => (
<Router history={history}>
<Routes>
{sections.map((section) =>
section
.getAppsEnabled()
.map((app) => (
<Route
path={`${app.basePath}`}
component={() => (
<ManagementAppWrapper
app={app}
setBreadcrumbs={setBreadcrumbs}
onAppMounted={onAppMounted}
history={history}
theme$={theme$}
/>
)}
/>
))
)}
{sections.map((section) =>
section
.getAppsEnabled()
.filter((app) => app.redirectFrom)
.map((app) => <Redirect path={`/${app.redirectFrom}*`} to={`${app.basePath}*`} />)
)}
<Route
path={'/'}
component={() => (
<ManagementLandingPage setBreadcrumbs={setBreadcrumbs} onAppMounted={onAppMounted} />
({
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 (
<Router history={history}>
<Routes>
{sections.map((section) =>
section
.getAppsEnabled()
.map((app) => (
<Route
path={`${app.basePath}`}
component={() => (
<ManagementAppWrapper
app={app}
setBreadcrumbs={setBreadcrumbs}
onAppMounted={onAppMounted}
history={history}
theme$={theme$}
/>
)}
/>
))
)}
/>
</Routes>
</Router>
)
{sections.map((section) =>
section
.getAppsEnabled()
.filter((app) => app.redirectFrom)
.map((app) => <Redirect path={`/${app.redirectFrom}*`} to={`${app.basePath}*`} />)
)}
<Route
path={'/'}
component={() => (
<ManagementLandingPage setBreadcrumbs={setBreadcrumbs} onAppMounted={onAppMounted} />
)}
/>
</Routes>
</Router>
);
}
);

View file

@ -44,6 +44,7 @@ const createSetupContract = (): ManagementSetup => ({
const createStartContract = (): ManagementStart => ({
setIsSidebarEnabled: jest.fn(),
setupCardsNavigation: jest.fn(),
setLandingPageRedirect: jest.fn(),
});
export const managementPluginMock = {

View file

@ -72,6 +72,7 @@ export class ManagementPlugin
private hasAnyEnabledApps = true;
private isSidebarEnabled$ = new BehaviorSubject<boolean>(true);
private landingPageRedirect$ = new BehaviorSubject<string | undefined>(undefined);
private cardsNavigationConfig$ = new BehaviorSubject<NavigationCardsSubject>({
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),
};
}
}

View file

@ -30,6 +30,7 @@ export interface DefinedSections {
export interface ManagementStart {
setIsSidebarEnabled: (enabled: boolean) => void;
setLandingPageRedirect: (landingPageRedirect: string) => void;
setupCardsNavigation: ({ enabled, hideLinksTo }: NavigationCardsSubject) => void;
}

View file

@ -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/**/*",

View file

@ -14,6 +14,7 @@
],
"requiredPlugins": [
"kibanaReact",
"management",
"ml",
"security",
"securitySolution",

View file

@ -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 }) => (

View file

@ -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 {};
}

View file

@ -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 {

View file

@ -17,6 +17,7 @@
"kbn_references": [
"@kbn/core",
"@kbn/config-schema",
"@kbn/management-plugin",
"@kbn/security-plugin",
"@kbn/security-solution-plugin",
"@kbn/serverless",

View file

@ -52,6 +52,9 @@ export function createTestConfig(options: CreateTestConfigOptions) {
observability: {
pathname: '/app/observability',
},
management: {
pathname: '/app/management',
},
},
// choose where screenshots should be saved
screenshots: {

View file

@ -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'));
});
}

View file

@ -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');
});
});
}