[SecuritySolution] Get started page (#158461)

This commit is contained in:
Angela Chuang 2023-05-29 10:31:48 +01:00 committed by GitHub
parent 96e4a83dcb
commit 3bf3e13bc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 204 additions and 7 deletions

View file

@ -69,6 +69,7 @@
"xpack.server": "legacy/server",
"xpack.serverless": "plugins/serverless",
"xpack.serverlessSearch": "plugins/serverless_search",
"xpack.serverlessSecurity": "plugins/serverless_security",
"xpack.securitySolution": "plugins/security_solution",
"xpack.sessionView": "plugins/session_view",
"xpack.snapshotRestore": "plugins/snapshot_restore",

View file

@ -0,0 +1,66 @@
/*
* 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 React from 'react';
import { render } from '@testing-library/react';
import { LandingPage } from './landing';
import { useKibana } from '../../common/lib/kibana';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
import { TestProviders } from '../../common/mock/test_providers';
jest.mock('../../common/lib/kibana', () => ({
useKibana: jest.fn(),
}));
jest.mock('../../common/components/landing_page', () => ({
LandingPageComponent: jest
.fn()
.mockReturnValue(<div data-test-subj="default-get-started-page" />),
}));
describe('LandingPage', () => {
const mockGetStartedComponent = jest.fn();
const history = createBrowserHistory();
const mockSecuritySolutionTemplateWrapper = jest
.fn()
.mockImplementation(({ children }) => <div>{children}</div>);
const renderPage = () =>
render(
<Router history={history}>
<LandingPage />
</Router>,
{ wrapper: TestProviders }
);
beforeAll(() => {
(useKibana as jest.Mock).mockReturnValue({
services: {
securityLayout: {
getPluginWrapper: jest.fn().mockReturnValue(mockSecuritySolutionTemplateWrapper),
},
getStartedComponent: mockGetStartedComponent,
},
});
});
beforeEach(() => {
jest.clearAllMocks();
});
it('renders the default component', () => {
const { queryByTestId } = renderPage();
expect(queryByTestId('default-get-started-page')).toBeInTheDocument();
});
it('renders the get started component', () => {
mockGetStartedComponent.mockReturnValue(<div data-test-subj="get-started" />);
const { queryByTestId } = renderPage();
expect(queryByTestId('default-get-started-page')).not.toBeInTheDocument();
expect(queryByTestId('get-started')).toBeInTheDocument();
});
});

View file

@ -5,15 +5,23 @@
* 2.0.
*/
import React, { memo } from 'react';
import React, { memo, useMemo } from 'react';
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { SecurityPageName } from '../../../common/constants';
import { LandingPageComponent } from '../../common/components/landing_page';
import { useKibana } from '../../common/lib/kibana';
import { PluginTemplateWrapper } from '../../common/components/plugin_template_wrapper';
export const LandingPage = memo(() => {
const { getStartedComponent } = useKibana().services;
const GetStartedComponent = useMemo(() => getStartedComponent?.(), [getStartedComponent]);
return (
<>
<LandingPageComponent />
{GetStartedComponent ?? (
<PluginTemplateWrapper>
<LandingPageComponent />
</PluginTemplateWrapper>
)}
<SpyRoute pageName={SecurityPageName.landing} />
</>
);

View file

@ -41,11 +41,9 @@ const DetectionResponseRoutes = () => (
);
const LandingRoutes = () => (
<PluginTemplateWrapper>
<TrackApplicationView viewId={SecurityPageName.landing}>
<LandingPage />
</TrackApplicationView>
</PluginTemplateWrapper>
<TrackApplicationView viewId={SecurityPageName.landing}>
<LandingPage />
</TrackApplicationView>
);
const EntityAnalyticsRoutes = () => (

View file

@ -29,6 +29,7 @@ import type {
SubPlugins,
StartedSubPlugins,
StartPluginsDependencies,
GetStartedComponent,
} from './types';
import { initTelemetry, TelemetryService } from './common/lib/telemetry';
import { KibanaServices } from './common/lib/kibana/services';
@ -88,6 +89,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
readonly experimentalFeatures: ExperimentalFeatures;
private isSidebarEnabled$: BehaviorSubject<boolean>;
private getStartedComponent?: GetStartedComponent;
constructor(private readonly initializerContext: PluginInitializerContext) {
this.config = this.initializerContext.config.get<SecuritySolutionUiConfigType>();
@ -171,6 +173,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
},
savedObjectsManagement: startPluginsDeps.savedObjectsManagement,
isSidebarEnabled$: this.isSidebarEnabled$,
getStartedComponent: this.getStartedComponent,
telemetry: this.telemetry.start(),
};
return services;
@ -313,6 +316,9 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
getNavLinks$: () => navLinks$,
setIsSidebarEnabled: (isSidebarEnabled: boolean) =>
this.isSidebarEnabled$.next(isSidebarEnabled),
setGetStartedPage: (getStartedComponent: GetStartedComponent) => {
this.getStartedComponent = getStartedComponent;
},
};
}

View file

@ -132,6 +132,7 @@ export type StartServices = CoreStart &
};
savedObjectsManagement: SavedObjectsManagementPluginStart;
isSidebarEnabled$: BehaviorSubject<boolean>;
getStartedComponent: GetStartedComponent | undefined;
telemetry: TelemetryClientStart;
};
@ -139,9 +140,15 @@ export interface PluginSetup {
resolver: () => Promise<ResolverPluginSetup>;
}
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type GetStartedComponentProps = {};
export type GetStartedComponent = (props?: GetStartedComponentProps) => JSX.Element;
export interface PluginStart {
getNavLinks$: () => Observable<NavigationLink[]>;
setIsSidebarEnabled: (isSidebarEnabled: boolean) => void;
setGetStartedPage: (getStartedComponent: GetStartedComponent) => void;
}
export interface AppObservableLibs {

View file

@ -0,0 +1,71 @@
/*
* 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 {
EuiCard,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiSpacer,
EuiText,
EuiTitle,
} from '@elastic/eui';
import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface Props {}
const icons = ['Kibana', 'Kibana', 'Kibana'];
export const GetStartedComponent: React.FC<Props> = ({}) => {
const cardNodes = useMemo(
() =>
icons.map(function (item, index) {
return (
<EuiFlexItem key={index}>
<EuiCard icon={<EuiIcon size="xxl" type={`logo${item}`} />} title={`Elastic ${item}`} />
</EuiFlexItem>
);
}),
[]
);
return (
<KibanaPageTemplate restrictWidth={false}>
<KibanaPageTemplate.Header
paddingSize="m"
pageTitle={i18n.translate('xpack.serverlessSecurity.getStarted.title', {
defaultMessage: `Welcome`,
})}
description={
<>
<EuiTitle size="xs">
<h3>
{i18n.translate('xpack.serverlessSecurity.getStarted.subTitle', {
defaultMessage: `Lets get started`,
})}
</h3>
</EuiTitle>
<EuiText size="m">
{i18n.translate('xpack.serverlessSecurity.getStarted.description', {
defaultMessage: `Set up your Elastic Security workspace. Use the toggles below to curate a list of tasks that best fits your environment`,
})}
</EuiText>
</>
}
>
<EuiFlexGroup gutterSize="l">{cardNodes}</EuiFlexGroup>
</KibanaPageTemplate.Header>
<KibanaPageTemplate.Section>
<EuiSpacer size="m" />
</KibanaPageTemplate.Section>
</KibanaPageTemplate>
);
};
export const GetStarted = React.memo(GetStartedComponent);

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 React from 'react';
import { CoreStart } from '@kbn/core/public';
import type { GetStartedComponentProps, GetStartedComponent } from './types';
import { GetStarted } from './get_started';
import { KibanaServicesProvider } from '../../services';
import { ServerlessSecurityPluginStartDependencies } from '../../types';
export const getSecurityGetStartedComponent = (
core: CoreStart,
pluginsStart: ServerlessSecurityPluginStartDependencies
): GetStartedComponent => {
return (_props?: GetStartedComponentProps) => (
<KibanaServicesProvider core={core} pluginsStart={pluginsStart}>
<GetStarted />
</KibanaServicesProvider>
);
};

View file

@ -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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type GetStartedComponentProps = {};
export type GetStartedComponent = (props?: GetStartedComponentProps) => JSX.Element;

View file

@ -6,6 +6,7 @@
*/
import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import { getSecurityGetStartedComponent } from './components/get_started';
import { getSecuritySideNavComponent } from './components/side_navigation';
import {
ServerlessSecurityPluginSetup,
@ -37,6 +38,7 @@ export class ServerlessSecurityPlugin
const { securitySolution, serverless } = startDeps;
securitySolution.setIsSidebarEnabled(false);
securitySolution.setGetStartedPage(getSecurityGetStartedComponent(core, startDeps));
serverless.setSideNavComponent(getSecuritySideNavComponent(core, startDeps));
return {};

View file

@ -25,5 +25,7 @@
"@kbn/kibana-react-plugin",
"@kbn/core-chrome-browser",
"@kbn/i18n-react",
"@kbn/i18n",
"@kbn/shared-ux-page-kibana-template",
]
}