[SecuritySolution] Migrate ESS get started page to ess_security plugin (#159603)

## Summary
Original issue: https://github.com/elastic/security-team/issues/6489
Relevant issue: https://github.com/elastic/kibana/pull/158461 |
https://github.com/elastic/kibana/pull/158955

1. Move Landing Cards Component to ess_security plugin
2. Any component render `LandingPageComponent` when no indices exists
will be redirected to get started page.

**Steps to verify:**
1. Start an empty Elastic Search to verify the get started page, then
visit all the pages that render get started page when no indices exist
to see if they are rendered properly.
2. Add some data to Elastic Search and visit each page and see if it's
rendered properly. Open the timeline, and flyout, make sure they are
looking alright on the page.

ESS `yarn start`:

Get started page:
No data view exists:
<img width="2549" alt="ess-get-started"
src="dd560ab3-e3b1-4695-adfa-a1851da04cb0">




Serverless `yarn serverless-security`:

Get started page:
<img width="2553" alt="serverless-get-started"
src="2839c66f-e675-4eb5-b909-1b7af450b6d1">

No data view exists:
<img width="2542" alt="serverless-no-data"
src="d8e3a26b-3d39-442a-98ca-df0831e332ea">

Data exists:

<img width="2539" alt="Screenshot 2023-06-14 at 23 30 34"
src="ab7b413e-f25a-4c5e-9946-f4094a26fcdb">





### Checklist

Delete any items that are not applicable to this PR.


- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Sergi Massaneda <sergi.massaneda@gmail.com>
This commit is contained in:
Angela Chuang 2023-06-26 13:32:26 +01:00 committed by GitHub
parent c990bea80c
commit 0e67ae364c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 592 additions and 342 deletions

View file

@ -74,6 +74,7 @@
"xpack.serverlessObservability": "plugins/serverless_observability",
"xpack.serverlessSecurity": "plugins/serverless_security",
"xpack.securitySolution": "plugins/security_solution",
"xpack.securitySolutionEss": "plugins/ess_security",
"xpack.sessionView": "plugins/session_view",
"xpack.snapshotRestore": "plugins/snapshot_restore",
"xpack.spaces": "plugins/spaces",

View file

@ -9,9 +9,11 @@
"browser": true,
"configPath": ["xpack", "ess", "security"],
"requiredPlugins": [
"securitySolution",
"securitySolution"
],
"optionalPlugins": [],
"requiredBundles": []
"optionalPlugins": [
"cloudExperiments",
],
"requiredBundles": [ "kibanaReact"]
}
}

View file

@ -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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
export const KibanaServicesProvider = jest
.fn()
.mockImplementation(({ children }) => <div>{children}</div>);
export const useKibana = jest.fn().mockReturnValue({
services: {
http: {
basePath: {
prepend: jest.fn().mockImplementation((path: string) => path),
},
},
cloudExperiments: {},
},
});

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export const useVariation = jest.fn();

View file

@ -0,0 +1,40 @@
/*
* 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 { renderHook } from '@testing-library/react-hooks';
import { cloudExperimentsMock } from '@kbn/cloud-experiments-plugin/common/mocks';
import { useVariation } from './use_variation';
describe('useVariation', () => {
test('it should call the setter if cloudExperiments is enabled', async () => {
const cloudExperiments = cloudExperimentsMock.createStartMock();
cloudExperiments.getVariation.mockResolvedValue('resolved value');
const setter = jest.fn();
const { result } = renderHook(() =>
useVariation(
cloudExperiments,
'security-solutions.add-integrations-url',
'my default value',
setter
)
);
await new Promise((resolve) => process.nextTick(resolve));
expect(result.error).toBe(undefined);
expect(setter).toHaveBeenCalledTimes(1);
expect(setter).toHaveBeenCalledWith('resolved value');
});
test('it should not call the setter if cloudExperiments is not enabled', async () => {
const setter = jest.fn();
const { result } = renderHook(() =>
useVariation(undefined, 'security-solutions.add-integrations-url', 'my default value', setter)
);
await new Promise((resolve) => process.nextTick(resolve));
expect(result.error).toBe(undefined);
expect(setter).not.toHaveBeenCalled();
});
});

View file

@ -0,0 +1,34 @@
/*
* 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 { useEffect } from 'react';
import type {
CloudExperimentsFeatureFlagNames,
CloudExperimentsPluginStart,
} from '@kbn/cloud-experiments-plugin/common';
/**
* Retrieves the variation of the feature flag if the cloudExperiments plugin is enabled.
* @param cloudExperiments {@link CloudExperimentsPluginStart}
* @param featureFlagName The name of the feature flag {@link CloudExperimentsFeatureFlagNames}
* @param defaultValue The default value in case it cannot retrieve the feature flag
* @param setter The setter from {@link useState} to update the value.
*/
export const useVariation = <Data>(
cloudExperiments: CloudExperimentsPluginStart | undefined,
featureFlagName: CloudExperimentsFeatureFlagNames,
defaultValue: Data,
setter: (value: Data) => void
) => {
useEffect(() => {
(async function loadVariation() {
const variationUrl = await cloudExperiments?.getVariation(featureFlagName, defaultValue);
if (variationUrl) {
setter(variationUrl);
}
})();
}, [cloudExperiments, featureFlagName, defaultValue, setter]);
};

View file

@ -0,0 +1,26 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../../..',
roots: ['<rootDir>/x-pack/plugins/ess_security/public/common'],
testMatch: ['<rootDir>/x-pack/plugins/ess_security/public/common/**/*.test.{js,mjs,ts,tsx}'],
coverageDirectory:
'<rootDir>/target/kibana-coverage/jest/x-pack/plugins/ess_security/public/common',
coverageReporters: ['text', 'html'],
collectCoverageFrom: [
'<rootDir>/x-pack/plugins/ess_security/public/common/**/*.{ts,tsx}',
'!<rootDir>/x-pack/plugins/ess_security/public/common/*.test.{ts,tsx}',
'!<rootDir>/x-pack/plugins/ess_security/public/common/{__test__,__snapshots__,__examples__,*mock*,tests,test_helpers,integration_tests,types}/**/*',
'!<rootDir>/x-pack/plugins/ess_security/public/common/*mock*.{ts,tsx}',
'!<rootDir>/x-pack/plugins/ess_security/public/common/*.test.{ts,tsx}',
'!<rootDir>/x-pack/plugins/ess_security/public/common/*.d.ts',
'!<rootDir>/x-pack/plugins/ess_security/public/common/*.config.ts',
'!<rootDir>/x-pack/plugins/ess_security/public/common/index.{js,ts,tsx}',
],
};

