diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/__snapshots__/index.test.tsx.snap b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/__snapshots__/index.test.tsx.snap new file mode 100644 index 000000000000000..c096ccf3929101b --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/__snapshots__/index.test.tsx.snap @@ -0,0 +1,606 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`when on the alerting page when there is no selected alert in the url when data loads should render the alert summary row in the grid 1`] = ` +
+
+
+
+
+
+
+

+ + Row + : + 1 + , + Column + : + 1 + : + +

+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+

+ + Row + : + 1 + , + Column + : + 2 + : + +

+
+ open +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+

+ + Row + : + 1 + , + Column + : + 3 + : + +

+
+ Windows +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+

+ + Row + : + 1 + , + Column + : + 4 + : + +

+
+ 10.179.244.14 +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+

+ + Row + : + 1 + , + Column + : + 5 + : + +

+
+ HD-c15-bc09190a +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+

+ + Row + : + 1 + , + Column + : + 6 + : + +

+
+ 11/15/2018, 11:18:15 PM +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+

+ + Row + : + 1 + , + Column + : + 7 + : + +

+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+

+ + Row + : + 1 + , + Column + : + 8 + : + +

+
+ 3 +
+
+
+ +
+
+
+
+
+
+
+`; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx index cb4e5978653ec19..3083457cba64286 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx @@ -11,25 +11,49 @@ import { I18nProvider } from '@kbn/i18n/react'; import { AlertIndex } from './index'; import { appStoreFactory } from '../../store'; import { coreMock } from 'src/core/public/mocks'; -import { fireEvent } from '@testing-library/react'; +import { fireEvent, waitForElement } from '@testing-library/react'; import { RouteCapture } from '../route_capture'; import { createMemoryHistory, MemoryHistory } from 'history'; import { Router } from 'react-router-dom'; import { AppAction } from '../../types'; import { mockAlertResultList } from '../../store/alerts/mock_alert_result_list'; -function testSubjSelector(testSubjectID: string): string { - return `[data-test-subj="${testSubjectID}"]`; -} - describe('when on the alerting page', () => { let render: () => reactTestingLibrary.RenderResult; let history: MemoryHistory; let store: ReturnType; + + /** + * @testing-library/react provides `queryByTestId`, but that uses the data attribute + * 'data-testid' whereas our FTR and EUI's tests all use 'data-test-subj'. While @testing-library/react + * could be configured to use 'data-test-subj', it is not currently configured that way. + * + * This provides an equivalent function to `queryByTestId` but that uses our 'data-test-subj' attribute. + */ + let queryByTestSubjId: ( + renderResult: reactTestingLibrary.RenderResult, + testSubjId: string + ) => Promise; + beforeEach(async () => { + /** + * Create a 'history' instance that is only in-memory and causes no side effects to the testing environment. + */ history = createMemoryHistory(); + /** + * Create a store, with the middleware disabled. We don't want side effects being created by our code in this test. + */ store = appStoreFactory(coreMock.createStart(), true); + /** + * 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( @@ -42,16 +66,28 @@ describe('when on the alerting page', () => { ); }; + queryByTestSubjId = async (renderResult, testSubjId) => { + return await waitForElement( + () => renderResult.container.querySelector(`[data-test-subj="${testSubjId}"]`), + { + container: renderResult.container, + } + ); + }; }); - it('should show a data grid', () => { - expect(render().container.querySelector(testSubjSelector('alertListGrid'))).not.toBeNull(); + it('should show a data grid', async () => { + await render().findByTestId('alertListGrid'); }); describe('when there is no selected alert in the url', () => { - it('should not show the flyout', async () => { - expect(render().container.querySelector(testSubjSelector('alert-detail-flyout'))).toBeNull(); + it('should not show the flyout', () => { + expect(render().queryByTestId('alertDetailFlyout')).toBeNull(); }); describe('when data loads', () => { beforeEach(() => { + /** + * Dispatch the `serverReturnedAlertsData` action, which is normally dispatched by the middleware + * after interacting with the server. + */ reactTestingLibrary.act(() => { const action: AppAction = { type: 'serverReturnedAlertsData', @@ -61,28 +97,31 @@ describe('when on the alerting page', () => { }); }); it('should render the alert summary row in the grid', async () => { + const renderResult = render(); + const rows = await renderResult.findAllByRole('row'); + /** * There should be a 'row' which is the header, and * another 'row' which is the alert summary. */ - expect(await render().findAllByRole('row')).toHaveLength(2); + expect(rows).toHaveLength(2); + + /** + * Record the markup for the first row, to alert us in case something in the implementation changes. + */ + expect(rows[1]).toMatchSnapshot(); }); describe('when the user has clicked the alert type in the grid', () => { let renderResult: reactTestingLibrary.RenderResult; - beforeEach(() => { + beforeEach(async () => { renderResult = render(); - // This is the cell with the alert type, it has a link. - const alertTypeCellLink = renderResult.container.querySelector( - testSubjSelector('alert-type-cell-link') - ); - if (alertTypeCellLink) { - fireEvent.click(alertTypeCellLink); - } + /** + * This is the cell with the alert type, it has a link. + */ + fireEvent.click(await renderResult.findByTestId('alertTypeCellLink')); }); - it('should show the flyout', () => { - expect( - renderResult.container.querySelector(testSubjSelector('alert-detail-flyout')) - ).not.toBeNull(); + it('should show the flyout', async () => { + await renderResult.findByTestId('alertDetailFlyout'); }); }); }); @@ -96,26 +135,23 @@ describe('when on the alerting page', () => { }); }); }); - it('should show the flyout', () => { - expect( - render().container.querySelector(testSubjSelector('alert-detail-flyout')) - ).not.toBeNull(); + it('should show the flyout', async () => { + await render().findByTestId('alertDetailFlyout'); }); describe('when the user clicks the close button on the flyout', () => { let renderResult: reactTestingLibrary.RenderResult; - beforeEach(() => { + beforeEach(async () => { renderResult = render(); - const closeButton = renderResult.container.querySelector( - testSubjSelector('euiFlyoutCloseButton') - ); + /** + * Use our helper function to find the flyout's close button, as it uses a different test ID attribute. + */ + const closeButton = await queryByTestSubjId(renderResult, 'euiFlyoutCloseButton'); if (closeButton) { fireEvent.click(closeButton); } }); it('should no longer show the flyout', () => { - expect( - renderResult.container.querySelector(testSubjSelector('alert-detail-flyout')) - ).toBeNull(); + expect(render().queryByTestId('alertDetailFlyout')).toBeNull(); }); }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx index 97992a534e1387b..f236991f9f27eb5 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx @@ -20,6 +20,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useHistory, Link } from 'react-router-dom'; +import { FormattedDate } from 'react-intl'; +import { AlertData } from '../../../../../common/types'; import * as selectors from '../../store/alerts/selectors'; import { useAlertListSelector } from './hooks/use_alerts_selector'; @@ -98,23 +100,19 @@ export const AlertIndex = memo(() => { ); const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id)); - const formatter = useMemo( - () => - new Intl.DateTimeFormat(i18n.getLocale(), { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - }), - [] - ); const handleFlyoutClose = useCallback(() => { history.push(urlWithoutSelectedAlert); }, [history, urlWithoutSelectedAlert]); + const datesForRows: Map = useMemo(() => { + return new Map( + alertListData.map(alertData => { + return [alertData, new Date(alertData['@timestamp'])]; + }) + ); + }, [alertListData]); + const renderCellValue = useMemo(() => { return ({ rowIndex, columnId }: { rowIndex: number; columnId: string }) => { if (rowIndex > total) { @@ -125,7 +123,7 @@ export const AlertIndex = memo(() => { if (columnId === 'alert_type') { return ( - + {i18n.translate( 'xpack.endpoint.application.endpoint.alerts.alertType.maliciousFileDescription', { @@ -143,9 +141,19 @@ export const AlertIndex = memo(() => { } else if (columnId === 'host_name') { return row.host.hostname; } else if (columnId === 'timestamp') { - const date = new Date(row['@timestamp']); - if (isFinite(date.getTime())) { - return formatter.format(date); + const date = datesForRows.get(row)!; + if (date && isFinite(date.getTime())) { + return ( + + ); } else { return ( @@ -165,7 +173,7 @@ export const AlertIndex = memo(() => { } return null; }; - }, [alertListData, formatter, pageSize, total, urlWithSelectedAlert]); + }, [alertListData, datesForRows, pageSize, total, urlWithSelectedAlert]); const pagination = useMemo(() => { return { @@ -180,7 +188,7 @@ export const AlertIndex = memo(() => { return ( <> {hasSelectedAlert && ( - +

@@ -193,7 +201,7 @@ export const AlertIndex = memo(() => { )} - + { renderCellValue={renderCellValue} pagination={pagination} data-test-subj="alertListGrid" + data-testid="alertListGrid" />