Skip to content

Commit 53d23fc

Browse files
dplumleeoatkiller
andauthored
[Endpoint] Adds take action dropdown and tests to alert details flyout (#59242)
* adds dropdown * changes i18n fields * switches to buttons * adds tests for alert details flyout * updates es archiver data * finishes functional and react tests * cleanup tests for alerts * updates alert esarchive data * replaces es archives and fixes tests * rebase * fixes functional tests * suggested changes to take action button * addresses comments Co-authored-by: oatkiller <robert.austin@elastic.co>
1 parent caed9ba commit 53d23fc

File tree

22 files changed

+1700
-4719
lines changed

22 files changed

+1700
-4719
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import * as reactTestingLibrary from '@testing-library/react';
8+
import { appStoreFactory } from '../../store';
9+
import { fireEvent } from '@testing-library/react';
10+
import { MemoryHistory } from 'history';
11+
import { AppAction } from '../../types';
12+
import { mockAlertResultList } from '../../store/alerts/mock_alert_result_list';
13+
import { alertPageTestRender } from './test_helpers/render_alert_page';
14+
15+
describe('when the alert details flyout is open', () => {
16+
let render: () => reactTestingLibrary.RenderResult;
17+
let history: MemoryHistory<never>;
18+
let store: ReturnType<typeof appStoreFactory>;
19+
20+
beforeEach(async () => {
21+
// Creates the render elements for the tests to use
22+
({ render, history, store } = alertPageTestRender);
23+
});
24+
describe('when the alerts details flyout is open', () => {
25+
beforeEach(() => {
26+
reactTestingLibrary.act(() => {
27+
history.push({
28+
search: '?selected_alert=1',
29+
});
30+
});
31+
});
32+
describe('when the data loads', () => {
33+
beforeEach(() => {
34+
reactTestingLibrary.act(() => {
35+
const action: AppAction = {
36+
type: 'serverReturnedAlertDetailsData',
37+
payload: mockAlertResultList().alerts[0],
38+
};
39+
store.dispatch(action);
40+
});
41+
});
42+
it('should display take action button', async () => {
43+
await render().findByTestId('alertDetailTakeActionDropdownButton');
44+
});
45+
describe('when the user clicks the take action button on the flyout', () => {
46+
let renderResult: reactTestingLibrary.RenderResult;
47+
beforeEach(async () => {
48+
renderResult = render();
49+
const takeActionButton = await renderResult.findByTestId(
50+
'alertDetailTakeActionDropdownButton'
51+
);
52+
if (takeActionButton) {
53+
fireEvent.click(takeActionButton);
54+
}
55+
});
56+
it('should display the correct fields in the dropdown', async () => {
57+
await renderResult.findByTestId('alertDetailTakeActionCloseAlertButton');
58+
await renderResult.findByTestId('alertDetailTakeActionWhitelistButton');
59+
});
60+
});
61+
describe('when the user navigates to the overview tab', () => {
62+
let renderResult: reactTestingLibrary.RenderResult;
63+
beforeEach(async () => {
64+
renderResult = render();
65+
const overviewTab = await renderResult.findByTestId('overviewMetadata');
66+
if (overviewTab) {
67+
fireEvent.click(overviewTab);
68+
}
69+
});
70+
it('should render all accordion panels', async () => {
71+
await renderResult.findAllByTestId('alertDetailsAlertAccordion');
72+
await renderResult.findAllByTestId('alertDetailsHostAccordion');
73+
await renderResult.findAllByTestId('alertDetailsFileAccordion');
74+
await renderResult.findAllByTestId('alertDetailsHashAccordion');
75+
await renderResult.findAllByTestId('alertDetailsSourceProcessAccordion');
76+
await renderResult.findAllByTestId('alertDetailsSourceProcessTokenAccordion');
77+
});
78+
});
79+
});
80+
});
81+
});

x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/file_accordion.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export const FileAccordion = memo(({ alertData }: { alertData: Immutable<AlertDa
7373
}
7474
)}
7575
paddingSize="l"
76+
data-test-subj="alertDetailsFileAccordion"
7677
>
7778
<EuiDescriptionList type="column" listItems={columns} />
7879
</EuiAccordion>

x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/general_accordion.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export const GeneralAccordion = memo(({ alertData }: { alertData: Immutable<Aler
6161
)}
6262
paddingSize="l"
6363
initialIsOpen={true}
64+
data-test-subj="alertDetailsAlertAccordion"
6465
>
6566
<EuiDescriptionList type="column" listItems={columns} />
6667
</EuiAccordion>

x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/hash_accordion.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export const HashAccordion = memo(({ alertData }: { alertData: Immutable<AlertDa
4242
}
4343
)}
4444
paddingSize="l"
45+
data-test-subj="alertDetailsHashAccordion"
4546
>
4647
<EuiDescriptionList type="column" listItems={columns} />
4748
</EuiAccordion>

x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export const HostAccordion = memo(({ alertData }: { alertData: Immutable<AlertDa
4848
}
4949
)}
5050
paddingSize="l"
51+
data-test-subj="alertDetailsHostAccordion"
5152
>
5253
<EuiDescriptionList type="column" listItems={columns} />
5354
</EuiAccordion>

x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/source_process_accordion.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export const SourceProcessAccordion = memo(({ alertData }: { alertData: Immutabl
9090
}
9191
)}
9292
paddingSize="l"
93+
data-test-subj="alertDetailsSourceProcessAccordion"
9394
>
9495
<EuiDescriptionList type="column" listItems={columns} />
9596
</EuiAccordion>

x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/source_process_token_accordion.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const SourceProcessTokenAccordion = memo(
3737
}
3838
)}
3939
paddingSize="l"
40+
data-test-subj="alertDetailsSourceProcessTokenAccordion"
4041
>
4142
<EuiDescriptionList type="column" listItems={columns} />
4243
</EuiAccordion>

