Skip to content

Commit

Permalink
[Endpoint] Adds take action dropdown and tests to alert details flyout (
Browse files Browse the repository at this point in the history
#59242) (#60598)

* 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>

Co-authored-by: Davis Plumlee <56367316+dplumlee@users.noreply.github.com>
Co-authored-by: oatkiller <robert.austin@elastic.co>
  • Loading branch information
3 people committed Mar 19, 2020
1 parent aadf4f5 commit ee24743
Show file tree
Hide file tree
Showing 22 changed files with 1,700 additions and 4,719 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import * as reactTestingLibrary from '@testing-library/react';
import { appStoreFactory } from '../../store';
import { fireEvent } from '@testing-library/react';
import { MemoryHistory } from 'history';
import { AppAction } from '../../types';
import { mockAlertResultList } from '../../store/alerts/mock_alert_result_list';
import { alertPageTestRender } from './test_helpers/render_alert_page';

describe('when the alert details flyout is open', () => {
let render: () => reactTestingLibrary.RenderResult;
let history: MemoryHistory<never>;
let store: ReturnType<typeof appStoreFactory>;

beforeEach(async () => {
// Creates the render elements for the tests to use
({ render, history, store } = alertPageTestRender);
});
describe('when the alerts details flyout is open', () => {
beforeEach(() => {
reactTestingLibrary.act(() => {
history.push({
search: '?selected_alert=1',
});
});
});
describe('when the data loads', () => {
beforeEach(() => {
reactTestingLibrary.act(() => {
const action: AppAction = {
type: 'serverReturnedAlertDetailsData',
payload: mockAlertResultList().alerts[0],
};
store.dispatch(action);
});
});
it('should display take action button', async () => {
await render().findByTestId('alertDetailTakeActionDropdownButton');
});
describe('when the user clicks the take action button on the flyout', () => {
let renderResult: reactTestingLibrary.RenderResult;
beforeEach(async () => {
renderResult = render();
const takeActionButton = await renderResult.findByTestId(
'alertDetailTakeActionDropdownButton'
);
if (takeActionButton) {
fireEvent.click(takeActionButton);
}
});
it('should display the correct fields in the dropdown', async () => {
await renderResult.findByTestId('alertDetailTakeActionCloseAlertButton');
await renderResult.findByTestId('alertDetailTakeActionWhitelistButton');
});
});
describe('when the user navigates to the overview tab', () => {
let renderResult: reactTestingLibrary.RenderResult;
beforeEach(async () => {
renderResult = render();
const overviewTab = await renderResult.findByTestId('overviewMetadata');
if (overviewTab) {
fireEvent.click(overviewTab);
}
});
it('should render all accordion panels', async () => {
await renderResult.findAllByTestId('alertDetailsAlertAccordion');
await renderResult.findAllByTestId('alertDetailsHostAccordion');
await renderResult.findAllByTestId('alertDetailsFileAccordion');
await renderResult.findAllByTestId('alertDetailsHashAccordion');
await renderResult.findAllByTestId('alertDetailsSourceProcessAccordion');
await renderResult.findAllByTestId('alertDetailsSourceProcessTokenAccordion');
});
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const FileAccordion = memo(({ alertData }: { alertData: Immutable<AlertDa
}
)}
paddingSize="l"
data-test-subj="alertDetailsFileAccordion"
>
<EuiDescriptionList type="column" listItems={columns} />
</EuiAccordion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const GeneralAccordion = memo(({ alertData }: { alertData: Immutable<Aler
)}
paddingSize="l"
initialIsOpen={true}
data-test-subj="alertDetailsAlertAccordion"
>
<EuiDescriptionList type="column" listItems={columns} />
</EuiAccordion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const HashAccordion = memo(({ alertData }: { alertData: Immutable<AlertDa
}
)}
paddingSize="l"
data-test-subj="alertDetailsHashAccordion"
>
<EuiDescriptionList type="column" listItems={columns} />
</EuiAccordion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const HostAccordion = memo(({ alertData }: { alertData: Immutable<AlertDa
}
)}
paddingSize="l"
data-test-subj="alertDetailsHostAccordion"
>
<EuiDescriptionList type="column" listItems={columns} />
</EuiAccordion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export const SourceProcessAccordion = memo(({ alertData }: { alertData: Immutabl
}
)}
paddingSize="l"
data-test-subj="alertDetailsSourceProcessAccordion"
>
<EuiDescriptionList type="column" listItems={columns} />
</EuiAccordion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const SourceProcessTokenAccordion = memo(
}
)}
paddingSize="l"
data-test-subj="alertDetailsSourceProcessTokenAccordion"
>
<EuiDescriptionList type="column" listItems={columns} />
</EuiAccordion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@
import React, { memo, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiSpacer, EuiTitle, EuiText, EuiHealth, EuiTabbedContent } from '@elastic/eui';
import {
EuiSpacer,
EuiTitle,
EuiText,
EuiHealth,
EuiTabbedContent,
EuiTabbedContentTab,
} from '@elastic/eui';
import { useAlertListSelector } from '../../hooks/use_alerts_selector';
import * as selectors from '../../../../store/alerts/selectors';
import { MetadataPanel } from './metadata_panel';
import { FormattedDate } from '../../formatted_date';
import { AlertDetailResolver } from '../../resolver';
import { TakeActionDropdown } from './take_action_dropdown';

export const AlertDetailsOverview = memo(() => {
const alertDetailsData = useAlertListSelector(selectors.selectedAlertDetailsData);
Expand All @@ -22,10 +30,11 @@ export const AlertDetailsOverview = memo(() => {
selectors.selectedAlertIsLegacyEndpointEvent
);

const tabs = useMemo(() => {
const tabs: EuiTabbedContentTab[] = useMemo(() => {
return [
{
id: 'overviewMetadata',
'data-test-subj': 'overviewMetadata',
name: i18n.translate(
'xpack.endpoint.application.endpoint.alertDetails.overview.tabs.overview',
{
Expand Down Expand Up @@ -87,6 +96,8 @@ export const AlertDetailsOverview = memo(() => {
</EuiText>
<EuiText>Alert Status: Open</EuiText>
<EuiSpacer />
<TakeActionDropdown />
<EuiSpacer />
</section>
<EuiTabbedContent tabs={tabs} initialSelectedTab={tabs[0]} />
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { memo, useState, useCallback } from 'react';
import { EuiPopover, EuiFormRow, EuiButton, EuiButtonEmpty } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';

const TakeActionButton = memo(({ onClick }: { onClick: () => void }) => (
<EuiButton
iconType="arrowDown"
iconSide="right"
data-test-subj="alertDetailTakeActionDropdownButton"
onClick={onClick}
>
<FormattedMessage
id="xpack.endpoint.application.endpoint.alertDetails.takeAction.title"
defaultMessage="Take Action"
/>
</EuiButton>
));

export const TakeActionDropdown = memo(() => {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);

const onClick = useCallback(() => {
setIsDropdownOpen(!isDropdownOpen);
}, [isDropdownOpen]);

const closePopover = useCallback(() => {
setIsDropdownOpen(false);
}, []);

return (
<EuiPopover
button={<TakeActionButton onClick={onClick} />}
isOpen={isDropdownOpen}
anchorPosition="downRight"
closePopover={closePopover}
data-test-subj="alertListTakeActionDropdownContent"
>
<EuiFormRow>
<EuiButtonEmpty
data-test-subj="alertDetailTakeActionCloseAlertButton"
color="text"
iconType="folderCheck"
>
<FormattedMessage
id="xpack.endpoint.application.endpoint.alertDetails.takeAction.close"
defaultMessage="Close Alert"
/>
</EuiButtonEmpty>
</EuiFormRow>

<EuiFormRow>
<EuiButtonEmpty
data-test-subj="alertDetailTakeActionWhitelistButton"
color="text"
iconType="listAdd"
>
<FormattedMessage
id="xpack.endpoint.application.endpoint.alertDetails.takeAction.whitelist"
defaultMessage="Whitelist..."
/>
</EuiButtonEmpty>
</EuiFormRow>
</EuiPopover>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import * as reactTestingLibrary from '@testing-library/react';
import { Provider } from 'react-redux';
import { I18nProvider } from '@kbn/i18n/react';
import { AlertIndex } from './index';
import { IIndexPattern } from 'src/plugins/data/public';
import { appStoreFactory } from '../../store';
import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public';
import { fireEvent, act } from '@testing-library/react';
import { RouteCapture } from '../route_capture';
import { createMemoryHistory, MemoryHistory } from 'history';
import { Router } from 'react-router-dom';
import { MemoryHistory } from 'history';
import { AppAction } from '../../types';
import { mockAlertResultList } from '../../store/alerts/mock_alert_result_list';
import { DepsStartMock, depsStartMock } from '../../mocks';
import { DepsStartMock } from '../../mocks';
import { alertPageTestRender } from './test_helpers/render_alert_page';

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

beforeEach(async () => {
/**
* Create a 'history' instance that is only in-memory and causes no side effects to the testing environment.
*/
history = createMemoryHistory<never>();
/**
* Create a store, with the middleware disabled. We don't want side effects being created by our code in this test.
*/
store = appStoreFactory();

depsStart = depsStartMock();
depsStart.data.ui.SearchBar.mockImplementation(() => <div />);

/**
* Render the test component, use this after setting up anything in `beforeEach`.
*/
render = () => {
/**
* Provide the store via `Provider`, and i18n APIs via `I18nProvider`.
* Use react-router via `Router`, passing our in-memory `history` instance.
* Use `RouteCapture` to emit url-change actions when the URL is changed.
* Finally, render the `AlertIndex` component which we are testing.
*/
return reactTestingLibrary.render(
<Provider store={store}>
<KibanaContextProvider services={{ data: depsStart.data }}>
<I18nProvider>
<Router history={history}>
<RouteCapture>
<AlertIndex />
</RouteCapture>
</Router>
</I18nProvider>
</KibanaContextProvider>
</Provider>
);
};
// Creates the render elements for the tests to use
({ render, history, store, depsStart } = alertPageTestRender);
});
it('should show a data grid', async () => {
await render().findByTestId('alertListGrid');
Expand All @@ -80,7 +40,7 @@ describe('when on the alerting page', () => {
reactTestingLibrary.act(() => {
const action: AppAction = {
type: 'serverReturnedAlertsData',
payload: mockAlertResultList(),
payload: mockAlertResultList({ total: 11 }),
};
store.dispatch(action);
});
Expand All @@ -93,16 +53,17 @@ describe('when on the alerting page', () => {
* There should be a 'row' which is the header, and
* row which is the alert item.
*/
expect(rows).toHaveLength(2);
expect(rows).toHaveLength(11);
});
describe('when the user has clicked the alert type in the grid', () => {
let renderResult: reactTestingLibrary.RenderResult;
beforeEach(async () => {
renderResult = render();
const alertLinks = await renderResult.findAllByTestId('alertTypeCellLink');
/**
* This is the cell with the alert type, it has a link.
*/
fireEvent.click(await renderResult.findByTestId('alertTypeCellLink'));
fireEvent.click(alertLinks[0]);
});
it('should show the flyout', async () => {
await renderResult.findByTestId('alertDetailFlyout');
Expand Down
Loading

0 comments on commit ee24743

Please sign in to comment.