Skip to content

Commit ca4998a

Browse files
[8.x] [Cloud Security] Alerts Preview for Host Name (#197102) (#200062)
# Backport This will backport the following commits from `main` to `8.x`: - [[Cloud Security] Alerts Preview for Host Name (#197102)](#197102) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Rickyanto Ang","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-13T18:10:11Z","message":"[Cloud Security] Alerts Preview for Host Name (#197102)\n\n## Summary\r\n<img width=\"1447\" alt=\"Screenshot 2024-10-21 at 10 38 49 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/e7d011dd-8245-4bf8-ad96-a4fd634e82c1\">\r\n\r\n\r\nThis PR is for Alerts preview component in Contextual Flyout (Host Name)","sha":"675b54bee70142f03929391d36c724e0f5223196","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Cloud Security","backport:prev-minor","v8.17.0"],"title":"[Cloud Security] Alerts Preview for Host Name","number":197102,"url":"https://github.com/elastic/kibana/pull/197102","mergeCommit":{"message":"[Cloud Security] Alerts Preview for Host Name (#197102)\n\n## Summary\r\n<img width=\"1447\" alt=\"Screenshot 2024-10-21 at 10 38 49 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/e7d011dd-8245-4bf8-ad96-a4fd634e82c1\">\r\n\r\n\r\nThis PR is for Alerts preview component in Contextual Flyout (Host Name)","sha":"675b54bee70142f03929391d36c724e0f5223196"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/197102","number":197102,"mergeCommit":{"message":"[Cloud Security] Alerts Preview for Host Name (#197102)\n\n## Summary\r\n<img width=\"1447\" alt=\"Screenshot 2024-10-21 at 10 38 49 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/e7d011dd-8245-4bf8-ad96-a4fd634e82c1\">\r\n\r\n\r\nThis PR is for Alerts preview component in Contextual Flyout (Host Name)","sha":"675b54bee70142f03929391d36c724e0f5223196"}},{"branch":"8.x","label":"v8.17.0","branchLabelMappingKey":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Rickyanto Ang <[email protected]>
1 parent 6fdce91 commit ca4998a

File tree

4 files changed

+242
-4
lines changed

4 files changed

+242
-4
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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 { AlertsPreview } from './alerts_preview';
11+
import { TestProviders } from '../../../common/mock/test_providers';
12+
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
13+
import type { ParsedAlertsData } from '../../../overview/components/detection_response/alerts_by_status/types';
14+
15+
const mockAlertsData: ParsedAlertsData = {
16+
open: {
17+
total: 3,
18+
severities: [
19+
{ key: 'low', value: 2, label: 'Low' },
20+
{ key: 'medium', value: 1, label: 'Medium' },
21+
],
22+
},
23+
acknowledged: {
24+
total: 2,
25+
severities: [
26+
{ key: 'low', value: 1, label: 'Low' },
27+
{ key: 'high', value: 1, label: 'High' },
28+
],
29+
},
30+
};
31+
32+
jest.mock(
33+
'../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data'
34+
);
35+
jest.mock('@kbn/expandable-flyout');
36+
37+
describe('AlertsPreview', () => {
38+
const mockOpenLeftPanel = jest.fn();
39+
40+
beforeEach(() => {
41+
(useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel });
42+
});
43+
afterEach(() => {
44+
jest.clearAllMocks();
45+
});
46+
47+
it('renders', () => {
48+
const { getByTestId } = render(
49+
<TestProviders>
50+
<AlertsPreview alertsData={mockAlertsData} />
51+
</TestProviders>
52+
);
53+
54+
expect(getByTestId('securitySolutionFlyoutInsightsAlertsTitleText')).toBeInTheDocument();
55+
});
56+
57+
it('renders correct alerts number', () => {
58+
const { getByTestId } = render(
59+
<TestProviders>
60+
<AlertsPreview alertsData={mockAlertsData} />
61+
</TestProviders>
62+
);
63+
64+
expect(getByTestId('securitySolutionFlyoutInsightsAlertsCount').textContent).toEqual('5');
65+
});
66+
67+
it('should render the correct number of distribution bar section based on the number of severities', () => {
68+
const { queryAllByTestId } = render(
69+
<TestProviders>
70+
<AlertsPreview alertsData={mockAlertsData} />
71+
</TestProviders>
72+
);
73+
74+
expect(queryAllByTestId('AlertsPreviewDistributionBarTestId__part').length).toEqual(3);
75+
});
76+
});
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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 { capitalize } from 'lodash';
10+
import type { EuiThemeComputed } from '@elastic/eui';
11+
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle, useEuiTheme } from '@elastic/eui';
12+
import { FormattedMessage } from '@kbn/i18n-react';
13+
import { DistributionBar } from '@kbn/security-solution-distribution-bar';
14+
import { getAbbreviatedNumber } from '@kbn/cloud-security-posture-common';
15+
import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel';
16+
import { getSeverityColor } from '../../../detections/components/alerts_kpis/severity_level_panel/helpers';
17+
import type {
18+
AlertsByStatus,
19+
ParsedAlertsData,
20+
} from '../../../overview/components/detection_response/alerts_by_status/types';
21+
22+
const AlertsCount = ({
23+
alertsTotal,
24+
euiTheme,
25+
}: {
26+
alertsTotal: number;
27+
euiTheme: EuiThemeComputed<{}>;
28+
}) => {
29+
return (
30+
<EuiFlexItem>
31+
<EuiFlexGroup direction="column" gutterSize="none">
32+
<EuiFlexItem>
33+
<EuiTitle size="s">
34+
<h1 data-test-subj={'securitySolutionFlyoutInsightsAlertsCount'}>
35+
{getAbbreviatedNumber(alertsTotal)}
36+
</h1>
37+
</EuiTitle>
38+
</EuiFlexItem>
39+
<EuiFlexItem>
40+
<EuiText
41+
size="m"
42+
css={{
43+
fontWeight: euiTheme.font.weight.semiBold,
44+
}}
45+
>
46+
<FormattedMessage
47+
id="xpack.securitySolution.flyout.right.insights.alerts.alertsCountDescription"
48+
defaultMessage="Alerts"
49+
/>
50+
</EuiText>
51+
</EuiFlexItem>
52+
</EuiFlexGroup>
53+
</EuiFlexItem>
54+
);
55+
};
56+
57+
export const AlertsPreview = ({
58+
alertsData,
59+
isPreviewMode,
60+
}: {
61+
alertsData: ParsedAlertsData;
62+
isPreviewMode?: boolean;
63+
}) => {
64+
const { euiTheme } = useEuiTheme();
65+
66+
const severityMap = new Map<string, number>();
67+
68+
(Object.keys(alertsData || {}) as AlertsByStatus[]).forEach((status) => {
69+
if (alertsData?.[status]?.severities) {
70+
alertsData?.[status]?.severities.forEach((severity) => {
71+
const currentSeverity = severityMap.get(severity.key) || 0;
72+
severityMap.set(severity.key, currentSeverity + severity.value);
73+
});
74+
}
75+
});
76+
77+
const alertStats = Array.from(severityMap, ([key, count]) => ({
78+
key: capitalize(key),
79+
count,
80+
color: getSeverityColor(key),
81+
}));
82+
83+
const totalAlertsCount = alertStats.reduce((total, item) => total + item.count, 0);
84+
85+
return (
86+
<ExpandablePanel
87+
header={{
88+
title: (
89+
<EuiText
90+
size="xs"
91+
css={{
92+
fontWeight: euiTheme.font.weight.semiBold,
93+
}}
94+
>
95+
<FormattedMessage
96+
id="xpack.securitySolution.flyout.right.insights.alerts.alertsTitle"
97+
defaultMessage="Alerts"
98+
/>
99+
</EuiText>
100+
),
101+
}}
102+
data-test-subj={'securitySolutionFlyoutInsightsAlerts'}
103+
>
104+
<EuiFlexGroup gutterSize="none">
105+
<AlertsCount alertsTotal={totalAlertsCount} euiTheme={euiTheme} />
106+
<EuiFlexItem grow={2}>
107+
<EuiFlexGroup direction="column" gutterSize="none">
108+
<EuiFlexItem />
109+
<EuiFlexItem>
110+
<EuiSpacer />
111+
<DistributionBar
112+
stats={alertStats.reverse()}
113+
data-test-subj="AlertsPreviewDistributionBarTestId"
114+
/>
115+
</EuiFlexItem>
116+
</EuiFlexGroup>
117+
</EuiFlexItem>
118+
</EuiFlexGroup>
119+
</ExpandablePanel>
120+
);
121+
};