x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/overview/index.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,20 @@
66
import React, { memo, useMemo } from 'react';
77
import { i18n } from '@kbn/i18n';
88
import { FormattedMessage } from '@kbn/i18n/react';
9-
import { EuiSpacer, EuiTitle, EuiText, EuiHealth, EuiTabbedContent } from '@elastic/eui';
9+
import {
10+
EuiSpacer,
11+
EuiTitle,
12+
EuiText,
13+
EuiHealth,
14+
EuiTabbedContent,
15+
EuiTabbedContentTab,
16+
} from '@elastic/eui';
1017
import { useAlertListSelector } from '../../hooks/use_alerts_selector';
1118
import * as selectors from '../../../../store/alerts/selectors';
1219
import { MetadataPanel } from './metadata_panel';
1320
import { FormattedDate } from '../../formatted_date';
1421
import { AlertDetailResolver } from '../../resolver';
22+
import { TakeActionDropdown } from './take_action_dropdown';
1523

1624
export const AlertDetailsOverview = memo(() => {
1725
const alertDetailsData = useAlertListSelector(selectors.selectedAlertDetailsData);
@@ -22,10 +30,11 @@ export const AlertDetailsOverview = memo(() => {
2230
selectors.selectedAlertIsLegacyEndpointEvent
2331
);
2432

25-
const tabs = useMemo(() => {
33+
const tabs: EuiTabbedContentTab[] = useMemo(() => {
2634
return [
2735
{
2836
id: 'overviewMetadata',
37+
'data-test-subj': 'overviewMetadata',
2938
name: i18n.translate(
3039
'xpack.endpoint.application.endpoint.alertDetails.overview.tabs.overview',
3140
{
@@ -87,6 +96,8 @@ export const AlertDetailsOverview = memo(() => {
8796
</EuiText>
8897
<EuiText>Alert Status: Open</EuiText>
8998
<EuiSpacer />
99+
<TakeActionDropdown />
100+
<EuiSpacer />
90101
</section>
91102
<EuiTabbedContent tabs={tabs} initialSelectedTab={tabs[0]} />
92103
</>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import React, { memo, useState, useCallback } from 'react';
8+
import { EuiPopover, EuiFormRow, EuiButton, EuiButtonEmpty } from '@elastic/eui';
9+
import { FormattedMessage } from '@kbn/i18n/react';
10+
11+
const TakeActionButton = memo(({ onClick }: { onClick: () => void }) => (
12+
<EuiButton
13+
iconType="arrowDown"
14+
iconSide="right"
15+
data-test-subj="alertDetailTakeActionDropdownButton"
16+
onClick={onClick}
17+
>
18+
<FormattedMessage
19+
id="xpack.endpoint.application.endpoint.alertDetails.takeAction.title"
20+
defaultMessage="Take Action"
21+
/>
22+
</EuiButton>
23+
));
24+
25+
export const TakeActionDropdown = memo(() => {
26+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
27+
28+
const onClick = useCallback(() => {
29+
setIsDropdownOpen(!isDropdownOpen);
30+
}, [isDropdownOpen]);
31+
32+
const closePopover = useCallback(() => {
33+
setIsDropdownOpen(false);
34+
}, []);
35+
36+
return (
37+
<EuiPopover
38+
button={<TakeActionButton onClick={onClick} />}
39+
isOpen={isDropdownOpen}
40+
anchorPosition="downRight"
41+
closePopover={closePopover}
42+
data-test-subj="alertListTakeActionDropdownContent"
43+
>
44+
<EuiFormRow>
45+
<EuiButtonEmpty
46+
data-test-subj="alertDetailTakeActionCloseAlertButton"
47+
color="text"
48+
iconType="folderCheck"
49+
>
50+
<FormattedMessage
51+
id="xpack.endpoint.application.endpoint.alertDetails.takeAction.close"
52+
defaultMessage="Close Alert"
53+
/>
54+
</EuiButtonEmpty>
55+
</EuiFormRow>
56+
57+
<EuiFormRow>
58+
<EuiButtonEmpty
59+
data-test-subj="alertDetailTakeActionWhitelistButton"
60+
color="text"
61+
iconType="listAdd"
62+
>
63+
<FormattedMessage
64+
id="xpack.endpoint.application.endpoint.alertDetails.takeAction.whitelist"
65+
defaultMessage="Whitelist..."
66+
/>
67+
</EuiButtonEmpty>
68+
</EuiFormRow>
69+
</EuiPopover>
70+
);
71+
});

x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx

Lines changed: 9 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,15 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import React from 'react';
87
import * as reactTestingLibrary from '@testing-library/react';
9-
import { Provider } from 'react-redux';
10-
import { I18nProvider } from '@kbn/i18n/react';
11-
import { AlertIndex } from './index';
128
import { IIndexPattern } from 'src/plugins/data/public';
139
import { appStoreFactory } from '../../store';
14-
import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public';
1510
import { fireEvent, act } from '@testing-library/react';
16-
import { RouteCapture } from '../route_capture';
17-
import { createMemoryHistory, MemoryHistory } from 'history';
18-
import { Router } from 'react-router-dom';
11+
import { MemoryHistory } from 'history';
1912
import { AppAction } from '../../types';
2013
import { mockAlertResultList } from '../../store/alerts/mock_alert_result_list';
21-
import { DepsStartMock, depsStartMock } from '../../mocks';
14+
import { DepsStartMock } from '../../mocks';
15+
import { alertPageTestRender } from './test_helpers/render_alert_page';
2216

2317
describe('when on the alerting page', () => {
2418
let render: () => reactTestingLibrary.RenderResult;
@@ -27,42 +21,8 @@ describe('when on the alerting page', () => {
2721
let depsStart: DepsStartMock;
2822

2923
beforeEach(async () => {
30-
/**
31-
* Create a 'history' instance that is only in-memory and causes no side effects to the testing environment.
32-
*/
33-
history = createMemoryHistory<never>();
34-
/**
35-
* Create a store, with the middleware disabled. We don't want side effects being created by our code in this test.
36-
*/
37-
store = appStoreFactory();
38-
39-
depsStart = depsStartMock();
40-
depsStart.data.ui.SearchBar.mockImplementation(() => <div />);
41-
42-
/**
43-
* Render the test component, use this after setting up anything in `beforeEach`.
44-
*/
45-
render = () => {
46-
/**
47-
* Provide the store via `Provider`, and i18n APIs via `I18nProvider`.
48-
* Use react-router via `Router`, passing our in-memory `history` instance.
49-
* Use `RouteCapture` to emit url-change actions when the URL is changed.
50-
* Finally, render the `AlertIndex` component which we are testing.
51-
*/
52-
return reactTestingLibrary.render(
53-
<Provider store={store}>
54-
<KibanaContextProvider services={{ data: depsStart.data }}>
55-
<I18nProvider>
56-
<Router history={history}>
57-
<RouteCapture>
58-
<AlertIndex />
59-
</RouteCapture>
60-
</Router>
61-
</I18nProvider>
62-
</KibanaContextProvider>
63-
</Provider>
64-
);
65-
};
24+
// Creates the render elements for the tests to use
25+
({ render, history, store, depsStart } = alertPageTestRender);
6626
});
6727
it('should show a data grid', async () => {
6828
await render().findByTestId('alertListGrid');
@@ -80,7 +40,7 @@ describe('when on the alerting page', () => {
8040
reactTestingLibrary.act(() => {
8141
const action: AppAction = {
8242
type: 'serverReturnedAlertsData',
83-
payload: mockAlertResultList(),
43+
payload: mockAlertResultList({ total: 11 }),
8444
};
8545
store.dispatch(action);
8646
});
@@ -93,16 +53,17 @@ describe('when on the alerting page', () => {
9353
* There should be a 'row' which is the header, and
9454
* row which is the alert item.
9555
*/
96-
expect(rows).toHaveLength(2);
56+
expect(rows).toHaveLength(11);
9757
});
9858
describe('when the user has clicked the alert type in the grid', () => {
9959
let renderResult: reactTestingLibrary.RenderResult;
10060
beforeEach(async () => {
10161
renderResult = render();
62+
const alertLinks = await renderResult.findAllByTestId('alertTypeCellLink');
10263
/**
10364
* This is the cell with the alert type, it has a link.
10465
*/
105-
fireEvent.click(await renderResult.findByTestId('alertTypeCellLink'));
66+
fireEvent.click(alertLinks[0]);
10667
});
10768
it('should show the flyout', async () => {
10869
await renderResult.findByTestId('alertDetailFlyout');

0 commit comments

Comments
 (0)