Skip to content

Commit 11a512e

Browse files
[AI4DSOC] Alert summary landing page (#215246)
## Summary This PR continues the work done in #214889 and implements the landing page for the new alert summary page. This landing page should only be visible by users if none of the AI for SOC integrations have been installed (more info in [the previous PR](#214889)). The landing page consist of 2 main sections: - the top section with a title and an image - the bottom section where we list the top 2 AI for SOC integrations - Splunk and GoogleSecOps - which are clickable and will redirect the users to the respective integration detail pages, as well as a `View all integrations` button which will redirect the users to the integrations page. ![Screenshot 2025-03-19 at 3 37 46 PM](https://github.com/user-attachments/assets/311bb9b9-1bd3-4c7a-bcb9-f929d459aa70) https://github.com/user-attachments/assets/0d15a65d-7f2e-4e2d-9919-896f5532f08c Link to mocks: https://www.figma.com/design/DYs7j4GQdAhg7aWTLI4R69/AI4DSOC?node-id=4408-128249&t=GaxMP8OEZ9Qsjl0R-0 ### Notes - The current image is only temporary and acts as a placeholder while the UIUX team is creating a gif or video (no ETA on when it will be available). - The integration links are subject to change in the future, but that work is handled by a different team and as not being completed yet ## How to test This needs to be ran in Serverless: - `yarn es serverless --projectType security` - `yarn serverless-security --no-base-path` You also need to enable the AI for SOC tier, by adding the following to your `serverless.security.dev.yaml` file: ``` xpack.securitySolutionServerless.productTypes: [ { product_line: 'ai_soc', product_tier: 'search_ai_lake' }, ] ``` Use one of these Serverless users: - `platform_engineer` - `endpoint_operations_analyst` - `endpoint_policy_manager` - `admin` - `system_indices_superuser` ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [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 Contributes to elastic/security-team#11979
1 parent 2160dd1 commit 11a512e

File tree

10 files changed

+379
-70
lines changed

10 files changed

+379
-70
lines changed

x-pack/platform/plugins/shared/fleet/public/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export { pagePathGetters, EPM_API_ROUTES } from './constants';
6161
export { pkgKeyFromPackageInfo } from './services';
6262
export type { CustomAssetsAccordionProps } from './components/custom_assets_accordion';
6363
export { CustomAssetsAccordion } from './components/custom_assets_accordion';
64-
export { PackageIcon } from './components/package_icon';
64+
export { CardIcon, PackageIcon } from './components/package_icon';
6565
// Export Package editor components for custom editors
6666
export { PackagePolicyEditorDatastreamPipelines } from './applications/fleet/sections/agent_policy/create_package_policy_page/components/datastream_pipelines';
6767
export type { PackagePolicyEditorDatastreamPipelinesProps } from './applications/fleet/sections/agent_policy/create_package_policy_page/components/datastream_pipelines';
@@ -93,3 +93,4 @@ export const AvailablePackagesHook = () => {
9393

9494
export { useGetPackagesQuery } from './hooks/use_request/epm';
9595
export { useGetSettingsQuery } from './hooks/use_request/settings';
96+
export { useLink } from './hooks/use_link';
99.1 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { render } from '@testing-library/react';
10+
import type { PackageListItem } from '@kbn/fleet-plugin/common';
11+
import { installationStatuses } from '@kbn/fleet-plugin/common/constants';
12+
import { IntegrationCard } from './integration_card';
13+
import { useKibana } from '@kbn/kibana-react-plugin/public';
14+
15+
jest.mock('@kbn/kibana-react-plugin/public');
16+
17+
const dataTestSubj = 'test-id';
18+
const integration: PackageListItem = {
19+
id: 'splunk',
20+
icons: [{ src: 'icon.svg', path: 'mypath/icon.svg', type: 'image/svg+xml' }],
21+
name: 'splunk',
22+
status: installationStatuses.NotInstalled,
23+
title: 'Splunk',
24+
version: '0.1.0',
25+
};
26+
27+
describe('<IntegrationCard />', () => {
28+
it('should render the card and navigate to the integration details page', () => {
29+
const navigateToApp = jest.fn();
30+
(useKibana as jest.Mock).mockReturnValue({
31+
services: {
32+
application: { navigateToApp },
33+
http: {
34+
basePath: {
35+
prepend: jest.fn().mockReturnValue('/app/integrations/detail/splunk-0.1.0/overview'),
36+
},
37+
},
38+
},
39+
});
40+
41+
const { getByTestId } = render(
42+
<IntegrationCard integration={integration} data-test-subj={dataTestSubj} />
43+
);
44+
45+
const card = getByTestId(dataTestSubj);
46+
47+
expect(card).toHaveTextContent('Splunk');
48+
expect(card).toHaveTextContent('SIEM');
49+
50+
card.click();
51+
52+
expect(navigateToApp).toHaveBeenCalledWith('integrations', {
53+
path: '/detail/splunk-0.1.0/overview',
54+
});
55+
});
56+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React, { memo, useCallback } from 'react';
9+
import { css } from '@emotion/react';
10+
import { EuiBadge, EuiCard } from '@elastic/eui';
11+
import type { PackageListItem } from '@kbn/fleet-plugin/common';
12+
import { INTEGRATIONS_PLUGIN_ID } from '@kbn/fleet-plugin/common';
13+
import { CardIcon, useLink } from '@kbn/fleet-plugin/public';
14+
import { i18n } from '@kbn/i18n';
15+
import { useKibana } from '../../../../common/lib/kibana';
16+
17+
const SIEM_BADGE = i18n.translate('xpack.securitySolution.alertSummary.integrations.siemBadge', {
18+
defaultMessage: 'SIEM',
19+
});
20+
21+
const MIN_WIDTH = 275; // px
22+
const INTEGRATIONS_BASE_PATH = '/app/integrations';
23+
const INTEGRATION_DETAILS_PAGE = 'integration_details_overview';
24+
25+
export interface IntegrationCardProps {
26+
/**
27+
* AI for SOC integration available to install
28+
*/
29+
integration: PackageListItem;
30+
/**
31+
* Data test subject string for testing
32+
*/
33+
['data-test-subj']?: string;
34+
}
35+
36+
/**
37+
* Rendered on the alert summary landing page, when no integrations have been installed.
38+
* The card is clickable and will navigate the user to the integration's details page.
39+
*/
40+
export const IntegrationCard = memo(
41+
({ integration, 'data-test-subj': dataTestSubj }: IntegrationCardProps) => {
42+
const {
43+
services: { application },
44+
} = useKibana();
45+
const { getHref } = useLink();
46+
47+
const onClick = useCallback(() => {
48+
const url = getHref(INTEGRATION_DETAILS_PAGE, {
49+
pkgkey: `${integration.name}-${integration.version}`,
50+
...(integration.integration ? { integration: integration.integration } : {}),
51+
});
52+
53+
application.navigateToApp(INTEGRATIONS_PLUGIN_ID, {
54+
path: url.slice(INTEGRATIONS_BASE_PATH.length),
55+
});
56+
}, [application, getHref, integration.integration, integration.name, integration.version]);
57+
58+
return (
59+
<EuiCard
60+
css={css`
61+
min-width: ${MIN_WIDTH}px;
62+
`}
63+
data-test-subj={dataTestSubj}
64+
description={<EuiBadge color="hollow">{SIEM_BADGE}</EuiBadge>}
65+
display="plain"
66+
hasBorder
67+
icon={
68+
<CardIcon
69+
icons={integration.icons}
70+
integrationName={integration.title}
71+
packageName={integration.name}
72+
size="xl"
73+
version={integration.version}
74+
/>
75+
}
76+
layout="horizontal"
77+
onClick={onClick}
78+
titleSize="xs"
79+
title={integration.title}
80+
/>
81+
);
82+
}
83+
);
84+
85+
IntegrationCard.displayName = 'IntegrationCard';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { render } from '@testing-library/react';
10+
import {
11+
LANDING_PAGE_CARD_TEST_ID,
12+
LANDING_PAGE_IMAGE_TEST_ID,
13+
LANDING_PAGE_PROMPT_TEST_ID,
14+
LANDING_PAGE_VIEW_ALL_INTEGRATIONS_BUTTON_TEST_ID,
15+
LandingPage,
16+
} from './landing_page';
17+
import { useAddIntegrationsUrl } from '../../../../common/hooks/use_add_integrations_url';
18+
import type { PackageListItem } from '@kbn/fleet-plugin/common';
19+
import { installationStatuses } from '@kbn/fleet-plugin/common/constants';
20+
import { useKibana } from '../../../../common/lib/kibana';
21+
22+
jest.mock('../../../../common/hooks/use_add_integrations_url');
23+
jest.mock('../../../../common/lib/kibana');
24+
25+
const packages: PackageListItem[] = [
26+
{
27+
id: 'splunk',
28+
name: 'splunk',
29+
status: installationStatuses.NotInstalled,
30+
title: 'Splunk',
31+
version: '',
32+
},
33+
{
34+
id: 'google_secops',
35+
name: 'google_secops',
36+
status: installationStatuses.NotInstalled,
37+
title: 'Google SecOps',
38+
version: '',
39+
},
40+
{
41+
id: 'unknown',
42+
name: 'unknown',
43+
status: installationStatuses.NotInstalled,
44+
title: 'Unknown',
45+
version: '',
46+
},
47+
];
48+
49+
describe('<LandingPage />', () => {
50+
beforeEach(() => {
51+
(useKibana as jest.Mock).mockReturnValue({
52+
services: { application: { navigateToApp: jest.fn() } },
53+
});
54+
});
55+
56+
it('should render all the components', () => {
57+
(useAddIntegrationsUrl as jest.Mock).mockReturnValue({
58+
onClick: jest.fn(),
59+
});
60+
61+
const { getByTestId, queryByTestId } = render(<LandingPage packages={packages} />);
62+
63+
expect(getByTestId(LANDING_PAGE_PROMPT_TEST_ID)).toHaveTextContent(
64+
'All your alerts in one place with AI'
65+
);
66+
expect(getByTestId(LANDING_PAGE_PROMPT_TEST_ID)).toHaveTextContent(
67+
'Bring in your SIEM data to begin surfacing alerts'
68+
);
69+
70+
expect(getByTestId(LANDING_PAGE_IMAGE_TEST_ID)).toBeInTheDocument();
71+
expect(getByTestId(`${LANDING_PAGE_CARD_TEST_ID}splunk`)).toBeInTheDocument();
72+
expect(getByTestId(`${LANDING_PAGE_CARD_TEST_ID}google_secops`)).toBeInTheDocument();
73+
expect(queryByTestId(`${LANDING_PAGE_CARD_TEST_ID}unknown`)).not.toBeInTheDocument();
74+
expect(getByTestId(LANDING_PAGE_VIEW_ALL_INTEGRATIONS_BUTTON_TEST_ID)).toBeInTheDocument();
75+
});
76+
77+
it('should navigate to the fleet page when clicking on the more integrations button', () => {
78+
const moreIntegrations = jest.fn();
79+
(useAddIntegrationsUrl as jest.Mock).mockReturnValue({
80+
onClick: moreIntegrations,
81+
});
82+
83+
const { getByTestId } = render(<LandingPage packages={packages} />);
84+
85+
getByTestId(LANDING_PAGE_VIEW_ALL_INTEGRATIONS_BUTTON_TEST_ID).click();
86+
expect(moreIntegrations).toHaveBeenCalled();
87+
});
88+
});

x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/landing_page/landing_page.tsx

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,50 @@
55
* 2.0.
66
*/
77

8-
import React, { memo } from 'react';
8+
import React, { memo, useMemo } from 'react';
9+
import { css } from '@emotion/react';
10+
import {
11+
EuiButtonEmpty,
12+
EuiFlexGroup,
13+
EuiFlexItem,
14+
EuiImage,
15+
EuiSpacer,
16+
EuiText,
17+
EuiTitle,
18+
useEuiTheme,
19+
} from '@elastic/eui';
920
import type { PackageListItem } from '@kbn/fleet-plugin/common';
10-
import { EuiText } from '@elastic/eui';
21+
import { i18n } from '@kbn/i18n';
22+
import { useAddIntegrationsUrl } from '../../../../common/hooks/use_add_integrations_url';
23+
import { IntegrationCard } from './integration_card';
24+
import imageSrc from './alert_summary.png';
25+
26+
const TITLE = i18n.translate('xpack.securitySolution.alertSummary.landingPage.title', {
27+
defaultMessage: 'All your alerts in one place with AI',
28+
});
29+
const SUB_TITLE = i18n.translate('xpack.securitySolution.alertSummary.landingPage.subTitle', {
30+
defaultMessage: 'Bring in your SIEM data to begin surfacing alerts',
31+
});
32+
const DATA_TITLE = i18n.translate('xpack.securitySolution.alertSummary.landingPage.dataTitle', {
33+
defaultMessage: 'Start by connecting your data',
34+
});
35+
const VIEW_ALL_INTEGRATIONS = i18n.translate(
36+
'xpack.securitySolution.alertSummary.landingPage.viewAllIntegrationsButtonLabel',
37+
{
38+
defaultMessage: 'View all integrations',
39+
}
40+
);
41+
42+
const PRIMARY_INTEGRATIONS = [
43+
'splunk', // doesnt yet exist
44+
'google_secops',
45+
];
1146

1247
export const LANDING_PAGE_PROMPT_TEST_ID = 'alert-summary-landing-page-prompt';
48+
export const LANDING_PAGE_IMAGE_TEST_ID = 'alert-summary-landing-page-image';
49+
export const LANDING_PAGE_CARD_TEST_ID = 'alert-summary-landing-page-card-';
50+
export const LANDING_PAGE_VIEW_ALL_INTEGRATIONS_BUTTON_TEST_ID =
51+
'alert-summary-landing-page-view-all-integrations-button';
1352

1453
export interface LandingPageProps {
1554
/**
@@ -23,7 +62,94 @@ export interface LandingPageProps {
2362
* This page is rendered when no AI for SOC packages are installed.
2463
*/
2564
export const LandingPage = memo(({ packages }: LandingPageProps) => {
26-
return <EuiText data-test-subj={LANDING_PAGE_PROMPT_TEST_ID}>{'Landing page'}</EuiText>;
65+
const { euiTheme } = useEuiTheme();
66+
const { onClick: moreIntegrations } = useAddIntegrationsUrl(); // TODO this link might have to be revisited once the integration work is done
67+
68+
// We only want to show the 2 top integrations, Splunk and GoogleSecOps, in that specific order
69+
const primaryPackages = useMemo(
70+
() =>
71+
packages
72+
.filter((pkg) => PRIMARY_INTEGRATIONS.includes(pkg.name))
73+
.sort(
74+
(a, b) => PRIMARY_INTEGRATIONS.indexOf(a.name) - PRIMARY_INTEGRATIONS.indexOf(b.name)
75+
),
76+
[packages]
77+
);
78+
79+
return (
80+
<EuiFlexGroup data-test-subj={LANDING_PAGE_PROMPT_TEST_ID} direction="column" gutterSize="xl">
81+
<EuiFlexItem
82+
css={css`
83+
// to have the background color correctly applied
84+
// we need to compensate for the 24px padding that is applied to a parent component
85+
//(see here x-pack/solutions/security/plugins/security_solution/public/app/home/template_wrapper/index.tsx)
86+
margin: -${euiTheme.size.l} -${euiTheme.size.l} 0 -${euiTheme.size.l};
87+
`}
88+
>
89+
<EuiFlexGroup
90+
alignItems="center"
91+
direction="column"
92+
css={css`
93+
background-color: ${euiTheme.colors.backgroundBaseSubdued};
94+
`}
95+
>
96+
<EuiFlexItem>
97+
<EuiSpacer />
98+
</EuiFlexItem>
99+
<EuiFlexItem>
100+
<EuiTitle size="l">
101+
<h1>{TITLE}</h1>
102+
</EuiTitle>
103+
</EuiFlexItem>
104+
<EuiFlexItem>
105+
<EuiText>{SUB_TITLE}</EuiText>
106+
</EuiFlexItem>
107+
<EuiFlexItem>
108+
<EuiImage // TODO replace the image with a proper gif or video once provided by UIUX
109+
data-test-subj={LANDING_PAGE_IMAGE_TEST_ID}
110+
size="original"
111+
role="presentation"
112+
alt=""
113+
src={imageSrc}
114+
/>
115+
</EuiFlexItem>
116+
<EuiFlexItem>
117+
<EuiSpacer />
118+
</EuiFlexItem>
119+
</EuiFlexGroup>
120+
</EuiFlexItem>
121+
<EuiFlexItem>
122+
<EuiFlexGroup alignItems="center" direction="column">
123+
<EuiFlexItem>
124+
<EuiTitle size="s">
125+
<h3>{DATA_TITLE}</h3>
126+
</EuiTitle>
127+
</EuiFlexItem>
128+
<EuiFlexItem>
129+
<EuiFlexGroup gutterSize="m" alignItems="center">
130+
{primaryPackages.map((pkg) => (
131+
<EuiFlexItem grow={false} key={pkg.name}>
132+
<IntegrationCard
133+
integration={pkg}
134+
data-test-subj={`${LANDING_PAGE_CARD_TEST_ID}${pkg.name}`}
135+
/>
136+
</EuiFlexItem>
137+
))}
138+
</EuiFlexGroup>
139+
</EuiFlexItem>
140+
<EuiFlexItem grow={false}>
141+
<EuiButtonEmpty
142+
data-test-subj={LANDING_PAGE_VIEW_ALL_INTEGRATIONS_BUTTON_TEST_ID}
143+
iconType="plusInCircle"
144+
onClick={moreIntegrations}
145+
>
146+
{VIEW_ALL_INTEGRATIONS}
147+
</EuiButtonEmpty>
148+
</EuiFlexItem>
149+
</EuiFlexGroup>
150+
</EuiFlexItem>
151+
</EuiFlexGroup>
152+
);
27153
});
28154

29155
LandingPage.displayName = 'LandingPage';

0 commit comments

Comments
 (0)