x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,22 @@
77

88
import { EuiAccordion, EuiHorizontalRule, EuiSpacer, EuiTitle, useEuiTheme } from '@elastic/eui';
99

10-
import React from 'react';
10+
import React, { useMemo } from 'react';
1111
import { css } from '@emotion/react';
1212
import { FormattedMessage } from '@kbn/i18n-react';
1313
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
1414
import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
1515
import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
1616
import { hasVulnerabilitiesData } from '@kbn/cloud-security-posture';
17+
import { FILTER_CLOSED } from '../../../common/types';
1718
import { MisconfigurationsPreview } from './misconfiguration/misconfiguration_preview';
1819
import { VulnerabilitiesPreview } from './vulnerabilities/vulnerabilities_preview';
20+
import { AlertsPreview } from './alerts/alerts_preview';
21+
import { useGlobalTime } from '../../common/containers/use_global_time';
22+
import type { ParsedAlertsData } from '../../overview/components/detection_response/alerts_by_status/types';
23+
import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../overview/components/detection_response/alerts_by_status/types';
24+
import { useAlertsByStatus } from '../../overview/components/detection_response/alerts_by_status/use_alerts_by_status';
25+
import { useSignalIndex } from '../../detections/containers/detection_engine/alerts/use_signal_index';
1926

