Skip to content

Commit 3605a33

Browse files
[AI4DSOC] Alert summary page routing and initialization (#214889)
## Summary This PR is the setting the foundations for the AI for SOC Alert summary page. It has very little UI, instead it focuses on the following: - add routing for the `alert_summary` page - fetches the integrations, filters them to only keep the ones related to AI for SOC, then decides what to render depending on if some AI for SOC packages have been installed or not The PR also makes a small change to the `SecurityRoutePageWrapper` component, to allow us to redirect to the Security Solution HomePage instead of the NoPrivilegesPage. While this might not be a long term solution, it is the easiest path forward. In the future, AI for SOC will most likely be its own plugin (leaving outside of Security Solution) hence this will not be needed anymore. Here's the basic behavior of the Alert summary page: - The `Landing page` will be shown if none of the hardcoded AI for SOC packages are installed (these values are hardcoded as we currently do not have a way to filter integrations for the AI for SOC ones only): - splunk // doesnt yet exist - google_secops - microsoft_sentinel - sentinel_one - crowdstrike - The `Wrapper` component will only be shown if you have at least one of the above AI for SOC packages installed. ### Very limited UI added in this PR | Loading integrations | No installed packages | Some installed packages | | ------------- | ------------- | ------------- | | ![Screenshot 2025-03-17 at 6 58 45 PM](https://github.com/user-attachments/assets/68089c33-fa40-4201-8b51-3e7236d50d5a) | ![Screenshot 2025-03-17 at 6 59 15 PM](https://github.com/user-attachments/assets/e7e5af2d-bdab-4bef-881e-bb5e512c3545) | ![Screenshot 2025-03-17 at 6 59 40 PM](https://github.com/user-attachments/assets/61b346bb-799f-4a0b-95cb-e3092ea58d37) | ### Notes We need to remove the section at the top of the page that currently shows the `Add integrations` button. A follow PR will take care of that. [This](https://github.com/elastic/kibana/blob/main/x-pack/solutions/security/plugins/security_solution/public/app/home/index.tsx#L54) is where that bar is being added. We will have to find a way to not show that for the AI for SOC tier. ## 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' }, ] ``` The Alert summary navigation will NOT be shown for the following Serverless users: `viewer`, `t1_analyst`. and `t2_analyst`. For those, the navigation entry is not present, and navigating to the url directly will automatically re-route to the Security home page. Currently, retrieving the integrations (via the `fleet/epm/packages` endpoint) is also unauthorized for the following users: `editor`, `t3_analyst`, `threat_intelligence_analyst`, `rule_author`, `soc_manager` and `detections_admin`. This means that the only users that can be currently used to test this PR are: - `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 Will help close elastic/security-team#11954 as well as elastic/security-team#11979.
1 parent 108716d commit 3605a33

14 files changed

Lines changed: 512 additions & 51 deletions

File tree

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2298,6 +2298,7 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/
22982298

22992299
/x-pack/solutions/security/plugins/security_solution/common/timelines @elastic/security-threat-hunting-investigations
23002300
/x-pack/solutions/security/plugins/security_solution/public/common/components/alerts_viewer @elastic/security-threat-hunting-investigations
2301+
/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary @elastic/security-threat-hunting-investigations
23012302
/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/timeline_action @elastic/security-threat-hunting-investigations
23022303
/x-pack/solutions/security/plugins/security_solution/public/common/components/event_details @elastic/security-threat-hunting-investigations
23032304
/x-pack/solutions/security/plugins/security_solution/public/common/components/events_viewer @elastic/security-threat-hunting-investigations
@@ -2317,6 +2318,7 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/
23172318
/x-pack/solutions/security/plugins/security_solution/public/flyout/rule_details @elastic/security-threat-hunting-investigations
23182319
/x-pack/solutions/security/plugins/security_solution/public/investigations @elastic/security-threat-hunting-investigations
23192320
/x-pack/solutions/security/plugins/security_solution/public/detections/configurations/security_solution_detections @elastic/security-threat-hunting-investigations
2321+
/x-pack/solutions/security/plugins/security_solution/public/detections/pages/detections/alert_summary @elastic/security-threat-hunting-investigations
23202322
/x-pack/solutions/security/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @elastic/security-threat-hunting-investigations
23212323
/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_resolve_conflict.tsx @elastic/security-threat-hunting-investigations
23222324
/x-pack/solutions/security/plugins/security_solution/public/common/components/drag_and_drop @elastic/security-threat-hunting-investigations

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { PluginInitializerContext } from '@kbn/core/public';
1010
import { lazy } from 'react';
1111

1212
import { FleetPlugin } from './plugin';
13+
1314
export type { GetPackagesResponse } from './types';
1415
export { installationStatuses } from '../common/constants';
1516

@@ -89,3 +90,6 @@ export const AvailablePackagesHook = () => {
8990
'./applications/integrations/sections/epm/screens/home/hooks/use_available_packages'
9091
);
9192
};
93+
94+
export { useGetPackagesQuery } from './hooks/use_request/epm';
95+
export { useGetSettingsQuery } from './hooks/use_request/settings';