View file

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 281 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 513 KiB

After

Width:  |  Height:  |  Size: 513 KiB

Before After
Before After

View file

@ -0,0 +1,21 @@
/*
* 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 { KibanaServicesProvider } from '../services';
import { EssSecurityPluginStartDependencies } from '../types';
import { GetStarted } from './lazy';
export const getSecurityGetStartedComponent =
(core: CoreStart, pluginsStart: EssSecurityPluginStartDependencies): React.ComponentType =>
() =>
(
<KibanaServicesProvider core={core} pluginsStart={pluginsStart}>
<GetStarted />
</KibanaServicesProvider>
);

View file

@ -0,0 +1,16 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../../..',
roots: ['<rootDir>/x-pack/plugins/ess_security/public/get_started'],
coverageDirectory:
'<rootDir>/target/kibana-coverage/jest/x-pack/plugins/ess_security/public/get_started',
coverageReporters: ['text', 'html'],
collectCoverageFrom: ['<rootDir>/x-pack/plugins/ess_security/public/get_started/**/*.{ts,tsx}'],
};

View file

@ -7,49 +7,39 @@
import React from 'react';
import { render } from '@testing-library/react';
import { useVariationMock } from '../utils.mocks';
import { TestProviders } from '../../mock';
import { LandingCards } from '.';
import { ADD_DATA_PATH } from '../../../../common/constants';
import { LandingCards } from './landing_cards';
import { ADD_DATA_PATH } from '@kbn/security-solution-plugin/common';
import { useVariation } from '../common/hooks/use_variation';
jest.mock('../common/hooks/use_variation');
jest.mock('../services');
describe('LandingCards component', () => {
beforeEach(() => {
useVariationMock.mockReset();
(useVariation as jest.Mock).mockReset();
});
it('has add data links', () => {
const { getAllByText } = render(
<TestProviders>
<LandingCards />
</TestProviders>
);
const { getAllByText } = render(<LandingCards />);
expect(getAllByText('Add security integrations')).toHaveLength(2);
});
describe.each(['header', 'footer'])('URLs at the %s', (place) => {
it('points to the default Add data URL', () => {
const { queryByTestId } = render(
<TestProviders>
<LandingCards />
</TestProviders>
);
const { queryByTestId } = render(<LandingCards />);
const link = queryByTestId(`add-integrations-${place}`);
expect(link?.getAttribute('href')).toBe(ADD_DATA_PATH);
});
it('points to the resolved Add data URL by useVariation', () => {
const customResolvedUrl = '/test/url';
useVariationMock.mockImplementationOnce(
(useVariation as jest.Mock).mockImplementationOnce(
(cloudExperiments, featureFlagName, defaultValue, setter) => {
setter(customResolvedUrl);
}
);
const { queryByTestId } = render(
<TestProviders>
<LandingCards />
</TestProviders>
);
const { queryByTestId } = render(<LandingCards />);
const link = queryByTestId(`add-integrations-${place}`);
expect(link?.getAttribute('href')).toBe(customResolvedUrl);
});

View file

@ -6,15 +6,23 @@
*/
import React, { memo, useMemo, useState } from 'react';
import { EuiButton, EuiCard, EuiFlexGroup, EuiFlexItem, EuiPageHeader } from '@elastic/eui';
import styled from 'styled-components';
import { useVariation } from '../utils';
import {
EuiButton,
EuiCard,
EuiFlexGroup,
EuiFlexItem,
EuiPageHeader,
EuiThemeComputed,
useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import { ADD_DATA_PATH } from '@kbn/security-solution-plugin/common';
import { useVariation } from '../common/hooks/use_variation';
import * as i18n from './translations';
import endpointSvg from '../../images/endpoint1.svg';
import cloudSvg from '../../images/cloud1.svg';
import siemSvg from '../../images/siem1.svg';
import { ADD_DATA_PATH } from '../../../../common/constants';
import { useKibana } from '../../lib/kibana';
import endpointSvg from './images/endpoint1.svg';
import cloudSvg from './images/cloud1.svg';
import siemSvg from './images/siem1.svg';
import { useKibana } from '../services';
const imgUrls = {
cloud: cloudSvg,
@ -22,13 +30,32 @@ const imgUrls = {
endpoint: endpointSvg,
};
const StyledEuiCard = styled(EuiCard)`
const headerCardStyles = css`
span.euiTitle {
font-size: 36px;
line-height: 100%;
}
`;
const StyledEuiCardTop = styled(EuiCard)`
const pageHeaderStyles = css`
h1 {
font-size: 18px;
}
`;
const getFlexItemStyles = (euiTheme: EuiThemeComputed) => css`
background: ${euiTheme.colors.lightestShade};
padding: 20px;
`;
const cardStyles = css`
img {
margin-top: 20px;
max-width: 400px;
}
`;
const footerStyles = css`
span.euiTitle {
font-size: 36px;
line-height: 100%;
@ -37,24 +64,6 @@ const StyledEuiCardTop = styled(EuiCard)`
display: block;
margin: 20px auto 0;
`;
const StyledEuiPageHeader = styled(EuiPageHeader)`
h1 {
font-size: 18px;
}
`;
const StyledImgEuiCard = styled(EuiCard)`
img {
margin-top: 20px;
max-width: 400px;
}
`;
const StyledEuiFlexItem = styled(EuiFlexItem)`
background: ${({ theme }) => theme.eui.euiColorLightestShade};
padding: 20px;
margin: -12px !important;
`;
export const LandingCards = memo(() => {
const {
@ -64,6 +73,7 @@ export const LandingCards = memo(() => {
cloudExperiments,
} = useKibana().services;
const { euiTheme } = useEuiTheme();
const [addIntegrationsUrl, setAddIntegrationsUrl] = useState(ADD_DATA_PATH);
useVariation(
cloudExperiments,
@ -73,13 +83,18 @@ export const LandingCards = memo(() => {
);
const href = useMemo(() => prepend(addIntegrationsUrl), [prepend, addIntegrationsUrl]);
return (
<EuiFlexGroup data-test-subj="siem-landing-page" direction="column" gutterSize="l">
<EuiFlexItem>
<EuiFlexGroup data-test-subj="siem-landing-page" direction="column" gutterSize="m">
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="l">
<EuiFlexItem>
<StyledEuiPageHeader pageTitle={i18n.SIEM_HEADER} iconType="logoSecurity" />
<StyledEuiCard
<EuiPageHeader
pageTitle={i18n.SIEM_HEADER}
iconType="logoSecurity"
css={pageHeaderStyles}
/>
<EuiCard
display="plain"
description={i18n.SIEM_DESCRIPTION}
textAlign="left"
@ -89,6 +104,7 @@ export const LandingCards = memo(() => {
{i18n.SIEM_CTA}
</EuiButton>
}
css={headerCardStyles}
/>
</EuiFlexItem>
<EuiFlexItem>
@ -107,39 +123,42 @@ export const LandingCards = memo(() => {
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<StyledEuiFlexItem>
<EuiFlexItem css={getFlexItemStyles(euiTheme)}>
<EuiFlexGroup gutterSize="m">
<EuiFlexItem>
<StyledImgEuiCard
<EuiCard
hasBorder
description={i18n.SIEM_CARD_DESCRIPTION}
image={imgUrls.siem}
textAlign="center"
title={i18n.SIEM_CARD_TITLE}
css={cardStyles}
/>
</EuiFlexItem>
<EuiFlexItem>
<StyledImgEuiCard
<EuiCard
hasBorder
description={i18n.ENDPOINT_DESCRIPTION}
image={imgUrls.endpoint}
textAlign="center"
title={i18n.ENDPOINT_TITLE}
css={cardStyles}
/>
</EuiFlexItem>
<EuiFlexItem>
<StyledImgEuiCard
<EuiCard
hasBorder
description={i18n.CLOUD_CARD_DESCRIPTION}
image={imgUrls.cloud}
textAlign="center"
title={i18n.CLOUD_CARD_TITLE}
css={cardStyles}
/>
</EuiFlexItem>
</EuiFlexGroup>
</StyledEuiFlexItem>
</EuiFlexItem>
<EuiFlexItem>
<StyledEuiCardTop
<EuiCard
display="plain"
description={i18n.UNIFY_DESCRIPTION}
paddingSize="l"
@ -150,9 +169,13 @@ export const LandingCards = memo(() => {
{i18n.SIEM_CTA}
</EuiButton>
}
css={footerStyles}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
});
LandingCards.displayName = 'LandingCards';
// eslint-disable-next-line import/no-default-export
export default LandingCards;

View file

@ -0,0 +1,18 @@
/*
* 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, { lazy, Suspense } from 'react';
import { EuiLoadingLogo } from '@elastic/eui';
const LandingCardsLazy = lazy(() => import('./landing_cards'));
const centerLogoStyle = { display: 'flex', margin: 'auto' };
export const GetStarted = () => (
<Suspense fallback={<EuiLoadingLogo logo="logoSecurity" size="xl" style={centerLogoStyle} />}>
<LandingCardsLazy />
</Suspense>
);

View file

@ -8,79 +8,79 @@
import { i18n } from '@kbn/i18n';
export const SIEM_HEADER = i18n.translate(
'xpack.securitySolution.overview.landingCards.box.siem.header',
'xpack.securitySolutionEss.getStarted.landingCards.box.siem.header',
{
defaultMessage: 'Elastic Security',
}
);
export const SIEM_TITLE = i18n.translate(
'xpack.securitySolution.overview.landingCards.box.siem.title',
'xpack.securitySolutionEss.getStarted.landingCards.box.siem.title',
{
defaultMessage: 'Security at the speed of Elastic',
}
);
export const SIEM_DESCRIPTION = i18n.translate(
'xpack.securitySolution.overview.landingCards.box.siem.desc',
'xpack.securitySolutionEss.getStarted.landingCards.box.siem.desc',
{
defaultMessage:
'Elastic Security equips teams to prevent, detect, and respond to threats at cloud speed and scale — securing business operations with a unified, open platform.',
}
);
export const SIEM_CTA = i18n.translate(
'xpack.securitySolution.overview.landingCards.box.siem.cta',
'xpack.securitySolutionEss.getStarted.landingCards.box.siem.cta',
{
defaultMessage: 'Add security integrations',
}
);
export const ENDPOINT_TITLE = i18n.translate(
'xpack.securitySolution.overview.landingCards.box.endpoint.title',
'xpack.securitySolutionEss.getStarted.landingCards.box.endpoint.title',
{
defaultMessage: 'Endpoint security at scale',
}
);
export const ENDPOINT_DESCRIPTION = i18n.translate(
'xpack.securitySolution.overview.landingCards.box.endpoint.desc',
'xpack.securitySolutionEss.getStarted.landingCards.box.endpoint.desc',
{
defaultMessage: 'Prevent, collect, detect and respond — all with Elastic Agent.',
}
);
export const SIEM_CARD_TITLE = i18n.translate(
'xpack.securitySolution.overview.landingCards.box.siemCard.title',
'xpack.securitySolutionEss.getStarted.landingCards.box.siemCard.title',
{
defaultMessage: 'SIEM for the modern SOC',
}
);
export const SIEM_CARD_DESCRIPTION = i18n.translate(
'xpack.securitySolution.overview.landingCards.box.siemCard.desc',
'xpack.securitySolutionEss.getStarted.landingCards.box.siemCard.desc',
{
defaultMessage: 'Detect, investigate, and respond to evolving threats in your environment.',
}
);
export const CLOUD_CARD_TITLE = i18n.translate(
'xpack.securitySolution.overview.landingCards.box.cloudCard.title',
'xpack.securitySolutionEss.getStarted.landingCards.box.cloudCard.title',
{
defaultMessage: 'Cloud protection end-to-end',
}
);
export const CLOUD_CARD_DESCRIPTION = i18n.translate(
'xpack.securitySolution.overview.landingCards.box.cloudCard.desc',
'xpack.securitySolutionEss.getStarted.landingCards.box.cloudCard.desc',
{
defaultMessage: 'Assess your Cloud Posture and protect your workloads from attacks.',
}
);
export const UNIFY_TITLE = i18n.translate(
'xpack.securitySolution.overview.landingCards.box.unify.title',
'xpack.securitySolutionEss.getStarted.landingCards.box.unify.title',
{
defaultMessage: 'Unify SIEM, endpoint security, and cloud security',
}
);
export const UNIFY_DESCRIPTION = i18n.translate(
'xpack.securitySolution.overview.landingCards.box.unify.desc',
'xpack.securitySolutionEss.getStarted.landingCards.box.unify.desc',
{
defaultMessage:
'Elastic Security modernizes security operations — enabling analytics across years of data, automating key processes, and protecting every host.',

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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
/** all nested directories have their own Jest config file */
testMatch: ['<rootDir>/x-pack/plugins/ess_security/public/*.test.{js,mjs,ts,tsx}'],
roots: ['<rootDir>/x-pack/plugins/ess_security/public'],
coverageDirectory: '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/ess_security/public',
coverageReporters: ['text', 'html'],
collectCoverageFrom: [
'<rootDir>/x-pack/plugins/ess_security/public/**/*.{ts,tsx}',
'!<rootDir>/x-pack/plugins/ess_security/public/*.test.{ts,tsx}',
'!<rootDir>/x-pack/plugins/ess_security/public/{__test__,__snapshots__,__examples__,*mock*,tests,test_helpers,integration_tests,types}/**/*',
'!<rootDir>/x-pack/plugins/ess_security/public/*mock*.{ts,tsx}',
'!<rootDir>/x-pack/plugins/ess_security/public/*.test.{ts,tsx}',
'!<rootDir>/x-pack/plugins/ess_security/public/*.d.ts',
'!<rootDir>/x-pack/plugins/ess_security/public/*.config.ts',
'!<rootDir>/x-pack/plugins/ess_security/public/index.{js,ts,tsx}',
],
};

View file

@ -6,6 +6,7 @@
*/
import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import { getSecurityGetStartedComponent } from './get_started';
import {
EssSecurityPluginSetup,
EssSecurityPluginStart,
@ -25,16 +26,19 @@ export class EssSecurityPlugin
constructor() {}
public setup(
_core: CoreSetup,
_setupDeps: EssSecurityPluginSetupDependencies
core: CoreSetup,
setupDeps: EssSecurityPluginSetupDependencies
): EssSecurityPluginSetup {
return {};
}
public start(
_core: CoreStart,
_startDeps: EssSecurityPluginStartDependencies
core: CoreStart,
startDeps: EssSecurityPluginStartDependencies
): EssSecurityPluginStart {
const { securitySolution } = startDeps;
securitySolution.setGetStartedPage(getSecurityGetStartedComponent(core, startDeps));
return {};
}

View file

@ -0,0 +1,26 @@
/*
* 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 { CoreStart } from '@kbn/core/public';
import React from 'react';
import {
KibanaContextProvider,
useKibana as useKibanaReact,
} from '@kbn/kibana-react-plugin/public';
import type { EssSecurityPluginStartDependencies } from './types';
export type Services = CoreStart & EssSecurityPluginStartDependencies;
export const KibanaServicesProvider: React.FC<{
core: CoreStart;
pluginsStart: EssSecurityPluginStartDependencies;
}> = ({ core, pluginsStart, children }) => {
const services: Services = { ...core, ...pluginsStart };
return <KibanaContextProvider services={services}>{children}</KibanaContextProvider>;
};
export const useKibana = () => useKibanaReact<Services>();

View file

@ -9,6 +9,7 @@ import type {
PluginSetup as SecuritySolutionPluginSetup,
PluginStart as SecuritySolutionPluginStart,
} from '@kbn/security-solution-plugin/public';
import { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/common';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface EssSecurityPluginSetup {}
@ -22,4 +23,5 @@ export interface EssSecurityPluginSetupDependencies {
export interface EssSecurityPluginStartDependencies {
securitySolution: SecuritySolutionPluginStart;
cloudExperiments?: CloudExperimentsPluginStart;
}

View file

@ -18,5 +18,8 @@
"@kbn/core",
"@kbn/config-schema",
"@kbn/security-solution-plugin",
"@kbn/i18n",
"@kbn/cloud-experiments-plugin",
"@kbn/kibana-react-plugin",
]
}

View file

@ -8,7 +8,14 @@
// TODO(jbudz): should be removed when upgrading to TS@4.8
// this is a skip for the errors created when typechecking with isolatedModules
export {};
export { APP_UI_ID, APP_ID, CASES_FEATURE_ID, SERVER_APP_ID, SecurityPageName } from './constants';
export {
APP_UI_ID,
APP_ID,
CASES_FEATURE_ID,
SERVER_APP_ID,
ADD_DATA_PATH,
SecurityPageName,
} from './constants';
export { ELASTIC_SECURITY_RULE_ID } from './detection_engine/constants';
export { allowedExperimentalValues, type ExperimentalFeatures } from './experimental_features';
export type { AppFeatureKeys } from './types/app_features';

View file

@ -30,7 +30,9 @@ export const SecuritySolutionBottomBar = React.memo(() => {
);
});
export const SecuritySolutionBottomBarProps: EuiBottomBarProps = {
export const SecuritySolutionBottomBarProps: EuiBottomBarProps & {
restrictWidth?: boolean | number | string;
} = {
className: BOTTOM_BAR_CLASSNAME,
'data-test-subj': 'timeline-bottom-bar-container',
};

View file

@ -24,11 +24,8 @@ import {
SecuritySolutionBottomBarProps,
} from './bottom_bar';
import { useShowTimeline } from '../../../common/utils/timeline/use_show_timeline';
import { useShowPagesWithEmptyView } from '../../../common/utils/empty_view/use_show_pages_with_empty_view';
import { useSyncFlyoutStateWithUrl } from '../../../flyout/url/use_sync_flyout_state_with_url';
const NO_DATA_PAGE_MAX_WIDTH = 950;
/**
* Need to apply the styles via a className to effect the containing bottom bar
* rather than applying them to the timeline bar directly
@ -68,8 +65,6 @@ export const SecuritySolutionTemplateWrapper: React.FC<Omit<KibanaPageTemplatePr
// To keep the mode in sync, we pass in the globalColorMode to the bottom bar here
const { colorMode: globalColorMode } = useEuiTheme();
const showEmptyState = useShowPagesWithEmptyView() || rest.isEmptyState;
const [flyoutRef, handleFlyoutChangedOrClosed] = useSyncFlyoutStateWithUrl();
/*
@ -88,16 +83,17 @@ export const SecuritySolutionTemplateWrapper: React.FC<Omit<KibanaPageTemplatePr
$isShowingTimelineOverlay={isShowingTimelineOverlay}
paddingSize="none"
solutionNav={solutionNavProps}
restrictWidth={showEmptyState ? NO_DATA_PAGE_MAX_WIDTH : false}
restrictWidth={false}
{...rest}
>
<GlobalKQLHeader />
<KibanaPageTemplate.Section
className="securityPageWrapper"
data-test-subj="pageContainer"
paddingSize="l"
alignment={showEmptyState ? 'center' : 'top'}
paddingSize={rest.paddingSize ?? 'l'}
alignment="top"
component="div"
grow={true}
>
{children}
</KibanaPageTemplate.Section>

View file

@ -0,0 +1,10 @@
/*
* 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';
export const LandingPageComponent = () => <div data-test-subj="siem-landing-page" />;

View file

@ -1,7 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LandingPageComponent component renders page properly 1`] = `
<Memo(SecuritySolutionPageWrapperComponent)>
<LandingCards />
</Memo(SecuritySolutionPageWrapperComponent)>
`;

View file

@ -4,15 +4,54 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { shallow } from 'enzyme';
import React from 'react';
import { render } from '@testing-library/react';
import { LandingPageComponent } from '.';
import { useKibana } from '../../lib/kibana';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
import { TestProviders } from '../../mock/test_providers';
describe('LandingPageComponent component', () => {
it('renders page properly', () => {
const EmptyComponent = shallow(<LandingPageComponent />);
expect(EmptyComponent).toMatchSnapshot();
jest.mock('../../lib/kibana', () => ({
useKibana: jest.fn(),
}));
jest.mock('react-use/lib/useObservable', () => jest.fn((fn) => fn()));
describe('LandingPageComponent', () => {
const mockGetStartedComponent = jest.fn();
const history = createBrowserHistory();
const mockSecuritySolutionTemplateWrapper = jest
.fn()
.mockImplementation(({ children }) => <div>{children}</div>);
const renderPage = () =>
render(
<Router history={history}>
<LandingPageComponent />
</Router>,
{ wrapper: TestProviders }
);
beforeAll(() => {
(useKibana as jest.Mock).mockReturnValue({
services: {
securityLayout: {
getPluginWrapper: jest.fn().mockReturnValue(mockSecuritySolutionTemplateWrapper),
},
getStartedComponent$: mockGetStartedComponent,
},
});
});
beforeEach(() => {
jest.clearAllMocks();
});
it('renders the get started component', () => {
mockGetStartedComponent.mockReturnValue(<div data-test-subj="get-started" />);
const { queryByTestId } = renderPage();
expect(queryByTestId('get-started')).toBeInTheDocument();
});
});

View file

@ -6,15 +6,13 @@
*/
import React, { memo } from 'react';
import { LandingCards } from '../landing_cards';
import { SecuritySolutionPageWrapper } from '../page_wrapper';
import useObservable from 'react-use/lib/useObservable';
import { useKibana } from '../../lib/kibana';
export const LandingPageComponent = memo(() => {
return (
<SecuritySolutionPageWrapper>
<LandingCards />
</SecuritySolutionPageWrapper>
);
const { getStartedComponent$ } = useKibana().services;
const GetStartedComponent = useObservable(getStartedComponent$);
return <>{GetStartedComponent}</>;
});
LandingPageComponent.displayName = 'LandingPageComponent';

View file

@ -1,9 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SecuritySolutionPageWrapper it renders 1`] = `
<Memo(SecuritySolutionPageWrapperComponent)>
<p>
Test page
</p>
</Memo(SecuritySolutionPageWrapperComponent)>
`;

View file

@ -5,22 +5,100 @@
* 2.0.
*/
import { shallow } from 'enzyme';
import React from 'react';
import { TestProviders } from '../../mock';
import { render } from '@testing-library/react';
import { SecuritySolutionPageWrapper } from '.';
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
import { TestProviders } from '../../mock';
jest.mock('../../hooks/use_experimental_features', () => ({
useIsExperimentalFeatureEnabled: jest.fn(),
}));
describe('SecuritySolutionPageWrapper', () => {
test('it renders', () => {
const wrapper = shallow(
<TestProviders>
<SecuritySolutionPageWrapper>
<p>{'Test page'}</p>
</SecuritySolutionPageWrapper>
</TestProviders>
beforeEach(() => {
jest.clearAllMocks();
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true);
});
it('should render children and apply classNames correctly', () => {
const { container } = render(
<SecuritySolutionPageWrapper
className="custom-class"
style={{ color: 'red' }}
noPadding
noTimeline
>
<div>{'Child Component'}</div>
</SecuritySolutionPageWrapper>,
{
wrapper: TestProviders,
}
);
expect(wrapper.find('Memo(SecuritySolutionPageWrapperComponent)')).toMatchSnapshot();
const wrapperElement = container.firstChild;
expect(wrapperElement).toHaveClass('securitySolutionWrapper');
expect(wrapperElement).toHaveClass('securitySolutionWrapper--noPadding');
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--withTimeline');
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--fullHeight');
expect(wrapperElement).toHaveClass('custom-class');
expect(wrapperElement).toHaveStyle('color: red');
expect(wrapperElement).toContainHTML('<div>Child Component</div>');
});
it('should apply "noPadding" class when "noPadding" prop is true', () => {
const { container } = render(
<SecuritySolutionPageWrapper noPadding>
<div>{'Child Component'}</div>
</SecuritySolutionPageWrapper>,
{
wrapper: TestProviders,
}
);
const wrapperElement = container.firstChild;
expect(wrapperElement).toHaveClass('securitySolutionWrapper');
expect(wrapperElement).toHaveClass('securitySolutionWrapper--noPadding');
expect(wrapperElement).toHaveClass('securitySolutionWrapper--withTimeline');
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--fullHeight');
});
it('should apply "noTimeline" class when "noTimeline" prop is true', () => {
const { container } = render(
<SecuritySolutionPageWrapper noTimeline>
<div>{'Child Component'}</div>
</SecuritySolutionPageWrapper>,
{
wrapper: TestProviders,
}
);
const wrapperElement = container.firstChild;
expect(wrapperElement).toHaveClass('securitySolutionWrapper');
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--noPadding');
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--withTimeline');
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--fullHeight');
});
it('should not apply "noTimeline" class when "noTimeline" prop is false', () => {
const { container } = render(
<SecuritySolutionPageWrapper noTimeline={false}>
<div>{'Child Component'}</div>
</SecuritySolutionPageWrapper>,
{
wrapper: TestProviders,
}
);
const wrapperElement = container.firstChild;
expect(wrapperElement).toHaveClass('securitySolutionWrapper');
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--noPadding');
expect(wrapperElement).toHaveClass('securitySolutionWrapper--withTimeline');
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--fullHeight');
});
});

View file

@ -34,7 +34,6 @@ Wrapper.displayName = 'Wrapper';
interface SecuritySolutionPageWrapperProps {
children: React.ReactNode;
restrictWidth?: boolean | number | string;
style?: Record<string, string>;
noPadding?: boolean;
noTimeline?: boolean;

View file

@ -1,53 +0,0 @@
/*
* 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 { renderHook, act } from '@testing-library/react-hooks';
import { useShowPagesWithEmptyView } from './use_show_pages_with_empty_view';
jest.mock('../route/use_route_spy', () => ({
useRouteSpy: jest
.fn()
.mockImplementationOnce(() => [{ pageName: 'hosts' }])
.mockImplementationOnce(() => [{ pageName: 'rules' }])
.mockImplementationOnce(() => [{ pageName: 'network' }]),
}));
jest.mock('../../containers/sourcerer', () => ({
useSourcererDataView: jest
.fn()
.mockImplementationOnce(() => [{ indicesExist: false }])
.mockImplementationOnce(() => [{ indicesExist: false }])
.mockImplementationOnce(() => [{ indicesExist: true }])
.mockImplementationOnce(() => [{ indicesExist: false }])
.mockImplementationOnce(() => [{ indicesExist: true }]),
}));
describe('use show pages with empty view', () => {
it('shows empty view when on an elligible page and indices do not exist', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook(() => useShowPagesWithEmptyView());
await waitForNextUpdate();
const emptyResult = result.current;
expect(emptyResult).toEqual(true);
});
});
it('does not show empty view when on an inelligible page and indices do not exist', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook(() => useShowPagesWithEmptyView());
await waitForNextUpdate();
const emptyResult = result.current;
expect(emptyResult).toEqual(false);
});
});
it('shows empty view when on an elligible page and indices do exist', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook(() => useShowPagesWithEmptyView());
await waitForNextUpdate();
const emptyResult = result.current;
expect(emptyResult).toEqual(true);
});
});
});

View file

@ -1,42 +0,0 @@
/*
* 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 { useState, useEffect } from 'react';
import { useRouteSpy } from '../route/use_route_spy';
import { SecurityPageName } from '../../../app/types';
import { useSourcererDataView } from '../../containers/sourcerer';
// Used to detect if we're on a top level page that is empty and set page background color to match the subdued Empty State
const isPageNameWithEmptyView = (currentName: string) => {
const pageNamesWithEmptyView: string[] = [
SecurityPageName.hosts,
SecurityPageName.network,
SecurityPageName.timelines,
SecurityPageName.overview,
SecurityPageName.users,
];
return pageNamesWithEmptyView.includes(currentName);
};
export const useShowPagesWithEmptyView = () => {
const [{ pageName }] = useRouteSpy();
const { indicesExist } = useSourcererDataView();
const shouldShowEmptyState = isPageNameWithEmptyView(pageName) && !indicesExist;
const [showEmptyState, setShowEmptyState] = useState(shouldShowEmptyState);
useEffect(() => {
if (shouldShowEmptyState) {
setShowEmptyState(true);
} else {
setShowEmptyState(false);
}
}, [shouldShowEmptyState]);
return showEmptyState;
};

View file

@ -26,11 +26,10 @@ import { Hosts } from './hosts';
import { HostsTabs } from './hosts_tabs';
import { useSourcererDataView } from '../../../common/containers/sourcerer';
import { mockCasesContract } from '@kbn/cases-plugin/public/mocks';
import { LandingPageComponent } from '../../../common/components/landing_page';
import { InputsModelId } from '../../../common/store/inputs/constants';
jest.mock('../../../common/containers/sourcerer');
jest.mock('../../../common/components/landing_page');
// Test will fail because we will to need to mock some core services to make the test work
// For now let's forget about SiemSearchBar and QueryBar
jest.mock('../../../common/components/search_bar', () => ({
@ -107,7 +106,7 @@ describe('Hosts - rendering', () => {
</TestProviders>
);
expect(wrapper.find(LandingPageComponent).exists()).toBe(true);
expect(wrapper.find('[data-test-subj="siem-landing-page"]').exists()).toBe(true);
});
test('it DOES NOT render the Setup Instructions text when an index is available', async () => {

View file

@ -109,6 +109,7 @@ jest.mock('../../../../common/components/search_bar', () => ({
jest.mock('../../../../common/components/query_bar', () => ({
QueryBar: () => null,
}));
jest.mock('../../../../common/components/landing_page');
const getMockHistory = (ip: string) => ({
length: 2,
@ -160,6 +161,10 @@ describe('Network Details', () => {
test('it renders', () => {
const ip = '123.456.78.90';
(useSourcererDataView as jest.Mock).mockReturnValue({
indicesExist: true,
indexPattern: {},
});
(useParams as jest.Mock).mockReturnValue({
detailName: ip,
flowTarget: FlowTargetSourceDest.source,
@ -197,4 +202,24 @@ describe('Network Details', () => {
.text()
).toEqual('fe80::24ce:f7ff:fede:a571');
});
test('it renders landing page component when no indices exist', () => {
const ip = '123.456.78.90';
(useSourcererDataView as jest.Mock).mockReturnValue({
indicesExist: false,
indexPattern: {},
});
(useParams as jest.Mock).mockReturnValue({
detailName: ip,
flowTarget: FlowTargetSourceDest.source,
});
const wrapper = mount(
<TestProviders store={store}>
<Router history={getMockHistory(ip)}>
<NetworkDetails />
</Router>
</TestProviders>
);
expect(wrapper.find('[data-test-subj="siem-landing-page"]').exists()).toBe(true);
});
});

View file

@ -26,9 +26,10 @@ import { inputsActions } from '../../../common/store/inputs';
import { Network } from './network';
import { NetworkRoutes } from './navigation';
import { mockCasesContract } from '@kbn/cases-plugin/public/mocks';
import { LandingPageComponent } from '../../../common/components/landing_page';
import { InputsModelId } from '../../../common/store/inputs/constants';
jest.mock('../../../common/components/landing_page');
jest.mock('../../../common/containers/sourcerer');
// Test will fail because we will to need to mock some core services to make the test work
@ -134,7 +135,7 @@ describe('Network page - rendering', () => {
</TestProviders>
);
expect(wrapper.find(LandingPageComponent).exists()).toBe(true);
expect(wrapper.find(`[data-test-subj="siem-landing-page"]`).exists()).toBe(true);
});
test('it DOES NOT render getting started page when an index is available', async () => {

View file

@ -15,8 +15,8 @@ import { TabNavigation } from '../../../common/components/navigation/tab_navigat
import { Users } from './users';
import { useSourcererDataView } from '../../../common/containers/sourcerer';
import { mockCasesContext } from '@kbn/cases-plugin/public/mocks/mock_cases_context';
import { LandingPageComponent } from '../../../common/components/landing_page';
jest.mock('../../../common/components/landing_page');
jest.mock('../../../common/containers/sourcerer');
jest.mock('../../../common/components/search_bar', () => ({
SiemSearchBar: () => null,
@ -84,7 +84,7 @@ describe('Users - rendering', () => {
</TestProviders>
);
expect(wrapper.find(LandingPageComponent).exists()).toBe(true);
expect(wrapper.find(`[data-test-subj="siem-landing-page"]`).exists()).toBe(true);
});
test('it should render tab navigation', async () => {

View file

@ -15,6 +15,8 @@ import { DataQuality } from './data_quality';
import { HOT, WARM, UNMANAGED } from './translations';
const mockedUseKibana = mockUseKibana();
jest.mock('../../common/components/landing_page');
jest.mock('../../common/lib/kibana', () => {
const original = jest.requireActual('../../common/lib/kibana');

View file

@ -44,6 +44,8 @@ jest.mock('../../common/components/filters_global', () => ({
FiltersGlobal: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}));
jest.mock('../../common/components/landing_page');
const defaultUseSourcererReturn = {
indicesExist: true,
loading: false,

View file

@ -4,65 +4,27 @@
* 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 '@kbn/shared-ux-router';
import { createBrowserHistory } from 'history';
import { TestProviders } from '../../common/mock/test_providers';
import React from 'react';
jest.mock('../../common/lib/kibana', () => ({
useKibana: jest.fn(),
}));
jest.mock('../../common/components/landing_page', () => ({
LandingPageComponent: jest
jest.mock('../../common/components/landing_page');
jest.mock('../../common/components/page_wrapper', () => ({
SecuritySolutionPageWrapper: jest
.fn()
.mockReturnValue(<div data-test-subj="default-get-started-page" />),
.mockImplementation(({ children }) => <div>{children}</div>),
}));
jest.mock('react-use/lib/useObservable', () => jest.fn((fn) => fn()));
const history = createBrowserHistory();
describe('LandingPage', () => {
const mockGetStartedComponent = jest.fn();
const history = createBrowserHistory();
const mockSecuritySolutionTemplateWrapper = jest
.fn()
.mockImplementation(({ children }) => <div>{children}</div>);
const renderPage = () =>
render(
it('renders page properly', () => {
const { queryByTestId } = render(
<Router history={history}>
<LandingPage />
</Router>,
{ wrapper: TestProviders }
</Router>
);
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();
expect(queryByTestId('siem-landing-page')).toBeInTheDocument();
});
});

View file

@ -6,28 +6,28 @@
*/
import React, { memo } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { Chat } from '@kbn/cloud-chat-plugin/public';
import { css } from '@emotion/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';
import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper';
export const LandingPage = memo(() => {
const { getStartedComponent$ } = useKibana().services;
const GetStartedComponent = useObservable(getStartedComponent$);
return (
<>
{GetStartedComponent ?? (
<PluginTemplateWrapper>
<LandingPageComponent />
</PluginTemplateWrapper>
)}
<Chat />
<SpyRoute pageName={SecurityPageName.landing} />
</>
<div
css={css`
display: flex;
flex-direction: column;
flex: 1 1 auto;
`}
>
<SecuritySolutionPageWrapper noPadding>
<LandingPageComponent />
<Chat />
<SpyRoute pageName={SecurityPageName.landing} />
</SecuritySolutionPageWrapper>
</div>
);
});

View file

@ -26,9 +26,9 @@ import { initialUserPrivilegesState } from '../../common/components/user_privile
import type { EndpointPrivileges } from '../../../common/endpoint/types';
import { useRiskScore } from '../../explore/containers/risk_score';
import { mockCasesContract } from '@kbn/cases-plugin/public/mocks';
import { LandingPageComponent } from '../../common/components/landing_page';
const mockNavigateToApp = jest.fn();
jest.mock('../../common/components/landing_page');
jest.mock('../../common/lib/kibana', () => {
const original = jest.requireActual('../../common/lib/kibana');
@ -308,7 +308,7 @@ describe('Overview', () => {
</TestProviders>
);
expect(wrapper.find(LandingPageComponent).exists()).toBe(true);
expect(wrapper.find(`[data-test-subj="siem-landing-page"]`).exists()).toBe(true);
});
});
});

View file

@ -17,13 +17,13 @@ import {
} from '../../common/constants';
import type { SecuritySubPluginRoutes } from '../app/types';
import { LandingPage } from './pages/landing';
import { StatefulOverview } from './pages/overview';
import { DataQuality } from './pages/data_quality';
import { DetectionResponse } from './pages/detection_response';
import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper';
import { EntityAnalyticsPage } from './pages/entity_analytics';
import { SecurityRoutePageWrapper } from '../common/components/security_route_page_wrapper';
import { LandingPage } from './pages/landing';
const OverviewRoutes = () => (
<PluginTemplateWrapper>
@ -42,9 +42,11 @@ const DetectionResponseRoutes = () => (
);
const LandingRoutes = () => (
<TrackApplicationView viewId={SecurityPageName.landing}>
<LandingPage />
</TrackApplicationView>
<PluginTemplateWrapper>
<TrackApplicationView viewId={SecurityPageName.landing}>
<LandingPage />
</TrackApplicationView>
</PluginTemplateWrapper>
);
const EntityAnalyticsRoutes = () => (

View file

@ -26,7 +26,6 @@ const FlyoutPaneComponent: React.FC<FlyoutPaneComponentProps> = ({
}) => {
const { euiTheme } = useEuiTheme();
const ref = useRef<HTMLDivElement>(null);
const timeline = useMemo(
() => (
<StatefulTimeline

View file

@ -28,8 +28,8 @@ import { useSourcererDataView } from '../../../../common/containers/sourcerer';
import { useNetworkDetails } from '../../../../explore/network/containers/details';
import { networkModel } from '../../../../explore/network/store';
import { useAnomaliesTableData } from '../../../../common/components/ml/anomaly/use_anomalies_table_data';
import { LandingCards } from '../../../../common/components/landing_cards';
import { useInstalledSecurityJobNameById } from '../../../../common/components/ml/hooks/use_installed_security_jobs';
import { LandingPageComponent } from '../../../../common/components/landing_page';
interface ExpandableNetworkProps {
expandedNetwork: { ip: string; flowTarget: FlowTargetSourceDest };
@ -147,6 +147,6 @@ export const ExpandableNetworkDetails = ({
jobNameById={jobNameById}
/>
) : (
<LandingCards />
<LandingPageComponent />
);
};

View file

@ -22,7 +22,21 @@ export const GetStartedComponent: React.FC = () => {
const { euiTheme } = useEuiTheme();
return (
<KibanaPageTemplate restrictWidth={false} contentBorder={false}>
<KibanaPageTemplate
restrictWidth={false}
contentBorder={false}
grow={true}
/* this is the only page without padding in Security Solution,
** ignoring main page wrapper padding using absolute positioning
*/
css={css`
top: 0;
right: 0;
bottom: 0;
left: 0;
position: absolute;
`}
>
<KibanaPageTemplate.Header
css={css`
padding: 0 ${euiTheme.base * 2.25}px;

View file

@ -32861,18 +32861,6 @@
"xpack.securitySolution.overview.ilmPhaseHot": "hot",
"xpack.securitySolution.overview.ilmPhaseUnmanaged": "non géré",
"xpack.securitySolution.overview.ilmPhaseWarm": "warm",
"xpack.securitySolution.overview.landingCards.box.cloudCard.desc": "Évaluez votre niveau de cloud et protégez vos charges de travail contre les attaques.",
"xpack.securitySolution.overview.landingCards.box.cloudCard.title": "Protection cloud de bout en bout",
"xpack.securitySolution.overview.landingCards.box.endpoint.desc": "Prévention, collecte, détection et réponse, le tout avec Elastic Agent.",
"xpack.securitySolution.overview.landingCards.box.endpoint.title": "Endpoint Security à grande échelle",
"xpack.securitySolution.overview.landingCards.box.siem.cta": "Ajouter des intégrations de sécurité",
"xpack.securitySolution.overview.landingCards.box.siem.desc": "Grâce à Elastic Security, les équipes sont en mesure de prévenir les menaces, de les détecter et de réagir en conséquence à l'échelle du cloud et grâce à sa vitesse. Ainsi, elles sécurisent les opérations de l'entreprise via une plateforme ouverte unifiée.",
"xpack.securitySolution.overview.landingCards.box.siem.header": "Elastic Security",
"xpack.securitySolution.overview.landingCards.box.siem.title": "La sécurité à la vitesse d'Elastic",
"xpack.securitySolution.overview.landingCards.box.siemCard.desc": "Détection des menaces en constante évolution, investigations et réponses adaptées dans votre environnement.",
"xpack.securitySolution.overview.landingCards.box.siemCard.title": "SIEM pour le centre opérationnel de sécurité moderne",
"xpack.securitySolution.overview.landingCards.box.unify.desc": "Elastic Security modernise les opérations de sécurité en facilitant l'analyse des données collectées au fil des ans, en automatisant les principaux processus et en protégeant chaque hôte.",
"xpack.securitySolution.overview.landingCards.box.unify.title": "Unification du SIEM, de la sécurité aux points de terminaison et de la sécurité cloud",
"xpack.securitySolution.overview.linkPanelLearnMoreButton": "En savoir plus",
"xpack.securitySolution.overview.networkAction": "Afficher le réseau",
"xpack.securitySolution.overview.networkStatGroupAuditbeat": "Auditbeat",

View file

@ -32842,18 +32842,6 @@
"xpack.securitySolution.overview.ilmPhaseHot": "ホット",
"xpack.securitySolution.overview.ilmPhaseUnmanaged": "管理対象外",
"xpack.securitySolution.overview.ilmPhaseWarm": "ウォーム",
"xpack.securitySolution.overview.landingCards.box.cloudCard.desc": "クラウド態勢を評価し、ワークロードを攻撃から保護します。",
"xpack.securitySolution.overview.landingCards.box.cloudCard.title": "エンドツーエンドのクラウド保護",
"xpack.securitySolution.overview.landingCards.box.endpoint.desc": "防御から収集、検知、対応まで実行する、Elastic Agent。",
"xpack.securitySolution.overview.landingCards.box.endpoint.title": "大規模なEndpoint Security",
"xpack.securitySolution.overview.landingCards.box.siem.cta": "セキュリティ統合を追加",
"xpack.securitySolution.overview.landingCards.box.siem.desc": "クラウドのスピードと規模を活かすElasticセキュリティで、脅威の防御、検知、対応まで完結させましょう。一元的でオープンなプラットフォーム1つで、事業経営を安全に維持できます。",
"xpack.securitySolution.overview.landingCards.box.siem.header": "Elasticセキュリティ",
"xpack.securitySolution.overview.landingCards.box.siem.title": "セキュリティも、Elasticの速さで",
"xpack.securitySolution.overview.landingCards.box.siemCard.desc": "環境で進化する脅威を検知、調査、対応。",
"xpack.securitySolution.overview.landingCards.box.siemCard.title": "最先端を行くSOCのSIEM",
"xpack.securitySolution.overview.landingCards.box.unify.desc": "Elasticセキュリティは数年分に及ぶデータの分析を可能にするほか、主要プロセスを自動化し、全ホストを保護して、最先端のセキュリティ運用を実現します。",
"xpack.securitySolution.overview.landingCards.box.unify.title": "SIEM、エンドポイントセキュリティ、クラウドセキュリティを一体化",
"xpack.securitySolution.overview.linkPanelLearnMoreButton": "詳細情報",
"xpack.securitySolution.overview.networkAction": "ネットワークを表示",
"xpack.securitySolution.overview.networkStatGroupAuditbeat": "Auditbeat",

View file

@ -32838,18 +32838,6 @@
"xpack.securitySolution.overview.ilmPhaseHot": "热",
"xpack.securitySolution.overview.ilmPhaseUnmanaged": "未受管",
"xpack.securitySolution.overview.ilmPhaseWarm": "温",
"xpack.securitySolution.overview.landingCards.box.cloudCard.desc": "评估您的云态势并防止工作负载受到攻击。",
"xpack.securitySolution.overview.landingCards.box.cloudCard.title": "端到端云防护",
"xpack.securitySolution.overview.landingCards.box.endpoint.desc": "防御、收集、检测和响应 — 所有这些活动均可通过 Elastic 代理来实现。",
"xpack.securitySolution.overview.landingCards.box.endpoint.title": "规模化终端安全",
"xpack.securitySolution.overview.landingCards.box.siem.cta": "添加安全集成",
"xpack.securitySolution.overview.landingCards.box.siem.desc": "借助 Elastic Security团队能够以云技术所带来的规模和速度来防御、检测和应对各种威胁在一体化的开放平台上保护业务运营的安全。",
"xpack.securitySolution.overview.landingCards.box.siem.header": "Elastic Security",
"xpack.securitySolution.overview.landingCards.box.siem.title": "以 Elastic 的速度实现安全保护",
"xpack.securitySolution.overview.landingCards.box.siemCard.desc": "检测、调查并响应您环境中不断变化的威胁。",
"xpack.securitySolution.overview.landingCards.box.siemCard.title": "适用于现代 SOC 的 SIEM",
"xpack.securitySolution.overview.landingCards.box.unify.desc": "Elastic Security 实现了安全运营现代化,能够对多年的数据进行分析,自动执行关键流程,并保护每台主机。",
"xpack.securitySolution.overview.landingCards.box.unify.title": "集 SIEM、Endpoint Security 和云安全于一体",
"xpack.securitySolution.overview.linkPanelLearnMoreButton": "了解详情",
"xpack.securitySolution.overview.networkAction": "查看网络",
"xpack.securitySolution.overview.networkStatGroupAuditbeat": "Auditbeat",