Skip to content

Commit 9b9eb7e

Browse files
[Security Solution][Alert details] - refactor UI on insights (#197349)
## Summary This PR refactors the Insights section of the expandable flyout for alerts and events. The changes are applied to the following section: - Threat Intelligence: when the user clicks on the number, we open the left section to the Insights Threat Intelligence tab - Correlations: when the user clicks on the number, we open the left section to the Insights Correlations tab - Prevalence: no user interactions When in preview mode, none of the number are clickable and the buttons are disabled. #### New UI | Normal flyout | Preview flyout | | ------------- | ------------- | | ![Screenshot 2024-10-22 at 6 01 38 PM](https://github.com/user-attachments/assets/de179a2b-c8ab-42f6-b5b7-839dae0139d5) | ![Screenshot 2024-10-22 at 6 01 54 PM](https://github.com/user-attachments/assets/63ed125e-5e3b-4c4c-a10e-7cc01d291660) | #### UX flows to expand the flyout https://github.com/user-attachments/assets/30031a12-c2f3-47e6-a783-5b9482359ee5 elastic/security-team#7033 ### Checklist - [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
1 parent fa0f397 commit 9b9eb7e

25 files changed

+806
-512
lines changed

x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.test.tsx

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import {
2222
CORRELATIONS_RELATED_CASES_TEST_ID,
2323
CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID,
2424
CORRELATIONS_TEST_ID,
25-
SUMMARY_ROW_VALUE_TEST_ID,
25+
SUMMARY_ROW_BUTTON_TEST_ID,
26+
SUMMARY_ROW_TEXT_TEST_ID,
2627
} from './test_ids';
2728
import { useShowRelatedAlertsByAncestry } from '../../shared/hooks/use_show_related_alerts_by_ancestry';
2829
import { useShowRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_show_related_alerts_by_same_source_event';
@@ -58,17 +59,32 @@ const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(CORRELATIO
5859
const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(CORRELATIONS_TEST_ID);
5960
const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(CORRELATIONS_TEST_ID);
6061

61-
const SUPPRESSED_ALERTS_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID);
62-
const RELATED_ALERTS_BY_ANCESTRY_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(
62+
const SUPPRESSED_ALERTS_TEXT_TEST_ID = SUMMARY_ROW_TEXT_TEST_ID(
63+
CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID
64+
);
65+
const SUPPRESSED_ALERTS_VALUE_TEST_ID = SUMMARY_ROW_BUTTON_TEST_ID(
66+
CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID
67+
);
68+
const RELATED_ALERTS_BY_ANCESTRY_TEXT_TEST_ID = SUMMARY_ROW_TEXT_TEST_ID(
69+
CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID
70+
);
71+
const RELATED_ALERTS_BY_ANCESTRY_VALUE_TEST_ID = SUMMARY_ROW_BUTTON_TEST_ID(
6372
CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID
6473
);
65-
const RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(
74+
const RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEXT_TEST_ID = SUMMARY_ROW_TEXT_TEST_ID(
6675
CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID
6776
);
68-
const RELATED_ALERTS_BY_SESSION_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(
77+
const RELATED_ALERTS_BY_SAME_SOURCE_EVENT_VALUE_TEST_ID = SUMMARY_ROW_BUTTON_TEST_ID(
78+
CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID
79+
);
80+
const RELATED_ALERTS_BY_SESSION_TEXT_TEST_ID = SUMMARY_ROW_TEXT_TEST_ID(
81+
CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID
82+
);
83+
const RELATED_ALERTS_BY_SESSION_VALUE_TEST_ID = SUMMARY_ROW_BUTTON_TEST_ID(
6984
CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID
7085
);
71-
const RELATED_CASES_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(CORRELATIONS_RELATED_CASES_TEST_ID);
86+
const RELATED_CASES_TEXT_TEST_ID = SUMMARY_ROW_TEXT_TEST_ID(CORRELATIONS_RELATED_CASES_TEST_ID);
87+
const RELATED_CASES_VALUE_TEST_ID = SUMMARY_ROW_BUTTON_TEST_ID(CORRELATIONS_RELATED_CASES_TEST_ID);
7288

7389
const panelContextValue = {
7490
eventId: 'event id',
@@ -193,11 +209,16 @@ describe('<CorrelationsOverview />', () => {
193209
});
194210

195211
const { getByTestId, queryByText } = render(renderCorrelationsOverview(panelContextValue));
196-
expect(getByTestId(RELATED_ALERTS_BY_ANCESTRY_TEST_ID)).toBeInTheDocument();
197-
expect(getByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID)).toBeInTheDocument();
198-
expect(getByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).toBeInTheDocument();
199-
expect(getByTestId(RELATED_CASES_TEST_ID)).toBeInTheDocument();
200-
expect(getByTestId(SUPPRESSED_ALERTS_TEST_ID)).toBeInTheDocument();
212+
expect(getByTestId(RELATED_ALERTS_BY_ANCESTRY_TEXT_TEST_ID)).toBeInTheDocument();
213+
expect(getByTestId(RELATED_ALERTS_BY_ANCESTRY_VALUE_TEST_ID)).toBeInTheDocument();
214+
expect(getByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEXT_TEST_ID)).toBeInTheDocument();
215+
expect(getByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_VALUE_TEST_ID)).toBeInTheDocument();
216+
expect(getByTestId(RELATED_ALERTS_BY_SESSION_TEXT_TEST_ID)).toBeInTheDocument();
217+
expect(getByTestId(RELATED_ALERTS_BY_SESSION_VALUE_TEST_ID)).toBeInTheDocument();
218+
expect(getByTestId(RELATED_CASES_TEXT_TEST_ID)).toBeInTheDocument();
219+
expect(getByTestId(RELATED_CASES_VALUE_TEST_ID)).toBeInTheDocument();
220+
expect(getByTestId(SUPPRESSED_ALERTS_TEXT_TEST_ID)).toBeInTheDocument();
221+
expect(getByTestId(SUPPRESSED_ALERTS_VALUE_TEST_ID)).toBeInTheDocument();
201222
expect(queryByText(NO_DATA_MESSAGE)).not.toBeInTheDocument();
202223
});
203224

@@ -215,11 +236,18 @@ describe('<CorrelationsOverview />', () => {
215236
jest.mocked(useShowSuppressedAlerts).mockReturnValue({ show: false, alertSuppressionCount: 0 });
216237

217238
const { getByText, queryByTestId } = render(renderCorrelationsOverview(panelContextValue));
218-
expect(queryByTestId(RELATED_ALERTS_BY_ANCESTRY_TEST_ID)).not.toBeInTheDocument();
219-
expect(queryByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID)).not.toBeInTheDocument();
220-
expect(queryByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).not.toBeInTheDocument();
221-
expect(queryByTestId(RELATED_CASES_TEST_ID)).not.toBeInTheDocument();
222-
expect(queryByTestId(SUPPRESSED_ALERTS_TEST_ID)).not.toBeInTheDocument();
239+
expect(queryByTestId(RELATED_ALERTS_BY_ANCESTRY_TEXT_TEST_ID)).not.toBeInTheDocument();
240+
expect(queryByTestId(RELATED_ALERTS_BY_ANCESTRY_VALUE_TEST_ID)).not.toBeInTheDocument();
241+
expect(queryByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEXT_TEST_ID)).not.toBeInTheDocument();
242+
expect(
243+
queryByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_VALUE_TEST_ID)
244+
).not.toBeInTheDocument();
245+
expect(queryByTestId(RELATED_ALERTS_BY_SESSION_TEXT_TEST_ID)).not.toBeInTheDocument();
246+
expect(queryByTestId(RELATED_ALERTS_BY_SESSION_VALUE_TEST_ID)).not.toBeInTheDocument();
247+
expect(queryByTestId(RELATED_CASES_TEXT_TEST_ID)).not.toBeInTheDocument();
248+
expect(queryByTestId(RELATED_CASES_VALUE_TEST_ID)).not.toBeInTheDocument();
249+
expect(queryByTestId(SUPPRESSED_ALERTS_TEXT_TEST_ID)).not.toBeInTheDocument();
250+
expect(queryByTestId(SUPPRESSED_ALERTS_VALUE_TEST_ID)).not.toBeInTheDocument();
223251
expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
224252
});
225253