x-pack/solutions/security/plugins/security_solution/public/app/translations.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ export const ALERTS = i18n.translate('xpack.securitySolution.navigation.alerts',
112112
defaultMessage: 'Alerts',
113113
});
114114

115+
export const ALERT_SUMMARY = i18n.translate('xpack.securitySolution.navigation.alertSummary', {
116+
defaultMessage: 'Alert summary',
117+
});
118+
115119
export const ATTACK_DISCOVERY = i18n.translate(
116120
'xpack.securitySolution.navigation.attackDiscovery',
117121
{

x-pack/solutions/security/plugins/security_solution/public/common/components/security_route_page_wrapper/index.test.tsx

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,17 @@ import { SecurityPageName } from '../../../../common';
1313
import { TestProviders } from '../../mock';
1414
import { generateHistoryMock } from '../../utils/route/mocks';
1515
import type { LinkInfo } from '../../links';
16+
import { useLinkInfo } from '../../links';
17+
import { useUpsellingPage } from '../../hooks/use_upselling';
18+
19+
jest.mock('../../links');
20+
jest.mock('../../hooks/use_upselling');
1621

1722
const defaultLinkInfo: LinkInfo = {
1823
id: SecurityPageName.exploreLanding,
1924
title: 'test',
2025
path: '/test',
2126
};
22-
const mockGetLink = jest.fn((): LinkInfo | undefined => defaultLinkInfo);
23-
jest.mock('../../links', () => ({
24-
useLinkInfo: () => mockGetLink(),
25-
}));
26-
27-
const mockUseUpsellingPage = jest.fn();
28-
jest.mock('../../hooks/use_upselling', () => ({
29-
useUpsellingPage: () => mockUseUpsellingPage(),
30-
}));
3127

3228
const REDIRECT_COMPONENT_SUBJ = 'redirect-component';
3329
const mockRedirect = jest.fn(() => <div data-test-subj={REDIRECT_COMPONENT_SUBJ} />);
@@ -47,36 +43,87 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
4743
);
4844

4945
describe('SecurityRoutePageWrapper', () => {
50-
it('should render children when authorized', () => {
51-
mockGetLink.mockReturnValueOnce({ ...defaultLinkInfo }); // authorized
46+
beforeEach(() => {
47+
jest.clearAllMocks();
48+
});
49+
50+
it('should render UpsellPage when it is available', () => {
51+
const TEST_ID = 'test-upsell-page';
52+
const TestUpsellPage = () => <div data-test-subj={TEST_ID} />;
53+
54+
(useLinkInfo as jest.Mock).mockReturnValue(defaultLinkInfo);
55+
(useUpsellingPage as jest.Mock).mockReturnValue(TestUpsellPage);
56+
5257
const { getByTestId } = render(
5358
<SecurityRoutePageWrapper pageName={SecurityPageName.exploreLanding}>
5459
<TestComponent />
5560
</SecurityRoutePageWrapper>,
5661
{ wrapper: Wrapper }
5762
);
5863

59-
expect(getByTestId(TEST_COMPONENT_SUBJ)).toBeInTheDocument();
64+
expect(getByTestId(TEST_ID)).toBeInTheDocument();
65+
});
66+
67+
it('should redirect when link missing and redirectOnMissing flag present', () => {
68+
(useLinkInfo as jest.Mock).mockReturnValue(undefined);
69+
(useUpsellingPage as jest.Mock).mockReturnValue(undefined);
70+
71+
const { getByTestId } = render(
72+
<SecurityRoutePageWrapper pageName={SecurityPageName.exploreLanding} redirectOnMissing>
73+
<TestComponent />
74+
</SecurityRoutePageWrapper>,
75+
{ wrapper: Wrapper }
76+
);
77+
78+
expect(getByTestId(REDIRECT_COMPONENT_SUBJ)).toBeInTheDocument();
79+
});
80+
81+
it('should redirect when link missing and redirectIfUnauthorized flag present', () => {
82+
(useLinkInfo as jest.Mock).mockReturnValue(undefined);
83+
(useUpsellingPage as jest.Mock).mockReturnValue(undefined);
84+
85+
const { getByTestId } = render(
86+
<SecurityRoutePageWrapper pageName={SecurityPageName.exploreLanding} redirectIfUnauthorized>
87+
<TestComponent />
88+
</SecurityRoutePageWrapper>,
89+
{ wrapper: Wrapper }
90+
);
91+
92+
expect(getByTestId(REDIRECT_COMPONENT_SUBJ)).toBeInTheDocument();
6093
});
6194

62-
it('should render UpsellPage when unauthorized and UpsellPage is available', () => {
63-
const TestUpsellPage = () => <div data-test-subj={'test-upsell-page'} />;
95+
it('should redirect when link is unauthorized and redirectIfUnauthorized flag present', () => {
96+
(useLinkInfo as jest.Mock).mockReturnValue({ ...defaultLinkInfo, unauthorized: true });
97+
(useUpsellingPage as jest.Mock).mockReturnValue(undefined);
98+
99+
const { getByTestId } = render(
100+
<SecurityRoutePageWrapper pageName={SecurityPageName.exploreLanding} redirectIfUnauthorized>
101+
<TestComponent />
102+
</SecurityRoutePageWrapper>,
103+
{ wrapper: Wrapper }
104+
);
105+
106+
expect(getByTestId(REDIRECT_COMPONENT_SUBJ)).toBeInTheDocument();
107+
});
108+
109+
it('should render NoPrivilegesPage when link missing and UpsellPage is undefined', () => {
110+
(useLinkInfo as jest.Mock).mockReturnValue(undefined);
111+
(useUpsellingPage as jest.Mock).mockReturnValue(undefined);
64112

65-
mockGetLink.mockReturnValueOnce({ ...defaultLinkInfo, unauthorized: true });
66-
mockUseUpsellingPage.mockReturnValue(TestUpsellPage);
67113
const { getByTestId } = render(
68114
<SecurityRoutePageWrapper pageName={SecurityPageName.exploreLanding}>
69115
<TestComponent />
70116
</SecurityRoutePageWrapper>,
71117
{ wrapper: Wrapper }
72118
);
73119

74-
expect(getByTestId('test-upsell-page')).toBeInTheDocument();
120+
expect(getByTestId('noPrivilegesPage')).toBeInTheDocument();
75121
});
76122

77-
it('should render NoPrivilegesPage when unauthorized and UpsellPage is unavailable', () => {
78-
mockGetLink.mockReturnValueOnce({ ...defaultLinkInfo, unauthorized: true });
79-
mockUseUpsellingPage.mockReturnValue(undefined);
123+
it('should render NoPrivilegesPage when unauthorized and UpsellPage is undefined', () => {
124+
(useLinkInfo as jest.Mock).mockReturnValue({ ...defaultLinkInfo, unauthorized: true });
125+
(useUpsellingPage as jest.Mock).mockReturnValue(undefined);
126+
80127
const { getByTestId } = render(
81128
<SecurityRoutePageWrapper pageName={SecurityPageName.exploreLanding}>
82129
<TestComponent />
@@ -87,16 +134,17 @@ describe('SecurityRoutePageWrapper', () => {
87134
expect(getByTestId('noPrivilegesPage')).toBeInTheDocument();
88135
});
89136

90-
it('should redirect when link missing and redirectOnMissing flag present', () => {
91-
mockGetLink.mockReturnValueOnce(undefined);
137+
it('should render children when authorized', () => {
138+
(useLinkInfo as jest.Mock).mockReturnValue(defaultLinkInfo);
139+
(useUpsellingPage as jest.Mock).mockReturnValue(undefined);
92140

93141
const { getByTestId } = render(
94-
<SecurityRoutePageWrapper pageName={SecurityPageName.exploreLanding} redirectOnMissing>
142+
<SecurityRoutePageWrapper pageName={SecurityPageName.exploreLanding}>
95143
<TestComponent />
96144
</SecurityRoutePageWrapper>,
97145
{ wrapper: Wrapper }
98146
);
99147

100-
expect(getByTestId(REDIRECT_COMPONENT_SUBJ)).toBeInTheDocument();
148+
expect(getByTestId(TEST_COMPONENT_SUBJ)).toBeInTheDocument();
101149
});
102150
});

x-pack/solutions/security/plugins/security_solution/public/common/components/security_route_page_wrapper/index.tsx

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import { SpyRoute } from '../../utils/route/spy_routes';
1818
interface SecurityRoutePageWrapperProps {
1919
pageName: SecurityPageName;
2020
redirectOnMissing?: boolean;
21+
/**
22+
* Used primarily in the AI for SOC tier, to allow redirecting to the home page instead of showing the NoPrivileges page.
23+
*/
24+
redirectIfUnauthorized?: boolean;
2125
}
2226

2327
/**
@@ -40,13 +44,14 @@ interface SecurityRoutePageWrapperProps {
4044
export const SecurityRoutePageWrapper: FC<PropsWithChildren<SecurityRoutePageWrapperProps>> = ({
4145
children,
4246
pageName,
47+
redirectIfUnauthorized,
4348
redirectOnMissing,
4449
}) => {
4550
const link = useLinkInfo(pageName);
46-
const UpsellingPage = useUpsellingPage(pageName);
4751

48-
// The upselling page is only returned when the license/product requirements are not met,
52+
// The upselling page is only returned when the license/product requirements are not met.
4953
// When it is defined it must be rendered, no need to check anything else.
54+
const UpsellingPage = useUpsellingPage(pageName);
5055
if (UpsellingPage) {
5156
return (
5257
<>
@@ -56,28 +61,38 @@ export const SecurityRoutePageWrapper: FC<PropsWithChildren<SecurityRoutePageWra
5661
);
5762
}
5863

64+
// Allows a redirect to the home page.
65+
if (redirectOnMissing && link == null) {
66+
return <Redirect to="" />;
67+
}
68+
5969
const isAuthorized = link != null && !link.unauthorized;
60-
if (isAuthorized) {
70+
71+
// Allows a redirect to the home page if the link is undefined or unauthorized.
72+
// This is used in the AI for SOC tier (for the Alert Summary page for example), as it does not make sense to show the NoPrivilegesPage.
73+
if (redirectIfUnauthorized && !isAuthorized) {
74+
return <Redirect to="" />;
75+
}
76+
77+
// Show the no privileges page if the link is undefined or unauthorized.
78+
if (!isAuthorized) {
6179
return (
62-
<TrackApplicationView viewId={pageName}>
63-
{children}
80+
<>
6481
<SpyRoute pageName={pageName} />
65-
</TrackApplicationView>
82+
<NoPrivilegesPage
83+
pageName={pageName}
84+
docLinkSelector={(docLinks) => docLinks.siem.privileges}
85+
/>
86+
</>
6687
);
6788
}
6889

69-
if (redirectOnMissing && link == null) {
70-
return <Redirect to="" />; // redirects to the home page
71-
}
72-
90+
// Show the actual application page.
7391
return (
74-
<>
92+
<TrackApplicationView viewId={pageName}>
93+
{children}
7594
<SpyRoute pageName={pageName} />
76-
<NoPrivilegesPage
77-
pageName={pageName}
78-
docLinkSelector={(docLinks) => docLinks.siem.privileges}
79-
/>
80-
</>
95+
</TrackApplicationView>
8196
);
8297
};
8398

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 } from 'react';
9+
import type { PackageListItem } from '@kbn/fleet-plugin/common';
10+
import { EuiText } from '@elastic/eui';
11+
12+
export const LANDING_PAGE_PROMPT_TEST_ID = 'alert-summary-landing-page-prompt';
13+
14+
export interface LandingPageProps {
15+
/**
16+
* List of available AI for SOC integrations
17+
*/
18+
packages: PackageListItem[];
19+
}
20+
21+
/**
22+
* Displays a gif of the alerts summary page, with empty prompt showing the top 2 available AI for SOC packages.
23+
* This page is rendered when no AI for SOC packages are installed.
24+
*/
25+
export const LandingPage = memo(({ packages }: LandingPageProps) => {
26+
return <EuiText data-test-subj={LANDING_PAGE_PROMPT_TEST_ID}>{'Landing page'}</EuiText>;
27+
});
28+
29+
LandingPage.displayName = 'LandingPage';
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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 } from 'react';
9+
import type { PackageListItem } from '@kbn/fleet-plugin/common';
10+
import { EuiText } from '@elastic/eui';
11+
12+
export const DATA_VIEW_LOADING_PROMPT_TEST_ID = 'alert-summary-data-view-loading-prompt';
13+
14+
export interface WrapperProps {
15+
/**
16+
* List of installed Ai for SOC integrations
17+
*/
18+
packages: PackageListItem[];
19+
}
20+
21+
/**
22+
* Creates a new dataView with the alert indices while displaying a loading skeleton.
23+
* Display the alert summary page content if the dataView is correctly created.
24+
* This page is rendered when there are AI for SOC packages installed.
25+
*/
26+
export const Wrapper = memo(({ packages }: WrapperProps) => {
27+
return <EuiText data-test-subj={DATA_VIEW_LOADING_PROMPT_TEST_ID}>{'Wrapper'}</EuiText>;
28+
});
29+
30+
Wrapper.displayName = 'Wrapper';

0 commit comments

Comments
 (0)