2027
export const EntityInsight = <T,>({
2128
name,
@@ -60,6 +67,39 @@ export const EntityInsight = <T,>({
6067

6168
const isVulnerabilitiesFindingForHost = hasVulnerabilitiesFindings && fieldName === 'host.name';
6269

70+
const { signalIndexName } = useSignalIndex();
71+
72+
const entityFilter = useMemo(() => ({ field: fieldName, value: name }), [fieldName, name]);
73+
74+
const { to, from } = useGlobalTime();
75+
76+
const { items: alertsData } = useAlertsByStatus({
77+
entityFilter,
78+
signalIndexName,
79+
queryId: DETECTION_RESPONSE_ALERTS_BY_STATUS_ID,
80+
to,
81+
from,
82+
});
83+
84+
const filteredAlertsData: ParsedAlertsData = alertsData
85+
? Object.fromEntries(Object.entries(alertsData).filter(([key]) => key !== FILTER_CLOSED))
86+
: {};
87+
88+
const alertsOpenCount = filteredAlertsData?.open?.total || 0;
89+
90+
const alertsAcknowledgedCount = filteredAlertsData?.acknowledged?.total || 0;
91+
92+
const alertsCount = alertsOpenCount + alertsAcknowledgedCount;
93+
94+
if (alertsCount > 0) {
95+
insightContent.push(
96+
<>
97+
<AlertsPreview alertsData={filteredAlertsData} isPreviewMode={isPreviewMode} />
98+
<EuiSpacer size="s" />
99+
</>
100+
);
101+
}
102+
63103
if (hasMisconfigurationFindings)
64104
insightContent.push(
65105
<>
@@ -76,7 +116,8 @@ export const EntityInsight = <T,>({
76116
);
77117
return (
78118
<>
79-
{(hasMisconfigurationFindings ||
119+
{(insightContent.length > 0 ||
120+
hasMisconfigurationFindings ||
80121
(isVulnerabilitiesFindingForHost && hasVulnerabilitiesFindings)) && (
81122
<>
82123
<EuiAccordion

x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ export const VulnerabilitiesPreview = ({
149149
callback: goToEntityInsightTab,
150150
tooltip: (
151151
<FormattedMessage
152-
id="xpack.securitySolution.flyout.right.insights.misconfiguration.misconfigurationTooltip"
153-
defaultMessage="Show all misconfiguration findings"
152+
id="xpack.securitySolution.flyout.right.insights.vulnerabilities.vulnerabilitiesTooltip"
153+
defaultMessage="Show all vulnerabilities findings"
154154
/>
155155
),
156156
}

0 commit comments

Comments
 (0)