x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export const CorrelationsOverview: React.FC = () => {
134134
data-test-subj={CORRELATIONS_TEST_ID}
135135
>
136136
{canShowAtLeastOneInsight ? (
137-
<EuiFlexGroup direction="column" gutterSize="none">
137+
<EuiFlexGroup direction="column" gutterSize="s">
138138
{showSuppressedAlerts && (
139139
<SuppressedAlerts alertSuppressionCount={alertSuppressionCount} ruleType={ruleType} />
140140
)}

x-pack/plugins/security_solution/public/flyout/document_details/right/components/insights_summary_row.stories.tsx

Lines changed: 0 additions & 77 deletions
This file was deleted.

x-pack/plugins/security_solution/public/flyout/document_details/right/components/insights_summary_row.test.tsx

Lines changed: 100 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,74 +9,147 @@ import React from 'react';
99
import { render } from '@testing-library/react';
1010
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
1111
import { InsightsSummaryRow } from './insights_summary_row';
12+
import { useDocumentDetailsContext } from '../../shared/context';
13+
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
14+
import { DocumentDetailsLeftPanelKey } from '../../shared/constants/panel_keys';
15+
import { LeftPanelInsightsTab } from '../../left';
16+
17+
jest.mock('@kbn/expandable-flyout');
18+
jest.mock('../../shared/context');
19+
20+
const mockOpenLeftPanel = jest.fn();
21+
const scopeId = 'scopeId';
22+
const eventId = 'eventId';
23+
const indexName = 'indexName';
1224

1325
const testId = 'test';
14-
const iconTestId = `${testId}Icon`;
26+
const textTestId = `${testId}Text`;
27+
const buttonTestId = `${testId}Button`;
1528
const valueTestId = `${testId}Value`;
16-
const colorTestId = `${testId}Color`;
1729
const loadingTestId = `${testId}Loading`;
1830

1931
describe('<InsightsSummaryRow />', () => {
20-
it('should render by default', () => {
32+
beforeEach(() => {
33+
jest.clearAllMocks();
34+
35+
(useDocumentDetailsContext as jest.Mock).mockReturnValue({
36+
eventId,
37+
indexName,
38+
scopeId,
39+
isPreviewMode: false,
40+
});
41+
(useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel });
42+
});
43+
44+
it('should render loading skeleton if loading is true', () => {
2145
const { getByTestId } = render(
46+
<InsightsSummaryRow
47+
loading={true}
48+
text={'text'}
49+
value={<div>{'value for this'}</div>}
50+
data-test-subj={testId}
51+
/>
52+
);
53+
54+
expect(getByTestId(loadingTestId)).toBeInTheDocument();
55+
});
56+
57+
it('should only render null when error is true', () => {
58+
const { container } = render(
59+
<InsightsSummaryRow error={true} text={'text'} value={<div>{'value for this'}</div>} />
60+
);
61+
62+
expect(container).toBeEmptyDOMElement();
63+
});
64+
65+
it('should render the value component', () => {
66+
const { getByTestId, queryByTestId } = render(
2267
<IntlProvider locale="en">
2368
<InsightsSummaryRow
24-
icon={'image'}
25-
value={1}
2669
text={'this is a test for red'}
27-
color={'rgb(189,39,30)'}
70+
value={<div>{'value for this'}</div>}
2871
data-test-subj={testId}
2972
/>
3073
</IntlProvider>
3174
);
3275

33-
expect(getByTestId(iconTestId)).toBeInTheDocument();
34-
expect(getByTestId(valueTestId)).toHaveTextContent('1 this is a test for red');
35-
expect(getByTestId(colorTestId)).toBeInTheDocument();
76+
expect(getByTestId(textTestId)).toHaveTextContent('this is a test for red');
77+
expect(getByTestId(valueTestId)).toHaveTextContent('value for this');
78+
expect(queryByTestId(buttonTestId)).not.toBeInTheDocument();
3679
});
3780

38-
it('should render loading skeletton if loading is true', () => {
39-
const { getByTestId } = render(
40-
<InsightsSummaryRow loading={true} icon={'image'} text={'text'} data-test-subj={testId} />
81+
it('should render the value as EuiBadge and EuiButtonEmpty', () => {
82+
const { getByTestId, queryByTestId } = render(
83+
<IntlProvider locale="en">
84+
<InsightsSummaryRow text={'this is a test for red'} value={2} data-test-subj={testId} />
85+
</IntlProvider>
4186
);
4287

43-
expect(getByTestId(loadingTestId)).toBeInTheDocument();
88+
expect(getByTestId(textTestId)).toHaveTextContent('this is a test for red');
89+
expect(getByTestId(buttonTestId)).toHaveTextContent('2');
90+
expect(queryByTestId(valueTestId)).not.toBeInTheDocument();
4491
});
4592

46-
it('should only render null when error is true', () => {
47-
const { container } = render(<InsightsSummaryRow error={true} icon={'image'} text={'text'} />);
93+
it('should render big numbers formatted correctly', () => {
94+
const { getByTestId } = render(
95+
<IntlProvider locale="en">
96+
<InsightsSummaryRow text={'this is a test for red'} value={2000} data-test-subj={testId} />
97+
</IntlProvider>
98+
);
4899

49-
expect(container).toBeEmptyDOMElement();
100+
expect(getByTestId(buttonTestId)).toHaveTextContent('2k');
50101
});
51102

52-
it('should handle big number in a compact notation', () => {
103+
it('should open the expanded section to the correct tab when the number is clicked', () => {
53104
const { getByTestId } = render(
54105
<IntlProvider locale="en">
55106
<InsightsSummaryRow
56-
icon={'image'}
57-
value={160000}
58107
text={'this is a test for red'}
59-
color={'rgb(189,39,30)'}
108+
value={2}
109+
expandedSubTab={'subTab'}
60110
data-test-subj={testId}
61111
/>
62112
</IntlProvider>
63113
);
114+
getByTestId(buttonTestId).click();
64115

65-
expect(getByTestId(valueTestId)).toHaveTextContent('160k this is a test for red');
116+
expect(mockOpenLeftPanel).toHaveBeenCalledWith({
117+
id: DocumentDetailsLeftPanelKey,
118+
path: {
119+
tab: LeftPanelInsightsTab,
120+
subTab: 'subTab',
121+
},
122+
params: {
123+
id: eventId,
124+
indexName,
125+
scopeId,
126+
},
127+
});
66128
});
67129

68-
it(`should not show the colored dot if color isn't provided`, () => {
69-
const { queryByTestId } = render(
130+
it('should disabled the click when in preview mode', () => {
131+
(useDocumentDetailsContext as jest.Mock).mockReturnValue({
132+
eventId,
133+
indexName,
134+
scopeId,
135+
isPreviewMode: true,
136+
});
137+
138+
const { getByTestId } = render(
70139
<IntlProvider locale="en">
71140
<InsightsSummaryRow
72-
icon={'image'}
73-
value={160000}
74-
text={'this is a test for no color'}
141+
text={'this is a test for red'}
142+
value={2}
143+
expandedSubTab={'subTab'}
75144
data-test-subj={testId}
76145
/>
77146
</IntlProvider>
78147
);
148+
const button = getByTestId(buttonTestId);
149+
150+
expect(button).toHaveAttribute('disabled');
79151

80-
expect(queryByTestId(colorTestId)).not.toBeInTheDocument();
152+
button.click();
153+
expect(mockOpenLeftPanel).not.toHaveBeenCalled();
81154
});
82155
});

0 commit comments

Comments
 (0)