diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_cases_table.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_cases_table.test.tsx deleted file mode 100644 index 629fd02072c9ce..00000000000000 --- a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_cases_table.test.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { render } from '@testing-library/react'; -import { CaseDetailsLink } from '../../../common/components/links'; - -import { - CorrelationsCasesTable, - type CorrelationsCasesTableProps, -} from './correlations_cases_table'; -import type { RelatedCase } from '@kbn/cases-plugin/common'; -import { CaseStatuses } from '@kbn/cases-components'; - -jest.mock('../../../common/components/links', () => ({ - CaseDetailsLink: jest - .fn() - .mockImplementation(({ title }) => <>{``}), -})); - -const cases: RelatedCase[] = [ - { - id: 'case-1', - title: 'Case 1', - description: '', - createdAt: '', - totals: { - alerts: 0, - userComments: 0, - }, - status: CaseStatuses.open, - }, - { - id: 'case-2', - title: 'Case 2', - description: '', - createdAt: '', - totals: { - alerts: 0, - userComments: 0, - }, - status: CaseStatuses.open, - }, -]; - -const props: CorrelationsCasesTableProps = { - cases, -}; - -describe('CorrelationsCasesTable', () => { - it('renders the table correctly', () => { - render(); - - expect(CaseDetailsLink).toHaveBeenCalledWith( - expect.objectContaining({ - title: 'Case 1', - detailName: 'case-1', - }), - expect.anything() - ); - }); -}); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_cases_table.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_cases_table.tsx deleted file mode 100644 index 993239ce45d7ed..00000000000000 --- a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_cases_table.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { type EuiBasicTableColumn, EuiInMemoryTable } from '@elastic/eui'; -import type { RelatedCase } from '@kbn/cases-plugin/common'; -import React, { type FC } from 'react'; -import { CaseDetailsLink } from '../../../common/components/links'; - -import * as i18n from './translations'; - -export interface CorrelationsCasesTableProps { - cases: RelatedCase[]; -} - -const columns: Array> = [ - { - field: 'title', - name: i18n.CORRELATIONS_CASE_NAME_COLUMN_TITLE, - truncateText: true, - render: (value: string, caseData: RelatedCase) => ( - - {caseData.title} - - ), - }, - { - field: 'status', - name: i18n.CORRELATIONS_CASE_STATUS_COLUMN_TITLE, - truncateText: true, - }, -]; - -export const CorrelationsCasesTable: FC = ({ cases }) => ( - -); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx index 6b52bcf921b0a9..18a60e3fabd4b0 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx @@ -6,62 +6,47 @@ */ import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { useTimelineEventsDetails } from '../../../timelines/containers/details'; -import { useSourcererDataView } from '../../../common/containers/sourcerer'; -import { useCorrelations, type UseCorrelationsResult } from '../../shared/hooks/use_correlations'; +import { render } from '@testing-library/react'; import { CorrelationsDetails } from './correlations_details'; -import { type GetRelatedCasesByAlertResponse } from '@kbn/cases-plugin/common'; -import type { SelectedDataView } from '../../../common/store/sourcerer/model'; import { TestProviders } from '../../../common/mock'; import { LeftPanelContext } from '../context'; -import { CaseStatuses } from '@kbn/cases-components'; - -jest.mock('../../../timelines/containers/details'); -jest.mock('../../../common/containers/sourcerer'); -jest.mock('../../shared/hooks/use_correlations'); - -const mockCasesByAlertId: GetRelatedCasesByAlertResponse = [ - { - id: '123', - title: 'Mock Case', - description: 'This is a mock case for testing purposes', - status: CaseStatuses.open, - createdAt: '2021-10-01T12:00:00Z', - totals: { - alerts: 5, - userComments: 2, - }, - }, -]; - -const mockUseCorrelationsResult: UseCorrelationsResult = { - loading: false, - error: false, - data: [], - dataCount: 0, - alertsBySessionIds: ['alert1', 'alert2', 'alert3'], - sameSourceAlertsIds: ['alert1', 'alert2'], - ancestryAlertsIds: ['alert3'], - cases: mockCasesByAlertId, -}; - -const contextValue: LeftPanelContext = { - indexName: 'index', - eventId: 'event', - getFieldsData: () => null, - dataFormattedForFieldBrowser: [], - dataAsNestedObject: null, - scopeId: '', - browserFields: null, - searchHit: undefined, - investigationFields: [], -}; +import { useShowRelatedAlertsByAncestry } from '../../shared/hooks/use_show_related_alerts_by_ancestry'; +import { useShowRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_show_related_alerts_by_same_source_event'; +import { useShowRelatedAlertsBySession } from '../../shared/hooks/use_show_related_alerts_by_session'; +import { useShowRelatedCases } from '../../shared/hooks/use_show_related_cases'; +import { + CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID, + CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID, + CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID, + CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID, + CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID, + CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID, + CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID, + CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID, +} from './test_ids'; +import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session'; +import { useFetchRelatedAlertsByAncestry } from '../../shared/hooks/use_fetch_related_alerts_by_ancestry'; +import { useFetchRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_fetch_related_alerts_by_same_source_event'; +import { useFetchRelatedCases } from '../../shared/hooks/use_fetch_related_cases'; +import { mockContextValue } from '../mocks/mock_context'; + +jest.mock('react-router-dom', () => { + const actual = jest.requireActual('react-router-dom'); + return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; +}); +jest.mock('../../shared/hooks/use_show_related_alerts_by_ancestry'); +jest.mock('../../shared/hooks/use_show_related_alerts_by_same_source_event'); +jest.mock('../../shared/hooks/use_show_related_alerts_by_session'); +jest.mock('../../shared/hooks/use_show_related_cases'); +jest.mock('../../shared/hooks/use_fetch_related_alerts_by_session'); +jest.mock('../../shared/hooks/use_fetch_related_alerts_by_ancestry'); +jest.mock('../../shared/hooks/use_fetch_related_alerts_by_same_source_event'); +jest.mock('../../shared/hooks/use_fetch_related_cases'); const renderCorrelationDetails = () => { return render( - + @@ -71,46 +56,84 @@ const renderCorrelationDetails = () => { describe('CorrelationsDetails', () => { beforeEach(() => { jest.clearAllMocks(); - - jest - .mocked(useSourcererDataView) - .mockReturnValue({ runtimeMappings: {} } as unknown as SelectedDataView); }); - it('renders loading spinner when data is loading', () => { + it('renders all sections', () => { jest - .mocked(useTimelineEventsDetails) - .mockReturnValue([true, null, undefined, null, async () => {}]); - jest.mocked(useCorrelations).mockReturnValue(mockUseCorrelationsResult); - - renderCorrelationDetails(); - - expect(screen.getByRole('progressbar')).toBeInTheDocument(); + .mocked(useShowRelatedAlertsByAncestry) + .mockReturnValue({ show: true, documentId: 'documentId', indices: ['index1'] }); + jest + .mocked(useShowRelatedAlertsBySameSourceEvent) + .mockReturnValue({ show: true, originalEventId: 'originalEventId' }); + jest + .mocked(useShowRelatedAlertsBySession) + .mockReturnValue({ show: true, entityId: 'entityId' }); + jest.mocked(useShowRelatedCases).mockReturnValue(true); + + (useFetchRelatedAlertsByAncestry as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: [], + dataCount: 1, + }); + (useFetchRelatedAlertsBySameSourceEvent as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: [], + dataCount: 1, + }); + (useFetchRelatedAlertsBySession as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: [], + dataCount: 1, + }); + (useFetchRelatedCases as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: [], + dataCount: 1, + }); + + const { getByTestId } = renderCorrelationDetails(); + + expect(getByTestId(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID)).toBeInTheDocument(); }); - it('renders error message when there is an error', () => { + it('should render no section if show values are false', () => { + jest + .mocked(useShowRelatedAlertsByAncestry) + .mockReturnValue({ show: false, documentId: 'documentId', indices: ['index1'] }); jest - .mocked(useTimelineEventsDetails) - .mockReturnValue([false, null, undefined, null, async () => {}]); - jest.mocked(useCorrelations).mockReturnValue({ ...mockUseCorrelationsResult, error: true }); + .mocked(useShowRelatedAlertsBySameSourceEvent) + .mockReturnValue({ show: false, originalEventId: 'originalEventId' }); + jest + .mocked(useShowRelatedAlertsBySession) + .mockReturnValue({ show: false, entityId: 'entityId' }); + jest.mocked(useShowRelatedCases).mockReturnValue(false); - renderCorrelationDetails(); + const { queryByTestId } = renderCorrelationDetails(); - expect( - screen.getByText('There was an error displaying Correlation Details view') - ).toBeInTheDocument(); + expect(queryByTestId(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID)).not.toBeInTheDocument(); }); - it('renders alerts tables when data is loaded', () => { - jest - .mocked(useTimelineEventsDetails) - .mockReturnValue([false, null, undefined, null, async () => {}]); - jest.mocked(useCorrelations).mockReturnValue(mockUseCorrelationsResult); + it('should render no section if values are null', () => { + jest.mocked(useShowRelatedAlertsByAncestry).mockReturnValue({ show: true }); + jest.mocked(useShowRelatedAlertsBySameSourceEvent).mockReturnValue({ show: true }); + jest.mocked(useShowRelatedAlertsBySession).mockReturnValue({ show: true }); + jest.mocked(useShowRelatedCases).mockReturnValue(false); - renderCorrelationDetails(); + const { queryByTestId } = renderCorrelationDetails(); - expect(screen.getByText('1 alert related by ancestry')).toBeInTheDocument(); - expect(screen.getByText('2 alerts related by source event')).toBeInTheDocument(); - expect(screen.getByText('3 alerts related by session')).toBeInTheDocument(); + expect(queryByTestId(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID)).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.tsx index 85b497716b32d9..2a738a4fb6fd7e 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.tsx @@ -6,41 +6,17 @@ */ import React from 'react'; -import { - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingSpinner, - EuiSpacer, -} from '@elastic/eui'; -import { useTimelineEventsDetails } from '../../../timelines/containers/details'; -import { useSourcererDataView } from '../../../common/containers/sourcerer'; +import { EuiSpacer } from '@elastic/eui'; +import { RelatedAlertsBySession } from './related_alerts_by_session'; +import { RelatedAlertsBySameSourceEvent } from './related_alerts_by_same_source_event'; +import { RelatedCases } from './related_cases'; +import { useShowRelatedCases } from '../../shared/hooks/use_show_related_cases'; +import { useShowRelatedAlertsByAncestry } from '../../shared/hooks/use_show_related_alerts_by_ancestry'; -import { useCorrelations } from '../../shared/hooks/use_correlations'; import { useLeftPanelContext } from '../context'; -import { useRouteSpy } from '../../../common/utils/route/use_route_spy'; -import { SecurityPageName } from '../../../../common'; -import { SourcererScopeName } from '../../../common/store/sourcerer/model'; -import { AlertsTable } from './correlations_details_alerts_table'; -import { ERROR_MESSAGE, ERROR_TITLE } from '../../shared/translations'; -import { - CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID, - CORRELATIONS_DETAILS_BY_ANCESTRY_TABLE_TEST_ID, - CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID, - CORRELATIONS_DETAILS_BY_SESSION_TABLE_TEST_ID, - CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID, - CORRELATIONS_DETAILS_BY_SOURCE_TABLE_TEST_ID, - CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID, - CORRELATIONS_DETAILS_ERROR_TEST_ID, -} from './test_ids'; -import { CorrelationsCasesTable } from './correlations_cases_table'; -import { - ANCESTRY_ALERTS_HEADING, - RELATED_CASES_HEADING, - SESSION_ALERTS_HEADING, - SOURCE_ALERTS_HEADING, -} from './translations'; -import { ExpandablePanel } from '../../shared/components/expandable_panel'; +import { useShowRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_show_related_alerts_by_same_source_event'; +import { useShowRelatedAlertsBySession } from '../../shared/hooks/use_show_related_alerts_by_session'; +import { RelatedAlertsByAncestry } from './related_alerts_by_ancestry'; export const CORRELATIONS_TAB_ID = 'correlations-details'; @@ -48,121 +24,45 @@ export const CORRELATIONS_TAB_ID = 'correlations-details'; * Correlations displayed in the document details expandable flyout left section under the Insights tab */ export const CorrelationsDetails: React.FC = () => { - const { indexName, eventId, scopeId } = useLeftPanelContext(); - - const [{ pageName }] = useRouteSpy(); - const sourcererScope = - pageName === SecurityPageName.detections - ? SourcererScopeName.detections - : SourcererScopeName.default; - - const sourcererDataView = useSourcererDataView(sourcererScope); - - const [isEventDataLoading, eventData, _searchHit, dataAsNestedObject] = useTimelineEventsDetails({ - indexName, - eventId, - runtimeMappings: sourcererDataView.runtimeMappings, - skip: !eventId, - }); + const { dataAsNestedObject, dataFormattedForFieldBrowser, eventId, getFieldsData, scopeId } = + useLeftPanelContext(); const { - loading: isCorrelationsLoading, - error: correlationsError, - ancestryAlertsIds, - alertsBySessionIds, - sameSourceAlertsIds, - cases, - } = useCorrelations({ - eventId, + show: showAlertsByAncestry, + documentId, + indices, + } = useShowRelatedAlertsByAncestry({ + getFieldsData, dataAsNestedObject, - dataFormattedForFieldBrowser: eventData, - scopeId, + dataFormattedForFieldBrowser, }); - - const topLevelLoading = isEventDataLoading || isCorrelationsLoading; - - if (topLevelLoading) { - return ( - - - - - - ); - } - - if (correlationsError) { - return ( - {ERROR_TITLE('Correlation Details')}} - body={

{ERROR_MESSAGE('Correlation Details view')}

} - data-test-subj={CORRELATIONS_DETAILS_ERROR_TEST_ID} - /> - ); - } + const { show: showSameSourceAlerts, originalEventId } = useShowRelatedAlertsBySameSourceEvent({ + getFieldsData, + }); + const { show: showAlertsBySession, entityId } = useShowRelatedAlertsBySession({ getFieldsData }); + const showCases = useShowRelatedCases(); return ( <> - - - + {showAlertsByAncestry && documentId && indices && ( + + )} - - - + {showSameSourceAlerts && originalEventId && ( + + )} - - - + {showAlertsBySession && entityId && ( + + )} - - - + {showCases && } ); }; diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.test.tsx index 156b254eaf0816..2b680b3ab071ff 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { EuiBasicTable } from '@elastic/eui'; -import { AlertsTable, columns } from './correlations_details_alerts_table'; +import { CorrelationsDetailsAlertsTable, columns } from './correlations_details_alerts_table'; import { usePaginatedAlerts } from '../hooks/use_paginated_alerts'; jest.mock('../hooks/use_paginated_alerts'); @@ -59,7 +59,7 @@ describe('AlertsTable', () => { }); it('renders EuiBasicTable with correct props', () => { - render(); + render(); expect(jest.mocked(usePaginatedAlerts)).toHaveBeenCalled(); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.tsx index 28d05d3a08d701..5db38fcbaee54f 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.tsx @@ -6,15 +6,15 @@ */ import React, { type FC, useMemo, useCallback } from 'react'; -import { type Criteria, EuiBasicTable, formatDate, EuiEmptyPrompt } from '@elastic/eui'; +import { type Criteria, EuiBasicTable, formatDate } from '@elastic/eui'; import { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import { isRight } from 'fp-ts/lib/Either'; import { ALERT_REASON, ALERT_RULE_NAME } from '@kbn/rule-data-utils'; import { SeverityBadge } from '../../../detections/components/rules/severity_badge'; import { usePaginatedAlerts } from '../hooks/use_paginated_alerts'; -import { ERROR_MESSAGE, ERROR_TITLE } from '../../shared/translations'; import * as i18n from './translations'; +import { ExpandablePanel } from '../../shared/components/expandable_panel'; export const TIMESTAMP_DATE_FORMAT = 'MMM D, YYYY @ HH:mm:ss.SSS'; @@ -47,11 +47,19 @@ export const columns = [ }, ]; -export interface AlertsTableProps { +export interface CorrelationsDetailsAlertsTableProps { + /** + * Text to display in the ExpandablePanel title section + */ + title: string; + /** + * Whether the table is loading + */ + loading: boolean; /** * Ids of alerts to display in the table */ - alertIds: string[]; + alertIds: string[] | undefined; /** * Data test subject string for testing */ @@ -61,9 +69,21 @@ export interface AlertsTableProps { /** * Renders paginated alert array based on the provided alertIds */ -export const AlertsTable: FC = ({ alertIds, 'data-test-subj': dataTestSubj }) => { - const { setPagination, setSorting, data, loading, paginationConfig, sorting, error } = - usePaginatedAlerts(alertIds); +export const CorrelationsDetailsAlertsTable: FC = ({ + title, + loading, + alertIds, + 'data-test-subj': dataTestSubj, +}) => { + const { + setPagination, + setSorting, + data, + loading: alertsLoading, + paginationConfig, + sorting, + error, + } = usePaginatedAlerts(alertIds || []); const onTableChange = useCallback( ({ page, sort }: Criteria>) => { @@ -90,27 +110,28 @@ export const AlertsTable: FC = ({ alertIds, 'data-test-subj': ); }, [data]); - if (error) { - return ( - {ERROR_TITLE('alert data')}} - body={

{ERROR_MESSAGE('alert data')}

} - data-test-subj={`${dataTestSubj}Error`} - /> - ); - } - return ( - > + + > + > + data-test-subj={`${dataTestSubj}Table`} + loading={loading || alertsLoading} + items={mappedData} + columns={columns} + pagination={paginationConfig} + sorting={sorting} + onChange={onTableChange} + /> + ); }; diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.test.tsx index 0446218830cef1..d02f84207ecdef 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.test.tsx @@ -13,6 +13,7 @@ import { TestProviders } from '../../../common/mock'; import { EntitiesDetails } from './entities_details'; import { ENTITIES_DETAILS_TEST_ID, HOST_DETAILS_TEST_ID, USER_DETAILS_TEST_ID } from './test_ids'; import { mockContextValue } from '../mocks/mock_context'; +import { EXPANDABLE_PANEL_CONTENT_TEST_ID } from '../../shared/components/test_ids'; jest.mock('react-router-dom', () => { const actual = jest.requireActual('react-router-dom'); @@ -31,6 +32,9 @@ jest.mock('react-redux', () => { }; }); +const USER_TEST_ID = EXPANDABLE_PANEL_CONTENT_TEST_ID(USER_DETAILS_TEST_ID); +const HOST_TEST_ID = EXPANDABLE_PANEL_CONTENT_TEST_ID(HOST_DETAILS_TEST_ID); + describe('', () => { it('renders entities details correctly', () => { const { getByTestId } = render( @@ -41,8 +45,8 @@ describe('', () => { ); expect(getByTestId(ENTITIES_DETAILS_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(USER_DETAILS_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(HOST_DETAILS_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(USER_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(HOST_TEST_ID)).toBeInTheDocument(); }); it('does not render user and host details if user name and host name are not available', () => { @@ -59,8 +63,8 @@ describe('', () => { ); - expect(queryByTestId(USER_DETAILS_TEST_ID)).not.toBeInTheDocument(); - expect(queryByTestId(HOST_DETAILS_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(USER_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(HOST_TEST_ID)).not.toBeInTheDocument(); }); it('does not render user and host details if @timestamp is not available', () => { @@ -85,7 +89,7 @@ describe('', () => { ); - expect(queryByTestId(USER_DETAILS_TEST_ID)).not.toBeInTheDocument(); - expect(queryByTestId(HOST_DETAILS_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(USER_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(HOST_TEST_ID)).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/host_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/host_details.test.tsx index d3281b21698771..4ee515e539442c 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/host_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/host_details.test.tsx @@ -21,6 +21,7 @@ import { HOST_DETAILS_INFO_TEST_ID, HOST_DETAILS_RELATED_USERS_TABLE_TEST_ID, } from './test_ids'; +import { EXPANDABLE_PANEL_CONTENT_TEST_ID } from '../../shared/components/test_ids'; jest.mock('react-router-dom', () => { const actual = jest.requireActual('react-router-dom'); @@ -134,7 +135,7 @@ describe('', () => { ); - expect(getByTestId(HOST_DETAILS_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(HOST_DETAILS_TEST_ID))).toBeInTheDocument(); }); describe('Host overview', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_ancestry.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_ancestry.test.tsx new file mode 100644 index 00000000000000..2346c568cd8f8e --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_ancestry.test.tsx @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { + CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID, + CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID, +} from './test_ids'; +import { RelatedAlertsByAncestry } from './related_alerts_by_ancestry'; +import { useFetchRelatedAlertsByAncestry } from '../../shared/hooks/use_fetch_related_alerts_by_ancestry'; +import { + EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID, + EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID, +} from '../../shared/components/test_ids'; +import { usePaginatedAlerts } from '../hooks/use_paginated_alerts'; + +jest.mock('../../shared/hooks/use_fetch_related_alerts_by_ancestry'); +jest.mock('../hooks/use_paginated_alerts'); + +const documentId = 'documentId'; +const indices = ['index1']; +const scopeId = 'scopeId'; + +const TOGGLE_ICON = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID( + CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID +); +const TITLE_ICON = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID( + CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID +); +const TITLE_TEXT = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( + CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID +); + +describe('', () => { + it('should render many related alerts correctly', () => { + (useFetchRelatedAlertsByAncestry as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: ['1', '2'], + dataCount: 2, + }); + (usePaginatedAlerts as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: [ + { + _id: '1', + _index: 'index', + fields: { + '@timestamp': ['2022-01-01'], + 'kibana.alert.rule.name': ['Rule1'], + 'kibana.alert.reason': ['Reason1'], + 'kibana.alert.severity': ['Severity1'], + }, + }, + { + _id: '2', + _index: 'index', + fields: { + '@timestamp': ['2022-01-02'], + 'kibana.alert.rule.name': ['Rule2'], + 'kibana.alert.reason': ['Reason2'], + 'kibana.alert.severity': ['Severity2'], + }, + }, + ], + }); + + const { getByTestId } = render( + + ); + expect(getByTestId(TOGGLE_ICON)).toBeInTheDocument(); + expect(getByTestId(TITLE_ICON)).toBeInTheDocument(); + expect(getByTestId(TITLE_TEXT)).toBeInTheDocument(); + expect(getByTestId(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID)).toBeInTheDocument(); + }); + + it('should render null if error', () => { + (useFetchRelatedAlertsByAncestry as jest.Mock).mockReturnValue({ + loading: false, + error: true, + }); + + const { container } = render( + + ); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_ancestry.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_ancestry.tsx new file mode 100644 index 00000000000000..b144d7e8b47d04 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_ancestry.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { CorrelationsDetailsAlertsTable } from './correlations_details_alerts_table'; +import { CORRELATIONS_ANCESTRY_ALERTS } from '../../shared/translations'; +import { useFetchRelatedAlertsByAncestry } from '../../shared/hooks/use_fetch_related_alerts_by_ancestry'; +import { CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID } from './test_ids'; + +export interface RelatedAlertsByAncestryProps { + /** + * Value of the kibana.alert.ancestors.id field + */ + documentId: string; + /** + * Values of the kibana.alert.rule.parameters.index field + */ + indices: string[]; + /** + * Maintain backwards compatibility // TODO remove when possible + */ + scopeId: string; +} + +/** + * Show related alerts by ancestry in an expandable panel with a table + */ +export const RelatedAlertsByAncestry: React.VFC = ({ + documentId, + indices, + scopeId, +}) => { + const { loading, error, data, dataCount } = useFetchRelatedAlertsByAncestry({ + documentId, + indices, + scopeId, + }); + const title = `${dataCount} ${CORRELATIONS_ANCESTRY_ALERTS(dataCount)}`; + + if (error) { + return null; + } + + return ( + + ); +}; + +RelatedAlertsByAncestry.displayName = 'RelatedAlertsByAncestry'; diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_same_source_event.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_same_source_event.test.tsx new file mode 100644 index 00000000000000..bde789ab08f945 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_same_source_event.test.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { + CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID, + CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID, +} from './test_ids'; +import { useFetchRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_fetch_related_alerts_by_same_source_event'; +import { RelatedAlertsBySameSourceEvent } from './related_alerts_by_same_source_event'; +import { + EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID, + EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID, +} from '../../shared/components/test_ids'; +import { usePaginatedAlerts } from '../hooks/use_paginated_alerts'; + +jest.mock('../../shared/hooks/use_fetch_related_alerts_by_same_source_event'); +jest.mock('../hooks/use_paginated_alerts'); + +const originalEventId = 'originalEventId'; +const scopeId = 'scopeId'; + +const TOGGLE_ICON = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID( + CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID +); +const TITLE_ICON = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID( + CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID +); +const TITLE_TEXT = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( + CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID +); + +describe('', () => { + it('should render component correctly', () => { + (useFetchRelatedAlertsBySameSourceEvent as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: ['1', '2'], + dataCount: 2, + }); + (usePaginatedAlerts as jest.Mock).mockReturnValue({ + loading: false, + erro: false, + data: [ + { + _id: '1', + _index: 'index', + fields: { + '@timestamp': ['2022-01-01'], + 'kibana.alert.rule.name': ['Rule1'], + 'kibana.alert.reason': ['Reason1'], + 'kibana.alert.severity': ['Severity1'], + }, + }, + { + _id: '2', + _index: 'index', + fields: { + '@timestamp': ['2022-01-02'], + 'kibana.alert.rule.name': ['Rule2'], + 'kibana.alert.reason': ['Reason2'], + 'kibana.alert.severity': ['Severity2'], + }, + }, + ], + }); + + const { getByTestId } = render( + + ); + expect(getByTestId(TOGGLE_ICON)).toBeInTheDocument(); + expect(getByTestId(TITLE_ICON)).toBeInTheDocument(); + expect(getByTestId(TITLE_TEXT)).toBeInTheDocument(); + expect(getByTestId(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID)).toBeInTheDocument(); + }); + + it('should render null if error', () => { + (useFetchRelatedAlertsBySameSourceEvent as jest.Mock).mockReturnValue({ + loading: false, + error: true, + }); + + const { container } = render( + + ); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_same_source_event.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_same_source_event.tsx new file mode 100644 index 00000000000000..6ca1b245e88be7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_same_source_event.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { CORRELATIONS_SAME_SOURCE_ALERTS } from '../../shared/translations'; +import { useFetchRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_fetch_related_alerts_by_same_source_event'; +import { CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID } from './test_ids'; +import { CorrelationsDetailsAlertsTable } from './correlations_details_alerts_table'; + +export interface RelatedAlertsBySameSourceEventProps { + /** + * Value of the kibana.alert.original_event.id field + */ + originalEventId: string; + /** + * Maintain backwards compatibility // TODO remove when possible + */ + scopeId: string; +} + +/** + * Show related alerts by same source event in an expandable panel with a table + */ +export const RelatedAlertsBySameSourceEvent: React.VFC = ({ + originalEventId, + scopeId, +}) => { + const { loading, error, data, dataCount } = useFetchRelatedAlertsBySameSourceEvent({ + originalEventId, + scopeId, + }); + const title = `${dataCount} ${CORRELATIONS_SAME_SOURCE_ALERTS(dataCount)}`; + + if (error) { + return null; + } + + return ( + + ); +}; + +RelatedAlertsBySameSourceEvent.displayName = 'RelatedAlertsBySameSourceEvent'; diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_session.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_session.test.tsx new file mode 100644 index 00000000000000..fca466ed169b1d --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_session.test.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { + CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID, + CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID, +} from './test_ids'; +import { RelatedAlertsBySession } from './related_alerts_by_session'; +import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session'; +import { usePaginatedAlerts } from '../hooks/use_paginated_alerts'; +import { + EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID, + EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID, +} from '../../shared/components/test_ids'; + +jest.mock('../../shared/hooks/use_fetch_related_alerts_by_session'); +jest.mock('../hooks/use_paginated_alerts'); + +const entityId = 'entityId'; +const scopeId = 'scopeId'; + +const TOGGLE_ICON = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID( + CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID +); +const TITLE_ICON = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID( + CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID +); +const TITLE_TEXT = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( + CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID +); + +describe('', () => { + it('should render component correctly', () => { + (useFetchRelatedAlertsBySession as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: ['1', '2'], + dataCount: 2, + }); + (usePaginatedAlerts as jest.Mock).mockReturnValue({ + loading: false, + erro: false, + data: [ + { + _id: '1', + _index: 'index', + fields: { + '@timestamp': ['2022-01-01'], + 'kibana.alert.rule.name': ['Rule1'], + 'kibana.alert.reason': ['Reason1'], + 'kibana.alert.severity': ['Severity1'], + }, + }, + { + _id: '2', + _index: 'index', + fields: { + '@timestamp': ['2022-01-02'], + 'kibana.alert.rule.name': ['Rule2'], + 'kibana.alert.reason': ['Reason2'], + 'kibana.alert.severity': ['Severity2'], + }, + }, + ], + }); + + const { getByTestId } = render( + + ); + expect(getByTestId(TOGGLE_ICON)).toBeInTheDocument(); + expect(getByTestId(TITLE_ICON)).toBeInTheDocument(); + expect(getByTestId(TITLE_TEXT)).toBeInTheDocument(); + expect(getByTestId(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID)).toBeInTheDocument(); + }); + + it('should render null if error', () => { + (useFetchRelatedAlertsBySession as jest.Mock).mockReturnValue({ + loading: false, + error: true, + }); + + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_session.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_session.tsx new file mode 100644 index 00000000000000..9e12a06b5eada8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_alerts_by_session.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { CORRELATIONS_SESSION_ALERTS } from '../../shared/translations'; +import { CorrelationsDetailsAlertsTable } from './correlations_details_alerts_table'; +import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session'; +import { CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID } from './test_ids'; + +export interface RelatedAlertsBySessionProps { + /** + * Value of the process.entry_leader.entity_id field + */ + entityId: string; + /** + * Maintain backwards compatibility // TODO remove when possible + */ + scopeId: string; +} + +/** + * Show related alerts by session in an expandable panel with a table + */ +export const RelatedAlertsBySession: React.VFC = ({ + entityId, + scopeId, +}) => { + const { loading, error, data, dataCount } = useFetchRelatedAlertsBySession({ + entityId, + scopeId, + }); + const title = `${dataCount} ${CORRELATIONS_SESSION_ALERTS(dataCount)}`; + + if (error) { + return null; + } + + return ( + + ); +}; + +RelatedAlertsBySession.displayName = 'RelatedAlertsBySession'; diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.test.tsx new file mode 100644 index 00000000000000..ce11b234e83592 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.test.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { + CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID, + CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID, +} from './test_ids'; +import { RelatedCases } from './related_cases'; +import { useFetchRelatedCases } from '../../shared/hooks/use_fetch_related_cases'; +import { + EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID, + EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID, +} from '../../shared/components/test_ids'; + +jest.mock('../../shared/hooks/use_fetch_related_cases'); +jest.mock('../../../common/components/links', () => ({ + CaseDetailsLink: jest + .fn() + .mockImplementation(({ title }) => <>{``}), +})); + +const eventId = 'eventId'; + +const TOGGLE_ICON = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID( + CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID +); +const TITLE_ICON = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID( + CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID +); +const TITLE_TEXT = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( + CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID +); + +describe('', () => { + it('should render many related cases correctly', () => { + (useFetchRelatedCases as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: [ + { + id: 'id', + title: 'title', + description: 'description', + status: 'open', + }, + ], + dataCount: 1, + }); + + const { getByTestId } = render(); + expect(getByTestId(TOGGLE_ICON)).toBeInTheDocument(); + expect(getByTestId(TITLE_ICON)).toBeInTheDocument(); + expect(getByTestId(TITLE_TEXT)).toHaveTextContent('1 related case'); + expect(getByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID)).toBeInTheDocument(); + }); + + it('should render null if error', () => { + (useFetchRelatedCases as jest.Mock).mockReturnValue({ + loading: false, + error: true, + }); + + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.tsx new file mode 100644 index 00000000000000..71b6edf46df4f7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.tsx @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { EuiBasicTableColumn } from '@elastic/eui'; +import { EuiInMemoryTable, EuiSkeletonText } from '@elastic/eui'; +import type { RelatedCase } from '@kbn/cases-plugin/common'; +import { CaseDetailsLink } from '../../../common/components/links'; +import { CORRELATIONS_RELATED_CASES } from '../../shared/translations'; +import { + CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID, + CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID, +} from './test_ids'; +import { useFetchRelatedCases } from '../../shared/hooks/use_fetch_related_cases'; +import { ExpandablePanel } from '../../shared/components/expandable_panel'; +import { + CORRELATIONS_CASE_NAME_COLUMN_TITLE, + CORRELATIONS_CASE_STATUS_COLUMN_TITLE, +} from './translations'; + +const ICON = 'warning'; + +const columns: Array> = [ + { + field: 'title', + name: CORRELATIONS_CASE_NAME_COLUMN_TITLE, + truncateText: true, + render: (value: string, caseData: RelatedCase) => ( + + {caseData.title} + + ), + }, + { + field: 'status', + name: CORRELATIONS_CASE_STATUS_COLUMN_TITLE, + truncateText: true, + }, +]; + +export interface RelatedCasesProps { + /** + * Id of the document + */ + eventId: string; +} + +/** + * + */ +export const RelatedCases: React.VFC = ({ eventId }) => { + const { loading, error, data, dataCount } = useFetchRelatedCases({ eventId }); + const title = `${dataCount} ${CORRELATIONS_RELATED_CASES(dataCount)}`; + + if (loading) { + return ; + } + + if (error) { + return null; + } + + return ( + + + + ); +}; + +RelatedCases.displayName = 'RelatedCases'; diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/session_view.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/session_view.test.tsx index 16c15e971f7a78..8438ab478cfb4a 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/session_view.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/session_view.test.tsx @@ -11,21 +11,21 @@ import '@testing-library/jest-dom'; import { LeftPanelContext } from '../context'; import { TestProviders } from '../../../common/mock'; import { SESSION_VIEW_ERROR_TEST_ID, SESSION_VIEW_TEST_ID } from './test_ids'; +import { SessionView } from './session_view'; import { - SessionView, - SESSION_ENTITY_ID, - SESSION_START_TIME, - KIBANA_ANCESTOR_INDEX, -} from './session_view'; + ANCESTOR_INDEX, + ENTRY_LEADER_ENTITY_ID, + ENTRY_LEADER_START, +} from '../../shared/constants/field_names'; interface MockData { [key: string]: string; } const mockData: MockData = { - [SESSION_ENTITY_ID]: 'id', - [SESSION_START_TIME]: '2023-04-25T04:33:23.676Z', - [KIBANA_ANCESTOR_INDEX]: '.ds-logs-endpoint.events.process-default', + [ENTRY_LEADER_ENTITY_ID]: 'id', + [ENTRY_LEADER_START]: '2023-04-25T04:33:23.676Z', + [ANCESTOR_INDEX]: '.ds-logs-endpoint.events.process-default', }; const mockFieldsData = (prop: string) => { diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/session_view.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/session_view.tsx index 94921bce0f82d8..48f1164f751684 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/session_view.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/session_view.tsx @@ -8,6 +8,11 @@ import type { FC } from 'react'; import React from 'react'; import { EuiEmptyPrompt } from '@elastic/eui'; +import { + ANCESTOR_INDEX, + ENTRY_LEADER_ENTITY_ID, + ENTRY_LEADER_START, +} from '../../shared/constants/field_names'; import { getField } from '../../shared/utils'; import { ERROR_MESSAGE, ERROR_TITLE } from '../../shared/translations'; import { SESSION_VIEW_ERROR_MESSAGE } from './translations'; @@ -15,10 +20,7 @@ import { SESSION_VIEW_ERROR_TEST_ID, SESSION_VIEW_TEST_ID } from './test_ids'; import { useKibana } from '../../../common/lib/kibana'; import { useLeftPanelContext } from '../context'; -export const SESSION_VIEW_ID = 'session_view'; -export const SESSION_ENTITY_ID = 'process.entry_leader.entity_id'; -export const SESSION_START_TIME = 'process.entry_leader.start'; -export const KIBANA_ANCESTOR_INDEX = 'kibana.alert.ancestors.index'; +export const SESSION_VIEW_ID = 'session-view'; /** * Session view displayed in the document details expandable flyout left section under the Visualize tab @@ -27,9 +29,9 @@ export const SessionView: FC = () => { const { sessionView } = useKibana().services; const { getFieldsData, indexName } = useLeftPanelContext(); - const ancestorIndex = getField(getFieldsData(KIBANA_ANCESTOR_INDEX)); // e.g in case of alert, we want to grab it's origin index - const sessionEntityId = getField(getFieldsData(SESSION_ENTITY_ID)); - const sessionStartTime = getField(getFieldsData(SESSION_START_TIME)); + const ancestorIndex = getField(getFieldsData(ANCESTOR_INDEX)); // e.g in case of alert, we want to grab it's origin index + const sessionEntityId = getField(getFieldsData(ENTRY_LEADER_ENTITY_ID)); + const sessionStartTime = getField(getFieldsData(ENTRY_LEADER_START)); const index = ancestorIndex || indexName; if (!index || !sessionEntityId || !sessionStartTime) { diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/left/components/test_ids.ts index ea58b280a101e3..c90b61762a0cd0 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/left/components/test_ids.ts @@ -45,6 +45,7 @@ export const PREVALENCE_DETAILS_PREVALENCE_CELL_VALUE_TEST_ID = `${PREFIX}PrevalenceDetailsPrevalenceCellValue` as const; /* Entities */ + export const ENTITIES_DETAILS_TEST_ID = `${PREFIX}EntitiesDetails` as const; export const USER_DETAILS_TEST_ID = `${PREFIX}UsersDetails` as const; export const USER_DETAILS_INFO_TEST_ID = 'user-overview'; @@ -61,24 +62,22 @@ export const THREAT_INTELLIGENCE_DETAILS_ENRICHMENTS_TEST_ID = `threat-match-det export const THREAT_INTELLIGENCE_DETAILS_SPINNER_TEST_ID = `${PREFIX}ThreatIntelligenceDetailsLoadingSpinner` as const; -export const CORRELATIONS_DETAILS_ERROR_TEST_ID = `${CORRELATIONS_DETAILS_TEST_ID}Error` as const; - -export const CORRELATIONS_DETAILS_BY_ANCESTRY_TABLE_TEST_ID = - `${CORRELATIONS_DETAILS_TEST_ID}AlertsByAncestryTable` as const; -export const CORRELATIONS_DETAILS_BY_SOURCE_TABLE_TEST_ID = - `${CORRELATIONS_DETAILS_TEST_ID}AlertsBySourceTable` as const; -export const CORRELATIONS_DETAILS_BY_SESSION_TABLE_TEST_ID = - `${CORRELATIONS_DETAILS_TEST_ID}AlertsBySessionTable` as const; - export const CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID = `${CORRELATIONS_DETAILS_TEST_ID}AlertsByAncestrySection` as const; +export const CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID = + `${CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID}Table` as const; export const CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID = `${CORRELATIONS_DETAILS_TEST_ID}AlertsBySourceSection` as const; +export const CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID = + `${CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID}Table` as const; export const CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID = `${CORRELATIONS_DETAILS_TEST_ID}AlertsBySessionSection` as const; +export const CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID = + `${CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID}Table` as const; export const CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID = `${CORRELATIONS_DETAILS_TEST_ID}CasesSection` as const; - +export const CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID = + `${CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID}Table` as const; export const RESPONSE_BASE_TEST_ID = `${PREFIX}Responses` as const; export const RESPONSE_DETAILS_TEST_ID = `${RESPONSE_BASE_TEST_ID}Details` as const; export const RESPONSE_EMPTY_TEST_ID = `${RESPONSE_BASE_TEST_ID}Empty` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/user_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/user_details.test.tsx index 6f449e22791959..51eaf1bb76b740 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/user_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/user_details.test.tsx @@ -21,6 +21,7 @@ import { USER_DETAILS_INFO_TEST_ID, USER_DETAILS_RELATED_HOSTS_TABLE_TEST_ID, } from './test_ids'; +import { EXPANDABLE_PANEL_CONTENT_TEST_ID } from '../../shared/components/test_ids'; jest.mock('react-router-dom', () => { const actual = jest.requireActual('react-router-dom'); @@ -132,7 +133,7 @@ describe('', () => { ); - expect(getByTestId(USER_DETAILS_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(USER_DETAILS_TEST_ID))).toBeInTheDocument(); }); describe('Host overview', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/left/mocks/mock_context.ts b/x-pack/plugins/security_solution/public/flyout/left/mocks/mock_context.ts index 3569570568986b..abc36f1bf202a1 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/mocks/mock_context.ts +++ b/x-pack/plugins/security_solution/public/flyout/left/mocks/mock_context.ts @@ -37,8 +37,8 @@ export const mockContextValue: LeftPanelContext = { eventId: 'eventId', indexName: 'index', scopeId: 'scopeId', - browserFields: null, - dataFormattedForFieldBrowser: null, + browserFields: {}, + dataFormattedForFieldBrowser: [], getFieldsData: mockGetFieldsData, searchHit: { _id: 'testId', diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.test.tsx index 8d691ad870892b..d8b1ac785b6ef6 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.test.tsx @@ -15,6 +15,7 @@ import { RightPanelContext } from '../context'; import { AnalyzerPreview } from './analyzer_preview'; import { ANALYZER_PREVIEW_TEST_ID } from './test_ids'; import * as mock from '../mocks/mock_analyzer_data'; +import { EXPANDABLE_PANEL_CONTENT_TEST_ID } from '../../shared/components/test_ids'; jest.mock('../../../common/containers/alerts/use_alert_prevalence_from_process_tree', () => ({ useAlertPrevalenceFromProcessTree: jest.fn(), @@ -64,7 +65,9 @@ describe('', () => { documentId: 'ancestors-id', indices: ['rule-parameters-index'], }); - expect(wrapper.getByTestId(ANALYZER_PREVIEW_TEST_ID)).toBeInTheDocument(); + expect( + wrapper.getByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(ANALYZER_PREVIEW_TEST_ID)) + ).toBeInTheDocument(); }); it('does not show analyzer preview when documentid and index are not present', () => { @@ -87,6 +90,8 @@ describe('', () => { documentId: '', indices: [], }); - expect(queryByTestId(ANALYZER_PREVIEW_TEST_ID)).not.toBeInTheDocument(); + expect( + queryByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(ANALYZER_PREVIEW_TEST_ID)) + ).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.tsx index e26ede68bc3979..bd1f90dd70b8d1 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.tsx @@ -6,6 +6,7 @@ */ import React, { useEffect, useState } from 'react'; import { find } from 'lodash/fp'; +import { ANCESTOR_ID, RULE_PARAMETERS_INDEX } from '../../shared/constants/field_names'; import { useRightPanelContext } from '../context'; import { useAlertPrevalenceFromProcessTree } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; import type { StatsNode } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; @@ -30,11 +31,11 @@ export const AnalyzerPreview: React.FC = () => { const [cache, setCache] = useState>({}); const { dataFormattedForFieldBrowser: data, scopeId } = useRightPanelContext(); - const documentId = find({ category: 'kibana', field: 'kibana.alert.ancestors.id' }, data); + const documentId = find({ category: 'kibana', field: ANCESTOR_ID }, data); const processDocumentId = documentId && Array.isArray(documentId.values) ? documentId.values[0] : ''; - const index = find({ category: 'kibana', field: 'kibana.alert.rule.parameters.index' }, data); + const index = find({ category: 'kibana', field: RULE_PARAMETERS_INDEX }, data); const indices = index?.values ?? []; const { loading, error, statsNodes } = useAlertPrevalenceFromProcessTree({ diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_tree.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_tree.test.tsx index 8cbc791c234d2b..62038fe5992000 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_tree.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_tree.test.tsx @@ -7,14 +7,8 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { - ANALYZER_PREVIEW_TOGGLE_ICON_TEST_ID, - ANALYZER_PREVIEW_TITLE_LINK_TEST_ID, - ANALYZER_PREVIEW_TITLE_ICON_TEST_ID, - ANALYZER_PREVIEW_CONTENT_TEST_ID, - ANALYZER_PREVIEW_TITLE_TEXT_TEST_ID, - ANALYZER_PREVIEW_LOADING_TEST_ID, -} from './test_ids'; +import { ANALYZER_PREVIEW_TEST_ID } from './test_ids'; +import { ANALYZE_GRAPH_ID } from '../../left/components/analyze_graph'; import { ANALYZER_PREVIEW_TITLE } from './translations'; import * as mock from '../mocks/mock_analyzer_data'; import type { AnalyzerTreeProps } from './analyzer_tree'; @@ -23,7 +17,21 @@ import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { TestProviders } from '@kbn/timelines-plugin/public/mock'; import { RightPanelContext } from '../context'; import { LeftPanelKey, LeftPanelVisualizeTab } from '../../left'; -import { ANALYZE_GRAPH_ID } from '../../left/components/analyze_graph'; + +import { + EXPANDABLE_PANEL_CONTENT_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID, + EXPANDABLE_PANEL_LOADING_TEST_ID, + EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID, +} from '../../shared/components/test_ids'; +const TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(ANALYZER_PREVIEW_TEST_ID); +const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(ANALYZER_PREVIEW_TEST_ID); +const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(ANALYZER_PREVIEW_TEST_ID); +const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(ANALYZER_PREVIEW_TEST_ID); +const CONTENT_TEST_ID = EXPANDABLE_PANEL_CONTENT_TEST_ID(ANALYZER_PREVIEW_TEST_ID); +const LOADING_TEST_ID = EXPANDABLE_PANEL_LOADING_TEST_ID(ANALYZER_PREVIEW_TEST_ID); const defaultProps: AnalyzerTreeProps = { statsNodes: mock.mockStatsNodes, @@ -56,17 +64,17 @@ const renderAnalyzerTree = (children: React.ReactNode) => describe('', () => { it('should render wrapper component', () => { const { getByTestId, queryByTestId } = renderAnalyzerTree(); - expect(queryByTestId(ANALYZER_PREVIEW_TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); - expect(getByTestId(ANALYZER_PREVIEW_TITLE_LINK_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(ANALYZER_PREVIEW_TITLE_ICON_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(ANALYZER_PREVIEW_CONTENT_TEST_ID)).toBeInTheDocument(); - expect(queryByTestId(ANALYZER_PREVIEW_TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(TITLE_LINK_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(TITLE_ICON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(CONTENT_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); }); it('should render the component when data is passed', () => { const { getByTestId, getByText } = renderAnalyzerTree(); expect(getByText(ANALYZER_PREVIEW_TITLE)).toBeInTheDocument(); - expect(getByTestId(ANALYZER_PREVIEW_CONTENT_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(CONTENT_TEST_ID)).toBeInTheDocument(); }); it('should render blank when data is not passed', () => { @@ -74,23 +82,23 @@ describe('', () => { ); expect(queryByText(ANALYZER_PREVIEW_TITLE)).not.toBeInTheDocument(); - expect(queryByTestId(ANALYZER_PREVIEW_CONTENT_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(CONTENT_TEST_ID)).not.toBeInTheDocument(); }); it('should render loading spinner when loading is true', () => { const { getByTestId } = renderAnalyzerTree(); - expect(getByTestId(ANALYZER_PREVIEW_LOADING_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(LOADING_TEST_ID)).toBeInTheDocument(); }); it('should not render when error is true', () => { const { getByTestId } = renderAnalyzerTree(); - expect(getByTestId(ANALYZER_PREVIEW_CONTENT_TEST_ID)).toBeEmptyDOMElement(); + expect(getByTestId(CONTENT_TEST_ID)).toBeEmptyDOMElement(); }); it('should navigate to left section Visualize tab when clicking on title', () => { const { getByTestId } = renderAnalyzerTree(); - getByTestId(ANALYZER_PREVIEW_TITLE_LINK_TEST_ID).click(); + getByTestId(TITLE_LINK_TEST_ID).click(); expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ id: LeftPanelKey, path: { tab: LeftPanelVisualizeTab, subTab: ANALYZE_GRAPH_ID }, diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.test.tsx index 70b37762b704b8..d446f5d84a723b 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.test.tsx @@ -11,25 +11,69 @@ import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { RightPanelContext } from '../context'; import { TestProviders } from '../../../common/mock'; import { CorrelationsOverview } from './correlations_overview'; -import { useCorrelations } from '../../shared/hooks/use_correlations'; import { CORRELATIONS_TAB_ID } from '../../left/components/correlations_details'; import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; import { - INSIGHTS_CORRELATIONS_CONTENT_TEST_ID, - INSIGHTS_CORRELATIONS_LOADING_TEST_ID, - INSIGHTS_CORRELATIONS_TITLE_ICON_TEST_ID, - INSIGHTS_CORRELATIONS_TITLE_LINK_TEST_ID, - INSIGHTS_CORRELATIONS_TITLE_TEXT_TEST_ID, - INSIGHTS_CORRELATIONS_TOGGLE_ICON_TEST_ID, + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID, + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID, + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID, + INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID, + INSIGHTS_CORRELATIONS_TEST_ID, + SUMMARY_ROW_VALUE_TEST_ID, } from './test_ids'; +import { useShowRelatedAlertsByAncestry } from '../../shared/hooks/use_show_related_alerts_by_ancestry'; +import { useShowRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_show_related_alerts_by_same_source_event'; +import { useShowRelatedAlertsBySession } from '../../shared/hooks/use_show_related_alerts_by_session'; +import { useShowRelatedCases } from '../../shared/hooks/use_show_related_cases'; +import { useFetchRelatedAlertsByAncestry } from '../../shared/hooks/use_fetch_related_alerts_by_ancestry'; +import { useFetchRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_fetch_related_alerts_by_same_source_event'; +import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session'; +import { useFetchRelatedCases } from '../../shared/hooks/use_fetch_related_cases'; +import { + EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID, + EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID, +} from '../../shared/components/test_ids'; + +jest.mock('../../shared/hooks/use_show_related_alerts_by_ancestry'); +jest.mock('../../shared/hooks/use_show_related_alerts_by_same_source_event'); +jest.mock('../../shared/hooks/use_show_related_alerts_by_session'); +jest.mock('../../shared/hooks/use_show_related_cases'); +jest.mock('../../shared/hooks/use_fetch_related_alerts_by_session'); +jest.mock('../../shared/hooks/use_fetch_related_alerts_by_ancestry'); +jest.mock('../../shared/hooks/use_fetch_related_alerts_by_same_source_event'); +jest.mock('../../shared/hooks/use_fetch_related_cases'); + +const TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(INSIGHTS_CORRELATIONS_TEST_ID); +const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID( + INSIGHTS_CORRELATIONS_TEST_ID +); +const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID( + INSIGHTS_CORRELATIONS_TEST_ID +); +const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( + INSIGHTS_CORRELATIONS_TEST_ID +); -jest.mock('../../shared/hooks/use_correlations'); +const RELATED_ALERTS_BY_ANCESTRY_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID +); +const RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID +); +const RELATED_ALERTS_BY_SESSION_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID +); +const RELATED_CASES_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( + INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID +); const panelContextValue = { eventId: 'event id', indexName: 'indexName', browserFields: {}, - dataFormattedForFieldBrowser: [], + getFieldsData: () => {}, scopeId: 'scopeId', } as unknown as RightPanelContext; @@ -43,101 +87,91 @@ const renderCorrelationsOverview = (contextValue: RightPanelContext) => ( describe('', () => { it('should render wrapper component', () => { - (useCorrelations as jest.Mock).mockReturnValue({ - loading: false, - error: false, - data: [], - }); + jest.mocked(useShowRelatedAlertsByAncestry).mockReturnValue({ show: false }); + jest.mocked(useShowRelatedAlertsBySameSourceEvent).mockReturnValue({ show: false }); + jest.mocked(useShowRelatedAlertsBySession).mockReturnValue({ show: false }); + jest.mocked(useShowRelatedCases).mockReturnValue(false); const { getByTestId, queryByTestId } = render(renderCorrelationsOverview(panelContextValue)); - expect(queryByTestId(INSIGHTS_CORRELATIONS_TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); - expect(getByTestId(INSIGHTS_CORRELATIONS_TITLE_LINK_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(INSIGHTS_CORRELATIONS_TITLE_ICON_TEST_ID)).toBeInTheDocument(); - expect(queryByTestId(INSIGHTS_CORRELATIONS_TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(TITLE_LINK_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(TITLE_ICON_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); }); it('should show component with all rows in expandable panel', () => { - (useCorrelations as jest.Mock).mockReturnValue({ + jest + .mocked(useShowRelatedAlertsByAncestry) + .mockReturnValue({ show: true, documentId: 'documentId', indices: ['index1'] }); + jest + .mocked(useShowRelatedAlertsBySameSourceEvent) + .mockReturnValue({ show: true, originalEventId: 'originalEventId' }); + jest + .mocked(useShowRelatedAlertsBySession) + .mockReturnValue({ show: true, entityId: 'entityId' }); + jest.mocked(useShowRelatedCases).mockReturnValue(true); + + (useFetchRelatedAlertsByAncestry as jest.Mock).mockReturnValue({ loading: false, error: false, - data: [ - { icon: 'warning', value: 1, text: 'related case' }, - { icon: 'warning', value: 2, text: 'alerts related by ancestry' }, - { icon: 'warning', value: 3, text: 'alerts related by the same source event' }, - { icon: 'warning', value: 4, text: 'alerts related by session' }, - ], - dataCount: 4, + dataCount: 1, }); - - const { getByTestId } = render(renderCorrelationsOverview(panelContextValue)); - expect(getByTestId(INSIGHTS_CORRELATIONS_TITLE_LINK_TEST_ID)).toHaveTextContent('Correlations'); - expect(getByTestId(INSIGHTS_CORRELATIONS_CONTENT_TEST_ID)).toHaveTextContent('1 related case'); - expect(getByTestId(INSIGHTS_CORRELATIONS_CONTENT_TEST_ID)).toHaveTextContent( - '2 alerts related by ancestry' - ); - expect(getByTestId(INSIGHTS_CORRELATIONS_CONTENT_TEST_ID)).toHaveTextContent( - '3 alerts related by the same source event' - ); - expect(getByTestId(INSIGHTS_CORRELATIONS_CONTENT_TEST_ID)).toHaveTextContent( - '4 alerts related by session' - ); - }); - - it('should hide row if data is missing', () => { - (useCorrelations as jest.Mock).mockReturnValue({ + (useFetchRelatedAlertsBySameSourceEvent as jest.Mock).mockReturnValue({ loading: false, error: false, - data: [ - { icon: 'warning', value: 1, text: 'alert related by ancestry' }, - { icon: 'warning', value: 1, text: 'alert related by the same source event' }, - { icon: 'warning', value: 1, text: 'alert related by session' }, - ], - dataCount: 4, + dataCount: 1, }); - - const { getByTestId } = render(renderCorrelationsOverview(panelContextValue)); - expect(getByTestId(INSIGHTS_CORRELATIONS_CONTENT_TEST_ID)).not.toHaveTextContent( - 'related case' - ); - }); - - it('should render null if all rows are hidden', () => { - (useCorrelations as jest.Mock).mockReturnValue({ + (useFetchRelatedAlertsBySession as jest.Mock).mockReturnValue({ loading: false, - error: true, - data: [], - dataCount: 0, + error: false, + dataCount: 1, + }); + (useFetchRelatedCases as jest.Mock).mockReturnValue({ + loading: false, + error: false, + dataCount: 1, }); const { getByTestId } = render(renderCorrelationsOverview(panelContextValue)); - expect(getByTestId(INSIGHTS_CORRELATIONS_CONTENT_TEST_ID)).toBeEmptyDOMElement(); + expect(getByTestId(RELATED_ALERTS_BY_ANCESTRY_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RELATED_CASES_TEST_ID)).toBeInTheDocument(); }); - it('should render loading if any rows are loading', () => { - (useCorrelations as jest.Mock).mockReturnValue({ - loading: true, - error: false, - data: [], - dataCount: 0, - }); + it('should hide rows if show values are false', () => { + jest + .mocked(useShowRelatedAlertsByAncestry) + .mockReturnValue({ show: false, documentId: 'documentId', indices: ['index1'] }); + jest + .mocked(useShowRelatedAlertsBySameSourceEvent) + .mockReturnValue({ show: false, originalEventId: 'originalEventId' }); + jest + .mocked(useShowRelatedAlertsBySession) + .mockReturnValue({ show: false, entityId: 'entityId' }); + jest.mocked(useShowRelatedCases).mockReturnValue(false); + + const { queryByTestId } = render(renderCorrelationsOverview(panelContextValue)); + expect(queryByTestId(RELATED_ALERTS_BY_ANCESTRY_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(RELATED_CASES_TEST_ID)).not.toBeInTheDocument(); + }); - const { getByTestId } = render(renderCorrelationsOverview(panelContextValue)); - expect(getByTestId(INSIGHTS_CORRELATIONS_LOADING_TEST_ID)).toBeInTheDocument(); + it('should hide rows if values are null', () => { + jest.mocked(useShowRelatedAlertsByAncestry).mockReturnValue({ show: true }); + jest.mocked(useShowRelatedAlertsBySameSourceEvent).mockReturnValue({ show: true }); + jest.mocked(useShowRelatedAlertsBySession).mockReturnValue({ show: true }); + jest.mocked(useShowRelatedCases).mockReturnValue(false); + + const { queryByTestId } = render(renderCorrelationsOverview(panelContextValue)); + expect(queryByTestId(RELATED_ALERTS_BY_ANCESTRY_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(RELATED_CASES_TEST_ID)).not.toBeInTheDocument(); }); it('should navigate to the left section Insights tab when clicking on button', () => { - (useCorrelations as jest.Mock).mockReturnValue({ - loading: false, - error: false, - data: [ - { icon: 'warning', value: 1, text: 'related case' }, - { icon: 'warning', value: 6, text: 'alerts related by ancestry' }, - { icon: 'warning', value: 1, text: 'alert related by the same source event' }, - { icon: 'warning', value: 6, text: 'alerts related by session' }, - ], - dataCount: 4, - }); - const flyoutContextValue = { openLeftPanel: jest.fn(), } as unknown as ExpandableFlyoutContext; @@ -152,7 +186,7 @@ describe('', () => { ); - getByTestId(INSIGHTS_CORRELATIONS_TITLE_LINK_TEST_ID).click(); + getByTestId(TITLE_LINK_TEST_ID).click(); expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ id: LeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: CORRELATIONS_TAB_ID }, diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx index 73d2407c9d1152..03a8e7f926dfab 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx @@ -5,12 +5,18 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback } from 'react'; import { EuiFlexGroup } from '@elastic/eui'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; import { ExpandablePanel } from '../../shared/components/expandable_panel'; -import { InsightsSummaryRow } from './insights_summary_row'; -import { useCorrelations } from '../../shared/hooks/use_correlations'; +import { useShowRelatedAlertsBySession } from '../../shared/hooks/use_show_related_alerts_by_session'; +import { RelatedAlertsBySession } from './related_alerts_by_session'; +import { useShowRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_show_related_alerts_by_same_source_event'; +import { RelatedAlertsBySameSourceEvent } from './related_alerts_by_same_source_event'; +import { RelatedAlertsByAncestry } from './related_alerts_by_ancestry'; +import { useShowRelatedAlertsByAncestry } from '../../shared/hooks/use_show_related_alerts_by_ancestry'; +import { RelatedCases } from './related_cases'; +import { useShowRelatedCases } from '../../shared/hooks/use_show_related_cases'; import { INSIGHTS_CORRELATIONS_TEST_ID } from './test_ids'; import { useRightPanelContext } from '../context'; import { CORRELATIONS_TITLE } from './translations'; @@ -23,8 +29,14 @@ import { CORRELATIONS_TAB_ID } from '../../left/components/correlations_details' * and the SummaryPanel component for data rendering. */ export const CorrelationsOverview: React.FC = () => { - const { eventId, indexName, dataAsNestedObject, dataFormattedForFieldBrowser, scopeId } = - useRightPanelContext(); + const { + dataAsNestedObject, + dataFormattedForFieldBrowser, + eventId, + indexName, + getFieldsData, + scopeId, + } = useRightPanelContext(); const { openLeftPanel } = useExpandableFlyoutContext(); const goToCorrelationsTab = useCallback(() => { @@ -42,26 +54,20 @@ export const CorrelationsOverview: React.FC = () => { }); }, [eventId, openLeftPanel, indexName, scopeId]); - const { loading, error, data } = useCorrelations({ - eventId, + const { + show: showAlertsByAncestry, + documentId, + indices, + } = useShowRelatedAlertsByAncestry({ + getFieldsData, dataAsNestedObject, dataFormattedForFieldBrowser, - scopeId, }); - - const correlationRows = useMemo( - () => - data.map((d) => ( - - )), - [data] - ); + const { show: showSameSourceAlerts, originalEventId } = useShowRelatedAlertsBySameSourceEvent({ + getFieldsData, + }); + const { show: showAlertsBySession, entityId } = useShowRelatedAlertsBySession({ getFieldsData }); + const showCases = useShowRelatedCases(); return ( { callback: goToCorrelationsTab, iconType: 'arrowStart', }} - content={{ loading, error }} data-test-subj={INSIGHTS_CORRELATIONS_TEST_ID} > - {correlationRows} + {showAlertsByAncestry && documentId && indices && ( + + )} + {showSameSourceAlerts && originalEventId && ( + + )} + {showAlertsBySession && entityId && ( + + )} + {showCases && } ); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.test.tsx index 29bb8068281aeb..d26a93262fa311 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.test.tsx @@ -11,14 +11,22 @@ import { RightPanelContext } from '../context'; import { ENTITIES_HOST_OVERVIEW_TEST_ID, ENTITIES_USER_OVERVIEW_TEST_ID, - INSIGHTS_ENTITIES_TOGGLE_ICON_TEST_ID, - INSIGHTS_ENTITIES_TITLE_LINK_TEST_ID, - INSIGHTS_ENTITIES_TITLE_ICON_TEST_ID, - INSIGHTS_ENTITIES_TITLE_TEXT_TEST_ID, + INSIGHTS_ENTITIES_TEST_ID, } from './test_ids'; import { EntitiesOverview } from './entities_overview'; import { TestProviders } from '../../../common/mock'; import { mockGetFieldsData } from '../mocks/mock_context'; +import { + EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID, + EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID, +} from '../../shared/components/test_ids'; + +const TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(INSIGHTS_ENTITIES_TEST_ID); +const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_ENTITIES_TEST_ID); +const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(INSIGHTS_ENTITIES_TEST_ID); +const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(INSIGHTS_ENTITIES_TEST_ID); describe('', () => { it('should render wrapper component', () => { @@ -35,11 +43,11 @@ describe('', () => { ); - expect(queryByTestId(INSIGHTS_ENTITIES_TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); - expect(getByTestId(INSIGHTS_ENTITIES_TITLE_LINK_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(INSIGHTS_ENTITIES_TITLE_LINK_TEST_ID)).toHaveTextContent('Entities'); - expect(getByTestId(INSIGHTS_ENTITIES_TITLE_ICON_TEST_ID)).toBeInTheDocument(); - expect(queryByTestId(INSIGHTS_ENTITIES_TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(TITLE_LINK_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(TITLE_LINK_TEST_ID)).toHaveTextContent('Entities'); + expect(getByTestId(TITLE_ICON_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); }); it('should render user and host', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.test.tsx index 828a8b247543b0..abf08209ec5498 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.test.tsx @@ -9,12 +9,7 @@ import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { render } from '@testing-library/react'; import { TestProviders } from '../../../common/mock'; import { RightPanelContext } from '../context'; -import { - INSIGHTS_PREVALENCE_TITLE_ICON_TEST_ID, - INSIGHTS_PREVALENCE_TITLE_LINK_TEST_ID, - INSIGHTS_PREVALENCE_TITLE_TEXT_TEST_ID, - INSIGHTS_PREVALENCE_TOGGLE_ICON_TEST_ID, -} from './test_ids'; +import { INSIGHTS_PREVALENCE_TEST_ID } from './test_ids'; import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; import React from 'react'; import { PrevalenceOverview } from './prevalence_overview'; @@ -24,11 +19,22 @@ import { useFetchFieldValuePairWithAggregation } from '../../shared/hooks/use_fe import { useFetchUniqueByField } from '../../shared/hooks/use_fetch_unique_by_field'; import { PREVALENCE_TAB_ID } from '../../left/components/prevalence_details'; import type { BrowserFields, TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import { + EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID, + EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID, +} from '../../shared/components/test_ids'; jest.mock('../../shared/hooks/use_fetch_field_value_pair_with_aggregation'); jest.mock('../../shared/hooks/use_fetch_unique_by_field'); jest.mock('../hooks/use_prevalence'); +const TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID); +const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID); +const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID); +const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID); + const highlightedField = { name: 'field', values: ['values'], @@ -71,10 +77,10 @@ describe('', () => { const { getByTestId, queryByTestId } = render( renderPrevalenceOverview(panelContextValue('eventId', {}, [])) ); - expect(queryByTestId(INSIGHTS_PREVALENCE_TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); - expect(getByTestId(INSIGHTS_PREVALENCE_TITLE_LINK_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(INSIGHTS_PREVALENCE_TITLE_ICON_TEST_ID)).toBeInTheDocument(); - expect(queryByTestId(INSIGHTS_PREVALENCE_TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(TITLE_LINK_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(TITLE_ICON_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); }); it('should render component', () => { @@ -94,7 +100,7 @@ describe('', () => { const { getByTestId } = render(renderPrevalenceOverview(panelContextValue('eventId', {}, []))); - expect(getByTestId(INSIGHTS_PREVALENCE_TITLE_LINK_TEST_ID)).toHaveTextContent('Prevalence'); + expect(getByTestId(TITLE_LINK_TEST_ID)).toHaveTextContent('Prevalence'); const iconDataTestSubj = 'testIcon'; const valueDataTestSubj = 'testValue'; @@ -154,7 +160,7 @@ describe('', () => { ); - getByTestId(INSIGHTS_PREVALENCE_TITLE_LINK_TEST_ID).click(); + getByTestId(TITLE_LINK_TEST_ID).click(); expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ id: LeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: PREVALENCE_TAB_ID }, diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_ancestry.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_ancestry.test.tsx new file mode 100644 index 00000000000000..9f6a4ec83a2c2e --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_ancestry.test.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { + SUMMARY_ROW_ICON_TEST_ID, + SUMMARY_ROW_VALUE_TEST_ID, + SUMMARY_ROW_LOADING_TEST_ID, + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID, +} from './test_ids'; +import { RelatedAlertsByAncestry } from './related_alerts_by_ancestry'; +import { useFetchRelatedAlertsByAncestry } from '../../shared/hooks/use_fetch_related_alerts_by_ancestry'; + +jest.mock('../../shared/hooks/use_fetch_related_alerts_by_ancestry'); + +const documentId = 'documentId'; +const indices = ['indices']; +const scopeId = 'scopeId'; + +const ICON_TEST_ID = SUMMARY_ROW_ICON_TEST_ID( + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID +); +const VALUE_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID +); +const LOADING_TEST_ID = SUMMARY_ROW_LOADING_TEST_ID( + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID +); + +describe('', () => { + it('should render many related alerts correctly', () => { + (useFetchRelatedAlertsByAncestry as jest.Mock).mockReturnValue({ + loading: false, + error: false, + dataCount: 2, + }); + + const { getByTestId } = render( + + ); + expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); + const value = getByTestId(VALUE_TEST_ID); + expect(value).toBeInTheDocument(); + expect(value).toHaveTextContent('2 alerts related by ancestry'); + expect(getByTestId(VALUE_TEST_ID)).toBeInTheDocument(); + }); + + it('should render single related alerts correctly', () => { + (useFetchRelatedAlertsByAncestry as jest.Mock).mockReturnValue({ + loading: false, + error: false, + dataCount: 1, + }); + + const { getByTestId } = render( + + ); + expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); + const value = getByTestId(VALUE_TEST_ID); + expect(value).toBeInTheDocument(); + expect(value).toHaveTextContent('1 alert related by ancestry'); + expect(getByTestId(VALUE_TEST_ID)).toBeInTheDocument(); + }); + + it('should render loading skeleton', () => { + (useFetchRelatedAlertsByAncestry as jest.Mock).mockReturnValue({ + loading: true, + }); + + const { getByTestId } = render( + + ); + expect(getByTestId(LOADING_TEST_ID)).toBeInTheDocument(); + }); + + it('should render null if error', () => { + (useFetchRelatedAlertsByAncestry as jest.Mock).mockReturnValue({ + loading: false, + error: true, + }); + + const { container } = render( + + ); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_ancestry.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_ancestry.tsx new file mode 100644 index 00000000000000..0a48443de73204 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_ancestry.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useFetchRelatedAlertsByAncestry } from '../../shared/hooks/use_fetch_related_alerts_by_ancestry'; +import { CORRELATIONS_ANCESTRY_ALERTS } from '../../shared/translations'; +import { InsightsSummaryRow } from './insights_summary_row'; +import { INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID } from './test_ids'; + +const ICON = 'warning'; + +export interface RelatedAlertsByAncestryProps { + /** + * Value of the kibana.alert.ancestors.id field + */ + documentId: string; + /** + * Values of the kibana.alert.rule.parameters.index field + */ + indices: string[]; + /** + * Maintain backwards compatibility // TODO remove when possible + */ + scopeId: string; +} + +/** + * Show related alerts by ancestry in summary row + */ +export const RelatedAlertsByAncestry: React.VFC = ({ + documentId, + indices, + scopeId, +}) => { + const { loading, error, dataCount } = useFetchRelatedAlertsByAncestry({ + documentId, + indices, + scopeId, + }); + const text = CORRELATIONS_ANCESTRY_ALERTS(dataCount); + + return ( + + ); +}; + +RelatedAlertsByAncestry.displayName = 'RelatedAlertsByAncestry'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_same_source.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_same_source.test.tsx new file mode 100644 index 00000000000000..afcbaeb8c5c88b --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_same_source.test.tsx @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { + SUMMARY_ROW_ICON_TEST_ID, + SUMMARY_ROW_VALUE_TEST_ID, + SUMMARY_ROW_LOADING_TEST_ID, + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID, +} from './test_ids'; +import { useFetchRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_fetch_related_alerts_by_same_source_event'; +import { RelatedAlertsBySameSourceEvent } from './related_alerts_by_same_source_event'; + +jest.mock('../../shared/hooks/use_fetch_related_alerts_by_same_source_event'); + +const originalEventId = 'originalEventId'; +const scopeId = 'scopeId'; + +const ICON_TEST_ID = SUMMARY_ROW_ICON_TEST_ID( + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID +); +const VALUE_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID +); +const LOADING_TEST_ID = SUMMARY_ROW_LOADING_TEST_ID( + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID +); + +describe('', () => { + it('should render many related alerts correctly', () => { + (useFetchRelatedAlertsBySameSourceEvent as jest.Mock).mockReturnValue({ + loading: false, + error: false, + dataCount: 2, + }); + + const { getByTestId } = render( + + ); + expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); + const value = getByTestId(VALUE_TEST_ID); + expect(value).toBeInTheDocument(); + expect(value).toHaveTextContent('2 alerts related by source event'); + expect(getByTestId(VALUE_TEST_ID)).toBeInTheDocument(); + }); + + it('should render single related alerts correctly', () => { + (useFetchRelatedAlertsBySameSourceEvent as jest.Mock).mockReturnValue({ + loading: false, + error: false, + dataCount: 1, + }); + + const { getByTestId } = render( + + ); + expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); + const value = getByTestId(VALUE_TEST_ID); + expect(value).toBeInTheDocument(); + expect(value).toHaveTextContent('1 alert related by source event'); + expect(getByTestId(VALUE_TEST_ID)).toBeInTheDocument(); + }); + + it('should render loading skeleton', () => { + (useFetchRelatedAlertsBySameSourceEvent as jest.Mock).mockReturnValue({ + loading: true, + }); + + const { getByTestId } = render( + + ); + expect(getByTestId(LOADING_TEST_ID)).toBeInTheDocument(); + }); + + it('should render null if error', () => { + (useFetchRelatedAlertsBySameSourceEvent as jest.Mock).mockReturnValue({ + loading: false, + error: true, + }); + + const { container } = render( + + ); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_same_source_event.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_same_source_event.tsx new file mode 100644 index 00000000000000..1d5df7a326c44c --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_same_source_event.tsx @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useFetchRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_fetch_related_alerts_by_same_source_event'; +import { CORRELATIONS_SAME_SOURCE_ALERTS } from '../../shared/translations'; +import { InsightsSummaryRow } from './insights_summary_row'; +import { INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID } from './test_ids'; + +const ICON = 'warning'; + +export interface RelatedAlertsBySameSourceEventProps { + /** + * Value of the kibana.alert.original_event.id field + */ + originalEventId: string; + /** + * Maintain backwards compatibility // TODO remove when possible + */ + scopeId: string; +} + +/** + * Show related alerts by same source event in summary row + */ +export const RelatedAlertsBySameSourceEvent: React.VFC = ({ + originalEventId, + scopeId, +}) => { + const { loading, error, dataCount } = useFetchRelatedAlertsBySameSourceEvent({ + originalEventId, + scopeId, + }); + const text = CORRELATIONS_SAME_SOURCE_ALERTS(dataCount); + + return ( + + ); +}; + +RelatedAlertsBySameSourceEvent.displayName = 'RelatedAlertsBySameSourceEvent'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_session.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_session.test.tsx new file mode 100644 index 00000000000000..133f00e44b26bd --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_session.test.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { + SUMMARY_ROW_ICON_TEST_ID, + SUMMARY_ROW_VALUE_TEST_ID, + SUMMARY_ROW_LOADING_TEST_ID, + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID, +} from './test_ids'; +import { RelatedAlertsBySession } from './related_alerts_by_session'; +import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session'; + +jest.mock('../../shared/hooks/use_fetch_related_alerts_by_session'); + +const entityId = 'entityId'; +const scopeId = 'scopeId'; + +const ICON_TEST_ID = SUMMARY_ROW_ICON_TEST_ID( + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID +); +const VALUE_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID +); +const LOADING_TEST_ID = SUMMARY_ROW_LOADING_TEST_ID( + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID +); + +describe('', () => { + it('should render many related alerts correctly', () => { + (useFetchRelatedAlertsBySession as jest.Mock).mockReturnValue({ + loading: false, + error: false, + dataCount: 2, + }); + + const { getByTestId } = render( + + ); + expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); + const value = getByTestId(VALUE_TEST_ID); + expect(value).toBeInTheDocument(); + expect(value).toHaveTextContent('2 alerts related by session'); + expect(getByTestId(VALUE_TEST_ID)).toBeInTheDocument(); + }); + + it('should render single related alerts correctly', () => { + (useFetchRelatedAlertsBySession as jest.Mock).mockReturnValue({ + loading: false, + error: false, + dataCount: 1, + }); + + const { getByTestId } = render( + + ); + expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); + const value = getByTestId(VALUE_TEST_ID); + expect(value).toBeInTheDocument(); + expect(value).toHaveTextContent('1 alert related by session'); + expect(getByTestId(VALUE_TEST_ID)).toBeInTheDocument(); + }); + + it('should render loading skeleton', () => { + (useFetchRelatedAlertsBySession as jest.Mock).mockReturnValue({ + loading: true, + }); + + const { getByTestId } = render( + + ); + expect(getByTestId(LOADING_TEST_ID)).toBeInTheDocument(); + }); + + it('should render null if error', () => { + (useFetchRelatedAlertsBySession as jest.Mock).mockReturnValue({ + loading: false, + error: true, + }); + + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_session.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_session.tsx new file mode 100644 index 00000000000000..2a015c0c880d95 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_alerts_by_session.tsx @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session'; +import { CORRELATIONS_SESSION_ALERTS } from '../../shared/translations'; +import { InsightsSummaryRow } from './insights_summary_row'; +import { INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID } from './test_ids'; + +const ICON = 'warning'; + +export interface RelatedAlertsBySessionProps { + /** + * Value of the process.entry_leader.entity_id field + */ + entityId: string; + /** + * Maintain backwards compatibility // TODO remove when possible + */ + scopeId: string; +} + +/** + * Show related alerts by session in summary row + */ +export const RelatedAlertsBySession: React.VFC = ({ + entityId, + scopeId, +}) => { + const { loading, error, dataCount } = useFetchRelatedAlertsBySession({ + entityId, + scopeId, + }); + const text = CORRELATIONS_SESSION_ALERTS(dataCount); + + return ( + + ); +}; + +RelatedAlertsBySession.displayName = 'RelatedAlertsBySession'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_cases.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_cases.test.tsx new file mode 100644 index 00000000000000..7d0d1a88fb2ecd --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_cases.test.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { + INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID, + SUMMARY_ROW_ICON_TEST_ID, + SUMMARY_ROW_LOADING_TEST_ID, + SUMMARY_ROW_VALUE_TEST_ID, +} from './test_ids'; +import { RelatedCases } from './related_cases'; +import { useFetchRelatedCases } from '../../shared/hooks/use_fetch_related_cases'; + +jest.mock('../../shared/hooks/use_fetch_related_cases'); + +const eventId = 'eventId'; + +const ICON_TEST_ID = SUMMARY_ROW_ICON_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID); +const VALUE_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID); +const LOADING_TEST_ID = SUMMARY_ROW_LOADING_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID); + +describe('', () => { + it('should render many related cases correctly', () => { + (useFetchRelatedCases as jest.Mock).mockReturnValue({ + loading: false, + error: false, + dataCount: 2, + }); + + const { getByTestId } = render(); + expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); + const value = getByTestId(VALUE_TEST_ID); + expect(value).toBeInTheDocument(); + expect(value).toHaveTextContent('2 related cases'); + expect(getByTestId(VALUE_TEST_ID)).toBeInTheDocument(); + }); + + it('should render single related case correctly', () => { + (useFetchRelatedCases as jest.Mock).mockReturnValue({ + loading: false, + error: false, + dataCount: 1, + }); + + const { getByTestId } = render(); + expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument(); + const value = getByTestId(VALUE_TEST_ID); + expect(value).toBeInTheDocument(); + expect(value).toHaveTextContent('1 related case'); + expect(getByTestId(VALUE_TEST_ID)).toBeInTheDocument(); + }); + + it('should render loading skeleton', () => { + (useFetchRelatedCases as jest.Mock).mockReturnValue({ + loading: true, + }); + + const { getByTestId } = render(); + expect(getByTestId(LOADING_TEST_ID)).toBeInTheDocument(); + }); + + it('should render null if error', () => { + (useFetchRelatedCases as jest.Mock).mockReturnValue({ + loading: false, + error: true, + }); + + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/related_cases.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/related_cases.tsx new file mode 100644 index 00000000000000..d20e4fc09d56b7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/related_cases.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { CORRELATIONS_RELATED_CASES } from '../../shared/translations'; +import { useFetchRelatedCases } from '../../shared/hooks/use_fetch_related_cases'; +import { InsightsSummaryRow } from './insights_summary_row'; +import { INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID } from './test_ids'; + +const ICON = 'warning'; + +export interface RelatedCasesProps { + /** + * Id of the document + */ + eventId: string; +} + +/** + * + */ +export const RelatedCases: React.VFC = ({ eventId }) => { + const { loading, error, dataCount } = useFetchRelatedCases({ eventId }); + const text = CORRELATIONS_RELATED_CASES(dataCount); + + return ( + + ); +}; + +RelatedCases.displayName = 'RelatedCases'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/session_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/session_preview.test.tsx index 64247d5b0dd17d..82179c0c166255 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/session_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/session_preview.test.tsx @@ -12,18 +12,25 @@ import { TestProviders } from '../../../common/mock'; import React from 'react'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { RightPanelContext } from '../context'; -import { - SESSION_PREVIEW_CONTENT_TEST_ID, - SESSION_PREVIEW_TITLE_ICON_TEST_ID, - SESSION_PREVIEW_TITLE_LINK_TEST_ID, - SESSION_PREVIEW_TITLE_TEXT_TEST_ID, - SESSION_PREVIEW_TOGGLE_ICON_TEST_ID, -} from './test_ids'; +import { SESSION_PREVIEW_TEST_ID } from './test_ids'; import { LeftPanelKey, LeftPanelVisualizeTab } from '../../left'; import { SESSION_VIEW_ID } from '../../left/components/session_view'; +import { + EXPANDABLE_PANEL_CONTENT_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID, + EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID, +} from '../../shared/components/test_ids'; jest.mock('../hooks/use_process_data'); +const TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(SESSION_PREVIEW_TEST_ID); +const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(SESSION_PREVIEW_TEST_ID); +const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(SESSION_PREVIEW_TEST_ID); +const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(SESSION_PREVIEW_TEST_ID); +const CONTENT_TEST_ID = EXPANDABLE_PANEL_CONTENT_TEST_ID(SESSION_PREVIEW_TEST_ID); + const flyoutContextValue = { openLeftPanel: jest.fn(), } as unknown as ExpandableFlyoutContext; @@ -64,11 +71,11 @@ describe('SessionPreview', () => { renderSessionPreview(); - expect(screen.queryByTestId(SESSION_PREVIEW_TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); - expect(screen.getByTestId(SESSION_PREVIEW_TITLE_LINK_TEST_ID)).toBeInTheDocument(); - expect(screen.getByTestId(SESSION_PREVIEW_TITLE_ICON_TEST_ID)).toBeInTheDocument(); - expect(screen.getByTestId(SESSION_PREVIEW_CONTENT_TEST_ID)).toBeInTheDocument(); - expect(screen.queryByTestId(SESSION_PREVIEW_TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); + expect(screen.getByTestId(TITLE_LINK_TEST_ID)).toBeInTheDocument(); + expect(screen.getByTestId(TITLE_ICON_TEST_ID)).toBeInTheDocument(); + expect(screen.getByTestId(CONTENT_TEST_ID)).toBeInTheDocument(); + expect(screen.queryByTestId(TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); }); it('renders session preview with all data', () => { @@ -129,7 +136,7 @@ describe('SessionPreview', () => { const { getByTestId } = renderSessionPreview(); - getByTestId(SESSION_PREVIEW_TITLE_LINK_TEST_ID).click(); + getByTestId(TITLE_LINK_TEST_ID).click(); expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ id: LeftPanelKey, path: { tab: LeftPanelVisualizeTab, subTab: SESSION_VIEW_ID }, diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts index 7d41fe13f9fcae..6c2b90e278467e 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts @@ -5,14 +5,6 @@ * 2.0. */ -import { - EXPANDABLE_PANEL_CONTENT_TEST_ID, - EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, - EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID, - EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID, - EXPANDABLE_PANEL_LOADING_TEST_ID, - EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID, -} from '../../shared/components/test_ids'; import { RESPONSE_BASE_TEST_ID } from '../../left/components/test_ids'; import { CONTENT_TEST_ID, HEADER_TEST_ID } from './expandable_section'; @@ -79,19 +71,17 @@ export const INVESTIGATION_GUIDE_BUTTON_TEST_ID = export const INSIGHTS_TEST_ID = 'securitySolutionDocumentDetailsFlyoutInsights'; export const INSIGHTS_HEADER_TEST_ID = `${INSIGHTS_TEST_ID}Header`; +/* Summary row */ + +export const SUMMARY_ROW_LOADING_TEST_ID = (dataTestSubj: string) => `${dataTestSubj}Loading`; +export const SUMMARY_ROW_ICON_TEST_ID = (dataTestSubj: string) => `${dataTestSubj}Icon`; +export const SUMMARY_ROW_VALUE_TEST_ID = (dataTestSubj: string) => `${dataTestSubj}Value`; + /* Insights Entities */ export const INSIGHTS_ENTITIES_TEST_ID = 'securitySolutionDocumentDetailsFlyoutInsightsEntities'; -export const INSIGHTS_ENTITIES_TOGGLE_ICON_TEST_ID = - EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(INSIGHTS_ENTITIES_TEST_ID); -export const INSIGHTS_ENTITIES_TITLE_LINK_TEST_ID = - EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_ENTITIES_TEST_ID); -export const INSIGHTS_ENTITIES_TITLE_TEXT_TEST_ID = - EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(INSIGHTS_ENTITIES_TEST_ID); -export const INSIGHTS_ENTITIES_TITLE_ICON_TEST_ID = - EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(INSIGHTS_ENTITIES_TEST_ID); -export const INSIGHTS_ENTITIES_CONTENT_TEST_ID = - EXPANDABLE_PANEL_CONTENT_TEST_ID(INSIGHTS_ENTITIES_TEST_ID); +export const TECHNICAL_PREVIEW_ICON_TEST_ID = + 'securitySolutionDocumentDetailsFlyoutTechnicalPreviewIcon'; export const ENTITIES_USER_OVERVIEW_TEST_ID = 'securitySolutionDocumentDetailsFlyoutEntitiesUserOverview'; export const ENTITIES_USER_OVERVIEW_LINK_TEST_ID = `${ENTITIES_USER_OVERVIEW_TEST_ID}Link`; @@ -109,70 +99,25 @@ export const ENTITIES_HOST_OVERVIEW_RISK_LEVEL_TEST_ID = `${ENTITIES_HOST_OVERVI export const INSIGHTS_THREAT_INTELLIGENCE_TEST_ID = 'securitySolutionDocumentDetailsFlyoutInsightsThreatIntelligence'; -export const INSIGHTS_THREAT_INTELLIGENCE_TOGGLE_ICON_TEST_ID = - EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(INSIGHTS_THREAT_INTELLIGENCE_TEST_ID); -export const INSIGHTS_THREAT_INTELLIGENCE_TITLE_LINK_TEST_ID = - EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_THREAT_INTELLIGENCE_TEST_ID); -export const INSIGHTS_THREAT_INTELLIGENCE_TITLE_TEXT_TEST_ID = - EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(INSIGHTS_THREAT_INTELLIGENCE_TEST_ID); -export const INSIGHTS_THREAT_INTELLIGENCE_TITLE_ICON_TEST_ID = - EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(INSIGHTS_THREAT_INTELLIGENCE_TEST_ID); -export const INSIGHTS_THREAT_INTELLIGENCE_LOADING_TEST_ID = EXPANDABLE_PANEL_LOADING_TEST_ID( - INSIGHTS_THREAT_INTELLIGENCE_TEST_ID -); -export const INSIGHTS_THREAT_INTELLIGENCE_CONTENT_TEST_ID = EXPANDABLE_PANEL_CONTENT_TEST_ID( - INSIGHTS_THREAT_INTELLIGENCE_TEST_ID -); export const INSIGHTS_THREAT_INTELLIGENCE_CONTAINER_TEST_ID = `${INSIGHTS_THREAT_INTELLIGENCE_TEST_ID}Container`; -export const INSIGHTS_THREAT_INTELLIGENCE_VALUE_TEST_ID = `${INSIGHTS_THREAT_INTELLIGENCE_TEST_ID}Value`; /* Insights Correlations */ export const INSIGHTS_CORRELATIONS_TEST_ID = 'securitySolutionDocumentDetailsFlyoutInsightsCorrelations'; -export const INSIGHTS_CORRELATIONS_TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID( - INSIGHTS_CORRELATIONS_TEST_ID -); -export const INSIGHTS_CORRELATIONS_TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID( - INSIGHTS_CORRELATIONS_TEST_ID -); -export const INSIGHTS_CORRELATIONS_TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( - INSIGHTS_CORRELATIONS_TEST_ID -); -export const INSIGHTS_CORRELATIONS_TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID( - INSIGHTS_CORRELATIONS_TEST_ID -); -export const INSIGHTS_CORRELATIONS_LOADING_TEST_ID = EXPANDABLE_PANEL_LOADING_TEST_ID( - INSIGHTS_CORRELATIONS_TEST_ID -); -export const INSIGHTS_CORRELATIONS_CONTENT_TEST_ID = EXPANDABLE_PANEL_CONTENT_TEST_ID( - INSIGHTS_CORRELATIONS_TEST_ID -); -export const INSIGHTS_CORRELATIONS_VALUE_TEST_ID = `${INSIGHTS_CORRELATIONS_TEST_ID}Value`; +export const INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID = + 'securitySolutionDocumentDetailsFlyoutInsightsCorrelationsRelatedCases'; +export const INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID = + 'securitySolutionDocumentDetailsFlyoutInsightsCorrelationsRelatedAlertsBySession'; +export const INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID = + 'securitySolutionDocumentDetailsFlyoutInsightsCorrelationsRelatedAlertsBySameSourceEvent'; +export const INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID = + 'securitySolutionDocumentDetailsFlyoutInsightsCorrelationsRelatedAlertsByAncestry'; /* Insights Prevalence */ export const INSIGHTS_PREVALENCE_TEST_ID = 'securitySolutionDocumentDetailsFlyoutInsightsPrevalence'; -export const INSIGHTS_PREVALENCE_TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID( - INSIGHTS_PREVALENCE_TEST_ID -); -export const INSIGHTS_PREVALENCE_TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID( - INSIGHTS_PREVALENCE_TEST_ID -); -export const INSIGHTS_PREVALENCE_TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( - INSIGHTS_PREVALENCE_TEST_ID -); -export const INSIGHTS_PREVALENCE_TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID( - INSIGHTS_PREVALENCE_TEST_ID -); -export const INSIGHTS_PREVALENCE_LOADING_TEST_ID = EXPANDABLE_PANEL_LOADING_TEST_ID( - INSIGHTS_PREVALENCE_TEST_ID -); -export const INSIGHTS_PREVALENCE_CONTENT_TEST_ID = EXPANDABLE_PANEL_CONTENT_TEST_ID( - INSIGHTS_PREVALENCE_TEST_ID -); -export const INSIGHTS_PREVALENCE_VALUE_TEST_ID = `${INSIGHTS_PREVALENCE_TEST_ID}Value`; export const INSIGHTS_PREVALENCE_ROW_TEST_ID = 'securitySolutionDocumentDetailsFlyoutInsightsPrevalenceRow'; @@ -185,34 +130,10 @@ export const VISUALIZATIONS_SECTION_HEADER_TEST_ID = /* Visualizations analyzer preview */ export const ANALYZER_PREVIEW_TEST_ID = 'securitySolutionDocumentDetailsAnalayzerPreview'; -export const ANALYZER_PREVIEW_TOGGLE_ICON_TEST_ID = - EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(ANALYZER_PREVIEW_TEST_ID); -export const ANALYZER_PREVIEW_TITLE_LINK_TEST_ID = - EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(ANALYZER_PREVIEW_TEST_ID); -export const ANALYZER_PREVIEW_TITLE_TEXT_TEST_ID = - EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(ANALYZER_PREVIEW_TEST_ID); -export const ANALYZER_PREVIEW_TITLE_ICON_TEST_ID = - EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(ANALYZER_PREVIEW_TEST_ID); -export const ANALYZER_PREVIEW_LOADING_TEST_ID = - EXPANDABLE_PANEL_LOADING_TEST_ID(ANALYZER_PREVIEW_TEST_ID); -export const ANALYZER_PREVIEW_CONTENT_TEST_ID = - EXPANDABLE_PANEL_CONTENT_TEST_ID(ANALYZER_PREVIEW_TEST_ID); /* Visualizations session preview */ export const SESSION_PREVIEW_TEST_ID = 'securitySolutionDocumentDetailsSessionPreview'; -export const SESSION_PREVIEW_TOGGLE_ICON_TEST_ID = - EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(SESSION_PREVIEW_TEST_ID); -export const SESSION_PREVIEW_TITLE_LINK_TEST_ID = - EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(SESSION_PREVIEW_TEST_ID); -export const SESSION_PREVIEW_TITLE_TEXT_TEST_ID = - EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(SESSION_PREVIEW_TEST_ID); -export const SESSION_PREVIEW_TITLE_ICON_TEST_ID = - EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(SESSION_PREVIEW_TEST_ID); -export const SESSION_PREVIEW_LOADING_TEST_ID = - EXPANDABLE_PANEL_LOADING_TEST_ID(SESSION_PREVIEW_TEST_ID); -export const SESSION_PREVIEW_CONTENT_TEST_ID = - EXPANDABLE_PANEL_CONTENT_TEST_ID(SESSION_PREVIEW_TEST_ID); /* Response section */ diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.test.tsx index 2dca57df2b3ebf..53fda9b40c3c46 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.test.tsx @@ -16,16 +16,34 @@ import { useFetchThreatIntelligence } from '../hooks/use_fetch_threat_intelligen import { THREAT_INTELLIGENCE_TAB_ID } from '../../left/components/threat_intelligence_details'; import { INSIGHTS_THREAT_INTELLIGENCE_CONTAINER_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_CONTENT_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_LOADING_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_TITLE_ICON_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_TITLE_LINK_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_TITLE_TEXT_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_TOGGLE_ICON_TEST_ID, + INSIGHTS_THREAT_INTELLIGENCE_TEST_ID, } from './test_ids'; +import { + EXPANDABLE_PANEL_CONTENT_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID, + EXPANDABLE_PANEL_LOADING_TEST_ID, + EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID, +} from '../../shared/components/test_ids'; jest.mock('../hooks/use_fetch_threat_intelligence'); +const TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID( + INSIGHTS_THREAT_INTELLIGENCE_TEST_ID +); +const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID( + INSIGHTS_THREAT_INTELLIGENCE_TEST_ID +); +const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID( + INSIGHTS_THREAT_INTELLIGENCE_TEST_ID +); +const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( + INSIGHTS_THREAT_INTELLIGENCE_TEST_ID +); +const CONTENT_TEST_ID = EXPANDABLE_PANEL_CONTENT_TEST_ID(INSIGHTS_THREAT_INTELLIGENCE_TEST_ID); +const LOADING_TEST_ID = EXPANDABLE_PANEL_LOADING_TEST_ID(INSIGHTS_THREAT_INTELLIGENCE_TEST_ID); + const panelContextValue = { eventId: 'event id', indexName: 'indexName', @@ -50,10 +68,10 @@ describe('', () => { renderThreatIntelligenceOverview(panelContextValue) ); - expect(queryByTestId(INSIGHTS_THREAT_INTELLIGENCE_TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); - expect(getByTestId(INSIGHTS_THREAT_INTELLIGENCE_TITLE_ICON_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(INSIGHTS_THREAT_INTELLIGENCE_TITLE_LINK_TEST_ID)).toBeInTheDocument(); - expect(queryByTestId(INSIGHTS_THREAT_INTELLIGENCE_TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(TITLE_ICON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(TITLE_LINK_TEST_ID)).toBeInTheDocument(); + expect(queryByTestId(TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); }); it('should render 1 match detected and 1 field enriched', () => { @@ -65,13 +83,9 @@ describe('', () => { const { getByTestId } = render(renderThreatIntelligenceOverview(panelContextValue)); - expect(getByTestId(INSIGHTS_THREAT_INTELLIGENCE_TITLE_LINK_TEST_ID)).toHaveTextContent( - 'Threat Intelligence' - ); - expect(getByTestId(INSIGHTS_THREAT_INTELLIGENCE_CONTENT_TEST_ID)).toHaveTextContent( - '1 threat match detected' - ); - expect(getByTestId(INSIGHTS_THREAT_INTELLIGENCE_CONTENT_TEST_ID)).toHaveTextContent( + expect(getByTestId(TITLE_LINK_TEST_ID)).toHaveTextContent('Threat Intelligence'); + expect(getByTestId(CONTENT_TEST_ID)).toHaveTextContent('1 threat match detected'); + expect(getByTestId(CONTENT_TEST_ID)).toHaveTextContent( '1 field enriched with threat intelligence' ); }); @@ -85,13 +99,9 @@ describe('', () => { const { getByTestId } = render(renderThreatIntelligenceOverview(panelContextValue)); - expect(getByTestId(INSIGHTS_THREAT_INTELLIGENCE_TITLE_LINK_TEST_ID)).toHaveTextContent( - 'Threat Intelligence' - ); - expect(getByTestId(INSIGHTS_THREAT_INTELLIGENCE_CONTENT_TEST_ID)).toHaveTextContent( - '2 threat matches detected' - ); - expect(getByTestId(INSIGHTS_THREAT_INTELLIGENCE_CONTENT_TEST_ID)).toHaveTextContent( + expect(getByTestId(TITLE_LINK_TEST_ID)).toHaveTextContent('Threat Intelligence'); + expect(getByTestId(CONTENT_TEST_ID)).toHaveTextContent('2 threat matches detected'); + expect(getByTestId(CONTENT_TEST_ID)).toHaveTextContent( '2 fields enriched with threat intelligence' ); }); @@ -105,7 +115,7 @@ describe('', () => { const { getByTestId } = render(renderThreatIntelligenceOverview(panelContextValue)); - expect(getByTestId(INSIGHTS_THREAT_INTELLIGENCE_CONTENT_TEST_ID)).toHaveTextContent( + expect(getByTestId(CONTENT_TEST_ID)).toHaveTextContent( '0 field enriched with threat intelligence' ); }); @@ -119,9 +129,7 @@ describe('', () => { const { getByTestId } = render(renderThreatIntelligenceOverview(panelContextValue)); - expect(getByTestId(INSIGHTS_THREAT_INTELLIGENCE_CONTENT_TEST_ID)).toHaveTextContent( - '0 threat match detected' - ); + expect(getByTestId(CONTENT_TEST_ID)).toHaveTextContent('0 threat match detected'); }); it('should render loading', () => { @@ -131,7 +139,7 @@ describe('', () => { const { getAllByTestId } = render(renderThreatIntelligenceOverview(panelContextValue)); - expect(getAllByTestId(INSIGHTS_THREAT_INTELLIGENCE_LOADING_TEST_ID)).toHaveLength(2); + expect(getAllByTestId(LOADING_TEST_ID)).toHaveLength(2); }); it('should render null when eventId is null', () => { @@ -183,7 +191,7 @@ describe('', () => { ); - getByTestId(INSIGHTS_THREAT_INTELLIGENCE_TITLE_LINK_TEST_ID).click(); + getByTestId(TITLE_LINK_TEST_ID).click(); expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ id: LeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: THREAT_INTELLIGENCE_TAB_ID }, diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/expandable_panel.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/expandable_panel.test.tsx index 7c7f46a11c308a..4889e4ebe005ac 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/components/expandable_panel.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/components/expandable_panel.test.tsx @@ -37,7 +37,6 @@ describe('', () => { {children} ); - expect(getByTestId(TEST_ID)).toBeInTheDocument(); expect(getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(TEST_ID))).toBeInTheDocument(); expect(getByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(TEST_ID))).toHaveTextContent( 'test content' @@ -104,7 +103,6 @@ describe('', () => { {children} ); - expect(getByTestId(TEST_ID)).toBeInTheDocument(); expect(getByTestId(EXPANDABLE_PANEL_HEADER_LEFT_SECTION_TEST_ID(TEST_ID))).toHaveTextContent( 'test title' ); @@ -151,7 +149,6 @@ describe('', () => { {children} ); - expect(getByTestId(TEST_ID)).toBeInTheDocument(); expect(getByTestId(EXPANDABLE_PANEL_HEADER_LEFT_SECTION_TEST_ID(TEST_ID))).toHaveTextContent( 'test title' ); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/expandable_panel.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/expandable_panel.tsx index f9bf5994dff35f..1f3ca141ee2310 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/components/expandable_panel.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/components/expandable_panel.tsx @@ -17,21 +17,9 @@ import { EuiTitle, EuiText, EuiLoadingSpinner, + useEuiTheme, } from '@elastic/eui'; -import styled from 'styled-components'; - -const StyledEuiFlexItem = styled(EuiFlexItem)` - margin-right: ${({ theme }) => theme.eui.euiSizeM}; -`; - -const StyledEuiIcon = styled(EuiIcon)` - margin: ${({ theme }) => theme.eui.euiSizeS} 0; -`; - -const StyledEuiLink = styled(EuiLink)` - font-size: 12px; - font-weight: 700; -`; +import { css } from '@emotion/react'; export interface ExpandablePanelPanelProps { header: { @@ -114,6 +102,8 @@ export const ExpandablePanel: React.FC = ({ [dataTestSubj, toggleStatus, toggleQuery] ); + const { euiTheme } = useEuiTheme(); + const headerLeftSection = useMemo( () => ( @@ -124,17 +114,27 @@ export const ExpandablePanel: React.FC = ({ > {expandable && children && toggleIcon} - {callback ? ( - + {title} - + ) : ( {title} @@ -144,17 +144,23 @@ export const ExpandablePanel: React.FC = ({ ), - [dataTestSubj, expandable, children, toggleIcon, callback, iconType, title] + [dataTestSubj, expandable, children, toggleIcon, callback, iconType, euiTheme.size.s, title] ); const headerRightSection = useMemo( () => headerContent && ( - + {headerContent} - + ), - [dataTestSubj, headerContent] + [dataTestSubj, euiTheme.size.m, headerContent] ); const showContent = useMemo(() => { @@ -175,7 +181,7 @@ export const ExpandablePanel: React.FC = ({ ); return ( - + {headerLeftSection} diff --git a/x-pack/plugins/security_solution/public/flyout/shared/constants/field_names.ts b/x-pack/plugins/security_solution/public/flyout/shared/constants/field_names.ts new file mode 100644 index 00000000000000..e9a896a73ddedd --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/constants/field_names.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const ANCESTOR_ID = 'kibana.alert.ancestors.id'; +export const RULE_PARAMETERS_INDEX = 'kibana.alert.rule.parameters.index'; +export const ORIGINAL_EVENT_ID = 'kibana.alert.original_event.id'; +export const ENTRY_LEADER_ENTITY_ID = 'process.entry_leader.entity_id'; +export const ENTRY_LEADER_START = 'process.entry_leader.start'; +export const ANCESTOR_INDEX = 'kibana.alert.ancestors.index'; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_correlations.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_correlations.test.tsx deleted file mode 100644 index 25762692d7f454..00000000000000 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_correlations.test.tsx +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import { useShowRelatedCases } from './use_show_related_cases'; -import { useFetchRelatedCases } from './use_fetch_related_cases'; -import { useShowRelatedAlertsByAncestry } from './use_show_related_alerts_by_ancestry'; -import { useFetchRelatedAlertsByAncestry } from './use_fetch_related_alerts_by_ancestry'; -import { useShowRelatedAlertsBySameSourceEvent } from './use_show_related_alerts_by_same_source_event'; -import { useFetchRelatedAlertsBySameSourceEvent } from './use_fetch_related_alerts_by_same_source_event'; -import { useShowRelatedAlertsBySession } from './use_show_related_alerts_by_session'; -import { useFetchRelatedAlertsBySession } from './use_fetch_related_alerts_by_session'; -import type { UseCorrelationsParams, UseCorrelationsResult } from './use_correlations'; -import { useCorrelations } from './use_correlations'; -import { mockDataAsNestedObject, mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; - -jest.mock('./use_show_related_cases'); -jest.mock('./use_fetch_related_cases'); -jest.mock('./use_show_related_alerts_by_ancestry'); -jest.mock('./use_fetch_related_alerts_by_ancestry'); -jest.mock('./use_show_related_alerts_by_same_source_event'); -jest.mock('./use_fetch_related_alerts_by_same_source_event'); -jest.mock('./use_show_related_alerts_by_session'); -jest.mock('./use_fetch_related_alerts_by_session'); - -const eventId = 'eventId'; -const dataAsNestedObject = mockDataAsNestedObject as unknown as Ecs; -const dataFormattedForFieldBrowser = mockDataFormattedForFieldBrowser; -const scopeId = 'scopeId'; - -const mockShowHooks = ({ - cases = true, - ancestry = true, - sameSource = true, - session = true, -}: { - cases?: boolean; - ancestry?: boolean; - sameSource?: boolean; - session?: boolean; -}) => { - (useShowRelatedCases as jest.Mock).mockReturnValue(cases); - (useShowRelatedAlertsByAncestry as jest.Mock).mockReturnValue(ancestry); - (useShowRelatedAlertsBySameSourceEvent as jest.Mock).mockReturnValue(sameSource); - (useShowRelatedAlertsBySession as jest.Mock).mockReturnValue(session); -}; - -const mockFetchLoadingReturnValue = { - loading: true, - error: false, - dataCount: 0, -}; - -const mockFetchErrorReturnValue = { - loading: false, - error: true, - dataCount: 0, -}; - -const mockFetchReturnValue = { - loading: false, - error: false, - dataCount: 1, -}; - -const mockFetchHooks = ({ - cases = mockFetchReturnValue, - ancestry = mockFetchReturnValue, - sameSource = mockFetchReturnValue, - session = mockFetchReturnValue, -}: { - cases?: typeof mockFetchReturnValue; - ancestry?: typeof mockFetchReturnValue; - sameSource?: typeof mockFetchReturnValue; - session?: typeof mockFetchReturnValue; -}) => { - (useFetchRelatedCases as jest.Mock).mockReturnValue(cases); - (useFetchRelatedAlertsByAncestry as jest.Mock).mockReturnValue(ancestry); - (useFetchRelatedAlertsBySameSourceEvent as jest.Mock).mockReturnValue(sameSource); - (useFetchRelatedAlertsBySession as jest.Mock).mockReturnValue(session); -}; - -describe('useCorrelations', () => { - let hookResult: RenderHookResult; - - it(`should return loading true if casesLoading is true`, () => { - mockShowHooks({}); - mockFetchHooks({ cases: mockFetchLoadingReturnValue }); - - hookResult = renderHook(() => - useCorrelations({ eventId, dataAsNestedObject, dataFormattedForFieldBrowser, scopeId }) - ); - - expect(hookResult.result.current.loading).toEqual(true); - expect(hookResult.result.current.dataCount).toEqual(3); - }); - - it(`should return loading true if ancestryAlertsLoading is true`, () => { - mockShowHooks({}); - mockFetchHooks({ ancestry: mockFetchLoadingReturnValue }); - - hookResult = renderHook(() => - useCorrelations({ eventId, dataAsNestedObject, dataFormattedForFieldBrowser, scopeId }) - ); - - expect(hookResult.result.current.loading).toEqual(true); - expect(hookResult.result.current.dataCount).toEqual(3); - }); - - it(`should return loading true if sameSourceAlertsLoading is true`, () => { - mockShowHooks({}); - mockFetchHooks({ sameSource: mockFetchLoadingReturnValue }); - - hookResult = renderHook(() => - useCorrelations({ eventId, dataAsNestedObject, dataFormattedForFieldBrowser, scopeId }) - ); - - expect(hookResult.result.current.loading).toEqual(true); - expect(hookResult.result.current.dataCount).toEqual(3); - }); - - it(`should return loading true if alertsBySessionLoading is true`, () => { - mockShowHooks({}); - mockFetchHooks({ session: mockFetchLoadingReturnValue }); - - hookResult = renderHook(() => - useCorrelations({ eventId, dataAsNestedObject, dataFormattedForFieldBrowser, scopeId }) - ); - - expect(hookResult.result.current.loading).toEqual(true); - expect(hookResult.result.current.dataCount).toEqual(3); - }); - - it(`should return dataCount 3 if casesError is true`, () => { - mockShowHooks({}); - mockFetchHooks({ cases: mockFetchErrorReturnValue }); - - hookResult = renderHook(() => - useCorrelations({ eventId, dataAsNestedObject, dataFormattedForFieldBrowser, scopeId }) - ); - - expect(hookResult.result.current.dataCount).toEqual(3); - }); - - it(`should return dataCount 3 if ancestryAlertsError is true`, () => { - mockShowHooks({}); - mockFetchHooks({ ancestry: mockFetchErrorReturnValue }); - - hookResult = renderHook(() => - useCorrelations({ eventId, dataAsNestedObject, dataFormattedForFieldBrowser, scopeId }) - ); - - expect(hookResult.result.current.dataCount).toEqual(3); - }); - - it(`should return dataCount 3 if sameSourceAlertsError is true`, () => { - mockShowHooks({}); - mockFetchHooks({ sameSource: mockFetchErrorReturnValue }); - - hookResult = renderHook(() => - useCorrelations({ eventId, dataAsNestedObject, dataFormattedForFieldBrowser, scopeId }) - ); - - expect(hookResult.result.current.dataCount).toEqual(3); - }); - - it(`should return dataCount 3 if alertsBySessionError is true`, () => { - mockShowHooks({}); - mockFetchHooks({ session: mockFetchErrorReturnValue }); - - hookResult = renderHook(() => - useCorrelations({ eventId, dataAsNestedObject, dataFormattedForFieldBrowser, scopeId }) - ); - - expect(hookResult.result.current.dataCount).toEqual(3); - }); - - it(`should return error true if all errors are true`, () => { - mockShowHooks({}); - mockFetchHooks({ - cases: mockFetchErrorReturnValue, - ancestry: mockFetchErrorReturnValue, - sameSource: mockFetchErrorReturnValue, - session: mockFetchErrorReturnValue, - }); - - hookResult = renderHook(() => - useCorrelations({ eventId, dataAsNestedObject, dataFormattedForFieldBrowser, scopeId }) - ); - - expect(hookResult.result.current.error).toEqual(true); - expect(hookResult.result.current.dataCount).toEqual(0); - }); - - it(`should return dataCount 0 if all loading are true`, () => { - mockShowHooks({}); - mockFetchHooks({ - cases: mockFetchLoadingReturnValue, - ancestry: mockFetchLoadingReturnValue, - sameSource: mockFetchLoadingReturnValue, - session: mockFetchLoadingReturnValue, - }); - - hookResult = renderHook(() => - useCorrelations({ eventId, dataAsNestedObject, dataFormattedForFieldBrowser, scopeId }) - ); - - expect(hookResult.result.current.loading).toEqual(true); - expect(hookResult.result.current.dataCount).toEqual(0); - }); -}); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_correlations.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_correlations.ts deleted file mode 100644 index 24f328d059d54b..00000000000000 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_correlations.ts +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import { useEffect, useMemo, useReducer } from 'react'; -import type { GetRelatedCasesByAlertResponse } from '@kbn/cases-plugin/common'; -import { useFetchRelatedAlertsBySameSourceEvent } from './use_fetch_related_alerts_by_same_source_event'; -import { useShowRelatedCases } from './use_show_related_cases'; -import { useFetchRelatedCases } from './use_fetch_related_cases'; -import { - CORRELATIONS_ANCESTRY_ALERT, - CORRELATIONS_ANCESTRY_ALERTS, - CORRELATIONS_RELATED_CASE, - CORRELATIONS_RELATED_CASES, - CORRELATIONS_SAME_SESSION_ALERT, - CORRELATIONS_SAME_SESSION_ALERTS, - CORRELATIONS_SAME_SOURCE_EVENT_ALERT, - CORRELATIONS_SAME_SOURCE_EVENT_ALERTS, -} from '../translations'; -import { useShowRelatedAlertsByAncestry } from './use_show_related_alerts_by_ancestry'; -import { useFetchRelatedAlertsByAncestry } from './use_fetch_related_alerts_by_ancestry'; -import { useShowRelatedAlertsBySameSourceEvent } from './use_show_related_alerts_by_same_source_event'; -import { useShowRelatedAlertsBySession } from './use_show_related_alerts_by_session'; -import { useFetchRelatedAlertsBySession } from './use_fetch_related_alerts_by_session'; - -export interface InsightsSummaryPanelData { - icon: string; - value: number; - text: string; -} - -export interface UseCorrelationsParams { - /** - * Id of the document - */ - eventId: string; - /** - * An object with top level fields from the ECS object - */ - dataAsNestedObject: Ecs | null; - /** - * An array of field objects with category and value - */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; - /** - * Maintain backwards compatibility // TODO remove when possible - */ - scopeId: string; -} -export interface UseCorrelationsResult { - /** - * Returns true while data is loading - */ - loading: boolean; - /** - * Returns true if there is an error while retrieving data - */ - error: boolean; - /** - * Data ready to be consumed by the InsightsSummaryPanel component - */ - data: InsightsSummaryPanelData[]; - /** - * Data length - */ - dataCount: number; - /** - * Ids of specific alerts correlated by session, can be used to fetch specific alert documents - */ - alertsBySessionIds: string[]; - /** - * Ids of specific alerts correlated by source, can be used to fetch specific alert documents - */ - sameSourceAlertsIds: string[]; - /** - * Ids of specific alerts correlated by ancestry, can be used to fetch specific alert documents - */ - ancestryAlertsIds: string[]; - /** - * Cases data, can be used to render correlated cases table - */ - cases: GetRelatedCasesByAlertResponse; -} - -/** - * Retrieves all correlations data from custom hooks - */ -export const useCorrelations = ({ - eventId, - dataAsNestedObject, - dataFormattedForFieldBrowser, - scopeId, -}: UseCorrelationsParams): UseCorrelationsResult => { - const [data, updateInsightsSummaryPanel] = useReducer( - ( - currentEntries: InsightsSummaryPanelData[], - newEntry: { icon: string; value: number; text: string } - ) => { - return [...currentEntries, newEntry]; - }, - [] - ); - - // cases - const showCases = useShowRelatedCases(); - const { - loading: casesLoading, - error: casesError, - dataCount: casesCount, - data: cases, - } = useFetchRelatedCases({ eventId }); - - useEffect(() => { - if (showCases && !casesLoading && !casesError) { - updateInsightsSummaryPanel({ - icon: 'warning', - value: casesCount, - text: casesCount <= 1 ? CORRELATIONS_RELATED_CASE : CORRELATIONS_RELATED_CASES, - }); - } - }, [casesCount, casesError, casesLoading, showCases]); - - // alerts by ancestry - const showAlertsByAncestry = useShowRelatedAlertsByAncestry({ - dataFormattedForFieldBrowser, - dataAsNestedObject, - }); - const { - loading: ancestryAlertsLoading, - error: ancestryAlertsError, - dataCount: ancestryAlertsCount, - data: ancestryAlertsIds, - } = useFetchRelatedAlertsByAncestry({ - dataFormattedForFieldBrowser, - scopeId, - }); - - useEffect(() => { - if (showAlertsByAncestry && !ancestryAlertsLoading && !ancestryAlertsError) { - updateInsightsSummaryPanel({ - icon: 'warning', - value: ancestryAlertsCount, - text: ancestryAlertsCount <= 1 ? CORRELATIONS_ANCESTRY_ALERT : CORRELATIONS_ANCESTRY_ALERTS, - }); - } - }, [ancestryAlertsCount, ancestryAlertsError, ancestryAlertsLoading, showAlertsByAncestry]); - - // alerts related to same source event - const showSameSourceAlerts = useShowRelatedAlertsBySameSourceEvent({ - dataFormattedForFieldBrowser, - }); - const { - loading: sameSourceAlertsLoading, - error: sameSourceAlertsError, - dataCount: sameSourceAlertsCount, - data: sameSourceAlertsIds, - } = useFetchRelatedAlertsBySameSourceEvent({ - dataFormattedForFieldBrowser, - scopeId, - }); - - useEffect(() => { - if (showSameSourceAlerts && !sameSourceAlertsLoading && !sameSourceAlertsError) { - updateInsightsSummaryPanel({ - icon: 'warning', - value: sameSourceAlertsCount, - text: - sameSourceAlertsCount <= 1 - ? CORRELATIONS_SAME_SOURCE_EVENT_ALERT - : CORRELATIONS_SAME_SOURCE_EVENT_ALERTS, - }); - } - }, [sameSourceAlertsCount, sameSourceAlertsError, sameSourceAlertsLoading, showSameSourceAlerts]); - - // alerts related by session - const showAlertsBySession = useShowRelatedAlertsBySession({ dataFormattedForFieldBrowser }); - const { - loading: alertsBySessionLoading, - error: alertsBySessionError, - dataCount: alertsBySessionCount, - data: alertsBySessionIds, - } = useFetchRelatedAlertsBySession({ - dataFormattedForFieldBrowser, - scopeId, - }); - - useEffect(() => { - if (showAlertsBySession && !alertsBySessionLoading && !alertsBySessionError) { - updateInsightsSummaryPanel({ - icon: 'warning', - value: alertsBySessionCount, - text: - alertsBySessionCount <= 1 - ? CORRELATIONS_SAME_SESSION_ALERT - : CORRELATIONS_SAME_SESSION_ALERTS, - }); - } - }, [alertsBySessionCount, alertsBySessionError, alertsBySessionLoading, showAlertsBySession]); - - return useMemo( - () => ({ - loading: - casesLoading || ancestryAlertsLoading || alertsBySessionLoading || sameSourceAlertsLoading, - error: data.length === 0, - data, - dataCount: data.length || 0, - alertsBySessionIds, - sameSourceAlertsIds, - ancestryAlertsIds: ancestryAlertsIds || [], - cases, - }), - [ - alertsBySessionIds, - alertsBySessionLoading, - ancestryAlertsIds, - ancestryAlertsLoading, - cases, - casesLoading, - data, - sameSourceAlertsIds, - sameSourceAlertsLoading, - ] - ); -}; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_ancestry.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_ancestry.test.tsx index 973dc391c9a318..27d0e83b34b1ac 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_ancestry.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_ancestry.test.tsx @@ -7,7 +7,6 @@ import type { RenderHookResult } from '@testing-library/react-hooks'; import { renderHook } from '@testing-library/react-hooks'; -import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; import type { UseFetchRelatedAlertsByAncestryParams, UseFetchRelatedAlertsByAncestryResult, @@ -17,7 +16,8 @@ import { useAlertPrevalenceFromProcessTree } from '../../../common/containers/al jest.mock('../../../common/containers/alerts/use_alert_prevalence_from_process_tree'); -const dataFormattedForFieldBrowser = mockDataFormattedForFieldBrowser; +const documentId = 'documentId'; +const indices = ['index1']; const scopeId = 'scopeId'; describe('useFetchRelatedAlertsByAncestry', () => { @@ -34,7 +34,7 @@ describe('useFetchRelatedAlertsByAncestry', () => { }); hookResult = renderHook(() => - useFetchRelatedAlertsByAncestry({ dataFormattedForFieldBrowser, scopeId }) + useFetchRelatedAlertsByAncestry({ documentId, indices, scopeId }) ); expect(hookResult.result.current.loading).toEqual(true); @@ -51,7 +51,7 @@ describe('useFetchRelatedAlertsByAncestry', () => { }); hookResult = renderHook(() => - useFetchRelatedAlertsByAncestry({ dataFormattedForFieldBrowser, scopeId }) + useFetchRelatedAlertsByAncestry({ documentId, indices, scopeId }) ); expect(hookResult.result.current.loading).toEqual(false); @@ -68,7 +68,7 @@ describe('useFetchRelatedAlertsByAncestry', () => { }); hookResult = renderHook(() => - useFetchRelatedAlertsByAncestry({ dataFormattedForFieldBrowser, scopeId }) + useFetchRelatedAlertsByAncestry({ documentId, indices, scopeId }) ); expect(hookResult.result.current.loading).toEqual(false); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_ancestry.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_ancestry.ts index d5d762e3dc5342..5dd4a2da67e703 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_ancestry.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_ancestry.ts @@ -5,17 +5,19 @@ * 2.0. */ -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { find } from 'lodash/fp'; import { useMemo } from 'react'; import { useAlertPrevalenceFromProcessTree } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; import { isActiveTimeline } from '../../../helpers'; export interface UseFetchRelatedAlertsByAncestryParams { /** - * An array of field objects with category and value + * Value of the kibana.alert.ancestors.id field */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + documentId: string; + /** + * Values of the kibana.alert.rule.parameters.index field + */ + indices: string[]; /** * Maintain backwards compatibility // TODO remove when possible */ @@ -45,33 +47,16 @@ export interface UseFetchRelatedAlertsByAncestryResult { * This uses the kibana.alert.ancestors.id and kibana.alert.rule.parameters.index fields. */ export const useFetchRelatedAlertsByAncestry = ({ - dataFormattedForFieldBrowser, + documentId, + indices, scopeId, }: UseFetchRelatedAlertsByAncestryParams): UseFetchRelatedAlertsByAncestryResult => { - const documentId = useMemo(() => { - const originalDocumentId = find( - { category: 'kibana', field: 'kibana.alert.ancestors.id' }, - dataFormattedForFieldBrowser - ); - const { values } = originalDocumentId ?? { values: [] }; - return Array.isArray(values) ? values[0] : ''; - }, [dataFormattedForFieldBrowser]); - - const { values: indices } = useMemo( - () => - find( - { category: 'kibana', field: 'kibana.alert.rule.parameters.index' }, - dataFormattedForFieldBrowser - ) || { values: [] }, - [dataFormattedForFieldBrowser] - ); - - const isActiveTimelines = isActiveTimeline(scopeId ?? ''); + const isActiveTimelines = isActiveTimeline(scopeId); const { loading, error, alertIds } = useAlertPrevalenceFromProcessTree({ isActiveTimeline: isActiveTimelines, documentId, - indices: indices || [], + indices, }); return useMemo( diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_same_source_event.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_same_source_event.test.tsx index cdac222d114cc1..f4f6bb894eba01 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_same_source_event.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_same_source_event.test.tsx @@ -7,7 +7,6 @@ import type { RenderHookResult } from '@testing-library/react-hooks'; import { renderHook } from '@testing-library/react-hooks'; -import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; import type { UseFetchRelatedAlertsBySameSourceEventParams, UseFetchRelatedAlertsBySameSourceEventResult, @@ -17,7 +16,7 @@ import { useAlertPrevalence } from '../../../common/containers/alerts/use_alert_ jest.mock('../../../common/containers/alerts/use_alert_prevalence'); -const dataFormattedForFieldBrowser = mockDataFormattedForFieldBrowser; +const originalEventId = 'originalEventId'; const scopeId = 'scopeId'; describe('useFetchRelatedAlertsBySameSourceEvent', () => { @@ -34,7 +33,7 @@ describe('useFetchRelatedAlertsBySameSourceEvent', () => { count: 0, }); hookResult = renderHook(() => - useFetchRelatedAlertsBySameSourceEvent({ dataFormattedForFieldBrowser, scopeId }) + useFetchRelatedAlertsBySameSourceEvent({ originalEventId, scopeId }) ); expect(hookResult.result.current.loading).toEqual(true); @@ -51,7 +50,7 @@ describe('useFetchRelatedAlertsBySameSourceEvent', () => { count: 0, }); hookResult = renderHook(() => - useFetchRelatedAlertsBySameSourceEvent({ dataFormattedForFieldBrowser, scopeId }) + useFetchRelatedAlertsBySameSourceEvent({ originalEventId, scopeId }) ); expect(hookResult.result.current.loading).toEqual(false); @@ -68,7 +67,7 @@ describe('useFetchRelatedAlertsBySameSourceEvent', () => { count: 2, }); hookResult = renderHook(() => - useFetchRelatedAlertsBySameSourceEvent({ dataFormattedForFieldBrowser, scopeId }) + useFetchRelatedAlertsBySameSourceEvent({ originalEventId, scopeId }) ); expect(hookResult.result.current.loading).toEqual(false); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_same_source_event.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_same_source_event.ts index 764e85f21b1b09..7e3fde3796c943 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_same_source_event.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_same_source_event.ts @@ -5,17 +5,16 @@ * 2.0. */ -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { find } from 'lodash/fp'; import { useMemo } from 'react'; +import { ORIGINAL_EVENT_ID } from '../constants/field_names'; import { useAlertPrevalence } from '../../../common/containers/alerts/use_alert_prevalence'; import { isActiveTimeline } from '../../../helpers'; export interface UseFetchRelatedAlertsBySameSourceEventParams { /** - * An array of field objects with category and value + * Value of the kibana.alert.original_event.id field */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + originalEventId: string; /** * Maintain backwards compatibility // TODO remove when possible */ @@ -44,21 +43,12 @@ export interface UseFetchRelatedAlertsBySameSourceEventResult { * Returns the number of alerts for the same source event (and the loading, error statuses as well as the alerts count) */ export const useFetchRelatedAlertsBySameSourceEvent = ({ - dataFormattedForFieldBrowser, + originalEventId, scopeId, }: UseFetchRelatedAlertsBySameSourceEventParams): UseFetchRelatedAlertsBySameSourceEventResult => { - const { field, values } = useMemo( - () => - find( - { category: 'kibana', field: 'kibana.alert.original_event.id' }, - dataFormattedForFieldBrowser - ) || { field: '', values: [] }, - [dataFormattedForFieldBrowser] - ); - const { loading, error, count, alertIds } = useAlertPrevalence({ - field, - value: values, + field: ORIGINAL_EVENT_ID, + value: originalEventId, isActiveTimelines: isActiveTimeline(scopeId), signalIndexName: null, includeAlertIds: true, diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_session.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_session.test.tsx index d3264531214053..dfbe47a2582776 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_session.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_session.test.tsx @@ -13,12 +13,11 @@ import type { UseFetchRelatedAlertsBySessionResult, } from './use_fetch_related_alerts_by_session'; import { useFetchRelatedAlertsBySession } from './use_fetch_related_alerts_by_session'; -import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; import { useAlertPrevalence } from '../../../common/containers/alerts/use_alert_prevalence'; jest.mock('../../../common/containers/alerts/use_alert_prevalence'); -const dataFormattedForFieldBrowser = mockDataFormattedForFieldBrowser; +const entityId = 'entityId'; const scopeId = 'scopeId'; describe('useFetchRelatedAlertsBySession', () => { @@ -34,9 +33,7 @@ describe('useFetchRelatedAlertsBySession', () => { alertIds: [], count: 0, }); - hookResult = renderHook(() => - useFetchRelatedAlertsBySession({ dataFormattedForFieldBrowser, scopeId }) - ); + hookResult = renderHook(() => useFetchRelatedAlertsBySession({ entityId, scopeId })); expect(hookResult.result.current.loading).toEqual(true); expect(hookResult.result.current.error).toEqual(false); @@ -51,9 +48,7 @@ describe('useFetchRelatedAlertsBySession', () => { alertIds: [], count: 0, }); - hookResult = renderHook(() => - useFetchRelatedAlertsBySession({ dataFormattedForFieldBrowser, scopeId }) - ); + hookResult = renderHook(() => useFetchRelatedAlertsBySession({ entityId, scopeId })); expect(hookResult.result.current.loading).toEqual(false); expect(hookResult.result.current.error).toEqual(true); @@ -68,9 +63,7 @@ describe('useFetchRelatedAlertsBySession', () => { alertIds: ['1', '2'], count: 2, }); - hookResult = renderHook(() => - useFetchRelatedAlertsBySession({ dataFormattedForFieldBrowser, scopeId }) - ); + hookResult = renderHook(() => useFetchRelatedAlertsBySession({ entityId, scopeId })); expect(hookResult.result.current.loading).toEqual(false); expect(hookResult.result.current.error).toEqual(false); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_session.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_session.ts index 2c1104eadddc9f..1eca3d8d513687 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_session.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_fetch_related_alerts_by_session.ts @@ -5,17 +5,16 @@ * 2.0. */ -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { find } from 'lodash/fp'; import { useMemo } from 'react'; import { useAlertPrevalence } from '../../../common/containers/alerts/use_alert_prevalence'; import { isActiveTimeline } from '../../../helpers'; +import { ENTRY_LEADER_ENTITY_ID } from '../constants/field_names'; export interface UseFetchRelatedAlertsBySessionParams { /** - * An array of field objects with category and value + * Value of the process.entry_leader.entity_id field */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + entityId: string; /** * Maintain backwards compatibility // TODO remove when possible */ @@ -44,17 +43,12 @@ export interface UseFetchRelatedAlertsBySessionResult { * Returns the number of alerts by session for the document (and the loading, error statuses as well as the alerts count) */ export const useFetchRelatedAlertsBySession = ({ - dataFormattedForFieldBrowser, + entityId, scopeId, }: UseFetchRelatedAlertsBySessionParams): UseFetchRelatedAlertsBySessionResult => { - const processSessionField = find( - { category: 'process', field: 'process.entry_leader.entity_id' }, - dataFormattedForFieldBrowser - ); - const { field, values } = processSessionField || { field: '', values: [] }; const { loading, error, count, alertIds } = useAlertPrevalence({ - field, - value: values, + field: ENTRY_LEADER_ENTITY_ID, + value: entityId, isActiveTimelines: isActiveTimeline(scopeId), signalIndexName: null, includeAlertIds: true, diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.test.tsx index 812037d1ce4e8b..7500d7c041708c 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.test.tsx @@ -8,12 +8,14 @@ import type { RenderHookResult } from '@testing-library/react-hooks'; import { renderHook } from '@testing-library/react-hooks'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import type { UseShowRelatedAlertsByAncestryParams } from './use_show_related_alerts_by_ancestry'; +import type { + UseShowRelatedAlertsByAncestryParams, + UseShowRelatedAlertsByAncestryResult, +} from './use_show_related_alerts_by_ancestry'; import { useShowRelatedAlertsByAncestry } from './use_show_related_alerts_by_ancestry'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; import { licenseService } from '../../../common/hooks/use_license'; -import { mockDataAsNestedObject } from '../mocks/mock_context'; +import { mockDataAsNestedObject, mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; jest.mock('../../../common/hooks/use_experimental_features'); jest.mock('../../../common/hooks/use_license', () => { @@ -30,157 +32,83 @@ jest.mock('../../../common/hooks/use_license', () => { const licenseServiceMock = licenseService as jest.Mocked; const dataAsNestedObject = mockDataAsNestedObject as unknown as Ecs; +const dataFormattedForFieldBrowser = mockDataFormattedForFieldBrowser; -const agentType = { - category: 'agent', - field: 'agent.type', - isObjectArray: false, - originalValue: ['endpoint'], - values: ['endpoint'], -}; -const eventModule = { - category: 'event', - field: 'event.module', - isObjectArray: false, - originalValue: ['abc'], - values: ['abc'], -}; -const processEntityId = { - category: 'process', - field: 'process.entity_id', - isObjectArray: false, - originalValue: ['abc'], - values: ['abc'], -}; -const alertAncestorId = { - category: 'kibana', - field: 'kibana.alert.ancestors.id', - isObjectArray: false, - originalValue: ['abc'], - values: ['abc'], -}; -const ruleParameterIndex = { - category: 'kibana', - field: 'kibana.alert.rule.parameters.index', - isObjectArray: false, - originalValue: ['abc'], - values: ['abc'], -}; +describe('useShowRelatedAlertsByAncestry', () => { + let hookResult: RenderHookResult< + UseShowRelatedAlertsByAncestryParams, + UseShowRelatedAlertsByAncestryResult + >; -describe('useShowRelatedAlertsBySameSourceEvent', () => { - let hookResult: RenderHookResult; - - it('should return false if dataFormattedForFieldBrowser is null', () => { + it('should return false if getFieldsData returns null', () => { (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); licenseServiceMock.isPlatinumPlus.mockReturnValue(true); - const dataFormattedForFieldBrowser = null; + const getFieldsData = () => null; hookResult = renderHook(() => - useShowRelatedAlertsByAncestry({ dataFormattedForFieldBrowser, dataAsNestedObject }) + useShowRelatedAlertsByAncestry({ + getFieldsData, + dataAsNestedObject, + dataFormattedForFieldBrowser, + }) ); - expect(hookResult.result.current).toEqual(false); + expect(hookResult.result.current).toEqual({ show: false, indices: ['rule-parameters-index'] }); }); it(`should return false if feature isn't enabled`, () => { - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); - licenseServiceMock.isPlatinumPlus.mockReturnValue(false); - const dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] = [ - agentType, - eventModule, - processEntityId, - alertAncestorId, - ruleParameterIndex, - ]; - hookResult = renderHook(() => - useShowRelatedAlertsByAncestry({ dataFormattedForFieldBrowser, dataAsNestedObject }) - ); - - expect(hookResult.result.current).toEqual(false); - }); - - it('should return false if dataFormattedForFieldBrowser is missing kibana.alert.ancestors.id field', () => { - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false); licenseServiceMock.isPlatinumPlus.mockReturnValue(true); - const dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] = [ - agentType, - eventModule, - processEntityId, - ruleParameterIndex, - ]; + const getFieldsData = () => 'value'; hookResult = renderHook(() => - useShowRelatedAlertsByAncestry({ dataFormattedForFieldBrowser, dataAsNestedObject }) + useShowRelatedAlertsByAncestry({ + getFieldsData, + dataAsNestedObject, + dataFormattedForFieldBrowser, + }) ); - expect(hookResult.result.current).toEqual(false); + expect(hookResult.result.current).toEqual({ + show: false, + documentId: 'value', + indices: ['rule-parameters-index'], + }); }); - it('should return false if dataFormattedForFieldBrowser is missing kibana.alert.rule.parameters.index field', () => { + it(`should return false if license is lower than platinum`, () => { (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); - licenseServiceMock.isPlatinumPlus.mockReturnValue(true); - const dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] = [ - agentType, - eventModule, - processEntityId, - alertAncestorId, - ]; - hookResult = renderHook(() => - useShowRelatedAlertsByAncestry({ dataFormattedForFieldBrowser, dataAsNestedObject }) - ); - - expect(hookResult.result.current).toEqual(false); - }); - - it('should return false if dataFormattedForFieldBrowser is missing process.entity_id field', () => { - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); - licenseServiceMock.isPlatinumPlus.mockReturnValue(true); - const dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] = [ - agentType, - eventModule, - alertAncestorId, - ruleParameterIndex, - ]; - hookResult = renderHook(() => - useShowRelatedAlertsByAncestry({ dataFormattedForFieldBrowser, dataAsNestedObject }) - ); - - expect(hookResult.result.current).toEqual(false); - }); - - it('should return false if dataFormattedForFieldBrowser is missing event.module field', () => { - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); - licenseServiceMock.isPlatinumPlus.mockReturnValue(true); - const dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] = [ - { - category: 'agent', - field: 'agent.type', - isObjectArray: false, - originalValue: ['winlogbeat'], - values: ['winlogbeat'], - }, - processEntityId, - alertAncestorId, - ruleParameterIndex, - ]; + licenseServiceMock.isPlatinumPlus.mockReturnValue(false); + const getFieldsData = () => 'value'; hookResult = renderHook(() => - useShowRelatedAlertsByAncestry({ dataFormattedForFieldBrowser, dataAsNestedObject }) + useShowRelatedAlertsByAncestry({ + getFieldsData, + dataAsNestedObject, + dataFormattedForFieldBrowser, + }) ); - expect(hookResult.result.current).toEqual(false); + expect(hookResult.result.current).toEqual({ + show: false, + documentId: 'value', + indices: ['rule-parameters-index'], + }); }); - it('should return false if dataFormattedForFieldBrowser is missing agent.type field', () => { + it('should return true if getFieldsData has the correct fields', () => { (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); licenseServiceMock.isPlatinumPlus.mockReturnValue(true); - const dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] = [ - eventModule, - processEntityId, - alertAncestorId, - ruleParameterIndex, - ]; + const getFieldsData = () => 'value'; hookResult = renderHook(() => - useShowRelatedAlertsByAncestry({ dataFormattedForFieldBrowser, dataAsNestedObject }) + useShowRelatedAlertsByAncestry({ + getFieldsData, + dataAsNestedObject, + dataFormattedForFieldBrowser, + }) ); - expect(hookResult.result.current).toEqual(false); + expect(hookResult.result.current).toEqual({ + show: true, + documentId: 'value', + indices: ['rule-parameters-index'], + }); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.ts index 2904629303e316..85fde8e6732b33 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_ancestry.ts @@ -6,30 +6,54 @@ */ import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import { useMemo } from 'react'; import { find } from 'lodash/fp'; +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import type { GetFieldsData } from '../../../common/hooks/use_get_fields_data'; import { isInvestigateInResolverActionEnabled } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { useLicense } from '../../../common/hooks/use_license'; +import { getField } from '../utils'; +import { ANCESTOR_ID } from '../constants/field_names'; export interface UseShowRelatedAlertsByAncestryParams { /** - * An array of field objects with category and value + * Retrieves searchHit values for the provided field */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + getFieldsData: GetFieldsData; /** * An object with top level fields from the ECS object */ dataAsNestedObject: Ecs | null; + /** + * An array of field objects with category and value + */ + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; +} + +export interface UseShowRelatedAlertsByAncestryResult { + /** + * Returns true if the user has at least platinum privilege, and if the document has ancestry data + */ + show: boolean; + /** + * Value of the kibana.alert.ancestors.id field + */ + documentId?: string; + /** + * Values of the kibana.alert.rule.parameters.index field + */ + indices?: string[]; } /** * Returns true if the user has at least platinum privilege, and if the document has ancestry data */ export const useShowRelatedAlertsByAncestry = ({ - dataFormattedForFieldBrowser, + getFieldsData, dataAsNestedObject, -}: UseShowRelatedAlertsByAncestryParams): boolean => { + dataFormattedForFieldBrowser, +}: UseShowRelatedAlertsByAncestryParams): UseShowRelatedAlertsByAncestryResult => { const isRelatedAlertsByProcessAncestryEnabled = useIsExperimentalFeatureEnabled( 'insightsRelatedAlertsByProcessAncestry' ); @@ -37,21 +61,31 @@ export const useShowRelatedAlertsByAncestry = ({ dataAsNestedObject || undefined ); - const originalDocumentId = find( - { category: 'kibana', field: 'kibana.alert.ancestors.id' }, - dataFormattedForFieldBrowser - ); - const originalDocumentIndex = find( - { category: 'kibana', field: 'kibana.alert.rule.parameters.index' }, - dataFormattedForFieldBrowser + const originalDocumentId = getField(getFieldsData(ANCESTOR_ID)); + + // can't use getFieldsData here as the kibana.alert.rule.parameters is different and can be nested + const originalDocumentIndex = useMemo( + () => + find( + { category: 'kibana', field: 'kibana.alert.rule.parameters.index' }, + dataFormattedForFieldBrowser + ), + [dataFormattedForFieldBrowser] ); - const canShowAncestryInsight = + const hasAtLeastPlatinum = useLicense().isPlatinumPlus(); + + const show = isRelatedAlertsByProcessAncestryEnabled && hasProcessEntityInfo && - originalDocumentId && - originalDocumentIndex; - const hasAtLeastPlatinum = useLicense().isPlatinumPlus(); + originalDocumentId != null && + originalDocumentIndex != null && + hasAtLeastPlatinum; - return canShowAncestryInsight != null && canShowAncestryInsight && hasAtLeastPlatinum; + return { + show, + ...(originalDocumentId && { documentId: originalDocumentId }), + ...(originalDocumentIndex && + originalDocumentIndex.values && { indices: originalDocumentIndex.values }), + }; }; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_same_source_event.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_same_source_event.test.tsx index 5b47c7579ec125..01a014409264c9 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_same_source_event.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_same_source_event.test.tsx @@ -8,45 +8,29 @@ import type { RenderHookResult } from '@testing-library/react-hooks'; import { renderHook } from '@testing-library/react-hooks'; -import type { ShowRelatedAlertsBySameSourceEventParams } from './use_show_related_alerts_by_same_source_event'; +import type { + ShowRelatedAlertsBySameSourceEventParams, + ShowRelatedAlertsBySameSourceEventResult, +} from './use_show_related_alerts_by_same_source_event'; import { useShowRelatedAlertsBySameSourceEvent } from './use_show_related_alerts_by_same_source_event'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; describe('useShowRelatedAlertsBySameSourceEvent', () => { - let hookResult: RenderHookResult; + let hookResult: RenderHookResult< + ShowRelatedAlertsBySameSourceEventParams, + ShowRelatedAlertsBySameSourceEventResult + >; - it('should return false if dataFormattedForFieldBrowser is null', () => { - const dataFormattedForFieldBrowser = null; - hookResult = renderHook(() => - useShowRelatedAlertsBySameSourceEvent({ dataFormattedForFieldBrowser }) - ); + it('should return false if getFieldsData returns null', () => { + const getFieldsData = () => null; + hookResult = renderHook(() => useShowRelatedAlertsBySameSourceEvent({ getFieldsData })); - expect(hookResult.result.current).toEqual(false); + expect(hookResult.result.current).toEqual({ show: false }); }); - it('should return false if dataFormattedForFieldBrowser is missing the correct field', () => { - const dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] = []; - hookResult = renderHook(() => - useShowRelatedAlertsBySameSourceEvent({ dataFormattedForFieldBrowser }) - ); + it('should return true if getFieldsData has the correct field', () => { + const getFieldsData = () => 'original_event'; + hookResult = renderHook(() => useShowRelatedAlertsBySameSourceEvent({ getFieldsData })); - expect(hookResult.result.current).toEqual(false); - }); - - it('should return true if dataFormattedForFieldBrowser has the correct field', () => { - const dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] = [ - { - category: 'kibana', - field: 'kibana.alert.original_event.id', - isObjectArray: false, - originalValue: ['abc'], - values: ['abc'], - }, - ]; - hookResult = renderHook(() => - useShowRelatedAlertsBySameSourceEvent({ dataFormattedForFieldBrowser }) - ); - - expect(hookResult.result.current).toEqual(true); + expect(hookResult.result.current).toEqual({ show: true, originalEventId: 'original_event' }); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_same_source_event.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_same_source_event.ts index c02395db551d83..0de1af516aad46 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_same_source_event.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_same_source_event.ts @@ -5,27 +5,37 @@ * 2.0. */ -import { find } from 'lodash/fp'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { hasData } from '../../../common/components/event_details/insights/helpers'; +import type { GetFieldsData } from '../../../common/hooks/use_get_fields_data'; +import { ORIGINAL_EVENT_ID } from '../constants/field_names'; +import { getField } from '../utils'; export interface ShowRelatedAlertsBySameSourceEventParams { /** - * An array of field objects with category and value + * Retrieves searchHit values for the provided field */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + getFieldsData: GetFieldsData; +} + +export interface ShowRelatedAlertsBySameSourceEventResult { + /** + * Returns true if the document has kibana.alert.original_event.id field with values + */ + show: boolean; + /** + * Value of the kibana.alert.original_event.id field + */ + originalEventId?: string; } /** * Returns true if document has kibana.alert.original.event.id field with values */ export const useShowRelatedAlertsBySameSourceEvent = ({ - dataFormattedForFieldBrowser, -}: ShowRelatedAlertsBySameSourceEventParams): boolean => { - const sourceEventField = find( - { category: 'kibana', field: 'kibana.alert.original_event.id' }, - dataFormattedForFieldBrowser - ); - - return hasData(sourceEventField); + getFieldsData, +}: ShowRelatedAlertsBySameSourceEventParams): ShowRelatedAlertsBySameSourceEventResult => { + const originalEventId = getField(getFieldsData(ORIGINAL_EVENT_ID)); + return { + show: originalEventId != null, + ...(originalEventId && { originalEventId }), + }; }; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_session.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_session.test.tsx index 740f52a59a49e0..32595a4c27c6db 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_session.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_session.test.tsx @@ -8,39 +8,29 @@ import type { RenderHookResult } from '@testing-library/react-hooks'; import { renderHook } from '@testing-library/react-hooks'; -import type { UseShowRelatedAlertsBySessionParams } from './use_show_related_alerts_by_session'; +import type { + UseShowRelatedAlertsBySessionParams, + UseShowRelatedAlertsBySessionResult, +} from './use_show_related_alerts_by_session'; import { useShowRelatedAlertsBySession } from './use_show_related_alerts_by_session'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; describe('useShowRelatedAlertsBySession', () => { - let hookResult: RenderHookResult; + let hookResult: RenderHookResult< + UseShowRelatedAlertsBySessionParams, + UseShowRelatedAlertsBySessionResult + >; - it('should return false if dataFormattedForFieldBrowser is null', () => { - const dataFormattedForFieldBrowser = null; - hookResult = renderHook(() => useShowRelatedAlertsBySession({ dataFormattedForFieldBrowser })); + it('should return false if getFieldsData returns null', () => { + const getFieldsData = () => null; + hookResult = renderHook(() => useShowRelatedAlertsBySession({ getFieldsData })); - expect(hookResult.result.current).toEqual(false); + expect(hookResult.result.current).toEqual({ show: false }); }); - it('should return false if dataFormattedForFieldBrowser is missing the correct field', () => { - const dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] = []; - hookResult = renderHook(() => useShowRelatedAlertsBySession({ dataFormattedForFieldBrowser })); + it('should return true if getFieldsData has the correct field', () => { + const getFieldsData = () => 'entity_id'; + hookResult = renderHook(() => useShowRelatedAlertsBySession({ getFieldsData })); - expect(hookResult.result.current).toEqual(false); - }); - - it('should return true if dataFormattedForFieldBrowser has the correct field', () => { - const dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] = [ - { - category: 'process', - field: 'process.entry_leader.entity_id', - isObjectArray: false, - originalValue: ['abc'], - values: ['abc'], - }, - ]; - hookResult = renderHook(() => useShowRelatedAlertsBySession({ dataFormattedForFieldBrowser })); - - expect(hookResult.result.current).toEqual(true); + expect(hookResult.result.current).toEqual({ show: true, entityId: 'entity_id' }); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_session.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_session.ts index cec04767f38857..04831584a64a78 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_session.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_show_related_alerts_by_session.ts @@ -5,27 +5,37 @@ * 2.0. */ -import { find } from 'lodash/fp'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { hasData } from '../../../common/components/event_details/insights/helpers'; +import { ENTRY_LEADER_ENTITY_ID } from '../constants/field_names'; +import type { GetFieldsData } from '../../../common/hooks/use_get_fields_data'; +import { getField } from '../utils'; export interface UseShowRelatedAlertsBySessionParams { /** - * An array of field objects with category and value + * Retrieves searchHit values for the provided field */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + getFieldsData: GetFieldsData; +} + +export interface UseShowRelatedAlertsBySessionResult { + /** + * Returns true if the document has process.entry_leader.entity_id field with values + */ + show: boolean; + /** + * Value of the process.entry_leader.entity_id field + */ + entityId?: string; } /** * Returns true if document has process.entry_leader.entity_id field with values */ export const useShowRelatedAlertsBySession = ({ - dataFormattedForFieldBrowser, -}: UseShowRelatedAlertsBySessionParams): boolean => { - const processSessionField = find( - { category: 'process', field: 'process.entry_leader.entity_id' }, - dataFormattedForFieldBrowser - ); - - return hasData(processSessionField); + getFieldsData, +}: UseShowRelatedAlertsBySessionParams): UseShowRelatedAlertsBySessionResult => { + const entityId = getField(getFieldsData(ENTRY_LEADER_ENTITY_ID)); + return { + show: entityId != null, + ...(entityId && { entityId }), + }; }; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_context.ts b/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_context.ts index 4dec259d001367..f1da095bfc1dc2 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_context.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/mocks/mock_context.ts @@ -11,6 +11,9 @@ export const mockDataAsNestedObject = { _id: '123', '@timestamp': ['2023-01-01T01:01:01.000Z'], + agent: { + type: ['endpoint'], + }, event: { category: ['malware'], kind: ['signal'], @@ -28,6 +31,7 @@ export const mockDataAsNestedObject = { }, process: { name: ['process-name'], + entity_id: ['process-entity_id'], }, }; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/translations.ts b/x-pack/plugins/security_solution/public/flyout/shared/translations.ts index a9a7aa6cd217cf..26e6db71d3857e 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/translations.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/translations.ts @@ -19,62 +19,26 @@ export const ERROR_MESSAGE = (message: string) => defaultMessage: 'There was an error displaying {message}', }); -export const CORRELATIONS_TEXT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.overviewTab.correlationsText', - { - defaultMessage: 'fields of correlation', - } -); - -export const CORRELATIONS_ANCESTRY_ALERT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.ancestryAlert', - { - defaultMessage: 'alert related by ancestry', - } -); - -export const CORRELATIONS_ANCESTRY_ALERTS = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.ancestryAlerts', - { - defaultMessage: 'alerts related by ancestry', - } -); -export const CORRELATIONS_SAME_SOURCE_EVENT_ALERT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSourceEventAlert', - { - defaultMessage: 'alert related by the same source event', - } -); +export const CORRELATIONS_ANCESTRY_ALERTS = (count: number) => + i18n.translate('xpack.securitySolution.flyout.documentDetails.correlations.ancestryAlerts', { + defaultMessage: '{count, plural, one {alert} other {alerts}} related by ancestry', + values: { count }, + }); -export const CORRELATIONS_SAME_SOURCE_EVENT_ALERTS = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSourceEventAlerts', - { - defaultMessage: 'alerts related by the same source event', - } -); -export const CORRELATIONS_SAME_SESSION_ALERT = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSessionAlert', - { - defaultMessage: 'alert related by session', - } -); +export const CORRELATIONS_SAME_SOURCE_ALERTS = (count: number) => + i18n.translate('xpack.securitySolution.flyout.documentDetails.correlations.sourceAlerts', { + defaultMessage: '{count, plural, one {alert} other {alerts}} related by source event', + values: { count }, + }); -export const CORRELATIONS_SAME_SESSION_ALERTS = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSessionAlerts', - { - defaultMessage: 'alerts related by session', - } -); -export const CORRELATIONS_RELATED_CASE = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.relatedCase', - { - defaultMessage: 'related case', - } -); +export const CORRELATIONS_SESSION_ALERTS = (count: number) => + i18n.translate('xpack.securitySolution.flyout.documentDetails.correlations.sessionAlerts', { + defaultMessage: '{count, plural, one {alert} other {alerts}} related by session', + values: { count }, + }); -export const CORRELATIONS_RELATED_CASES = i18n.translate( - 'xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.relatedCases', - { - defaultMessage: 'related cases', - } -); +export const CORRELATIONS_RELATED_CASES = (count: number) => + i18n.translate('xpack.securitySolution.flyout.documentDetails.correlations.relatedCases', { + defaultMessage: 'related {count, plural, one {case} other {cases}}', + values: { count }, + }); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 4cd9f70e35e3de..cd8546ef2bd867 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -33317,15 +33317,6 @@ "xpack.securitySolution.flyout.documentDetails.investigationsTab": "Investigation", "xpack.securitySolution.flyout.documentDetails.jsonTab": "JSON", "xpack.securitySolution.flyout.documentDetails.overviewTab": "Aperçu", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.ancestryAlert": "alerte associée par ancêtre", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.ancestryAlerts": "alertes associées par ancêtre", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.relatedCase": "cas associé", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.relatedCases": "cas associés", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSessionAlert": "alerte associée par session", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSessionAlerts": "alertes associées par session", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSourceEventAlert": "alerte associée par le même événement source", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSourceEventAlerts": "alertes associées par le même événement source", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlationsText": "champs de corrélation", "xpack.securitySolution.flyout.documentDetails.overviewTab.prevalenceRowText": "est inhabituel", "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatEnrichment": "champ enrichi avec la Threat Intelligence", "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatEnrichments": "champs enrichis avec la Threat Intelligence", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5f43c6cea4c96b..1b37b084d2a0fb 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -33316,15 +33316,6 @@ "xpack.securitySolution.flyout.documentDetails.investigationsTab": "調査", "xpack.securitySolution.flyout.documentDetails.jsonTab": "JSON", "xpack.securitySolution.flyout.documentDetails.overviewTab": "概要", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.ancestryAlert": "上位項目に関連するアラート", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.ancestryAlerts": "上位項目に関連するアラート", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.relatedCase": "関連するケース", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.relatedCases": "関連するケース", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSessionAlert": "セッションに関連するアラート", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSessionAlerts": "セッションに関連するアラート", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSourceEventAlert": "同じソースイベントに関連するアラート", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSourceEventAlerts": "同じソースイベントに関連するアラート", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlationsText": "相関のフィールド", "xpack.securitySolution.flyout.documentDetails.overviewTab.prevalenceRowText": "共通しない", "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatEnrichment": "脅威インテリジェンスで拡張されたフィールド", "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatEnrichments": "脅威インテリジェンスで拡張されたフィールド", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e3f52187565a69..07bf6d01e86e74 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -33312,15 +33312,6 @@ "xpack.securitySolution.flyout.documentDetails.investigationsTab": "调查", "xpack.securitySolution.flyout.documentDetails.jsonTab": "JSON", "xpack.securitySolution.flyout.documentDetails.overviewTab": "概览", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.ancestryAlert": "告警与体系相关", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.ancestryAlerts": "告警与体系相关", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.relatedCase": "相关案例", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.relatedCases": "相关案例", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSessionAlert": "告警与会话相关", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSessionAlerts": "告警与会话相关", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSourceEventAlert": "告警与同一源事件相关", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlations.sameSourceEventAlerts": "告警与同一源事件相关", - "xpack.securitySolution.flyout.documentDetails.overviewTab.correlationsText": "相关性字段", "xpack.securitySolution.flyout.documentDetails.overviewTab.prevalenceRowText": "不常见", "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatEnrichment": "已使用威胁情报扩充字段", "xpack.securitySolution.flyout.documentDetails.overviewTab.threatIntelligence.threatEnrichments": "已使用威胁情报扩充字段", diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts index b80e9cdd766468..fbe8495c74280e 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts @@ -8,21 +8,19 @@ import { tag } from '../../../../tags'; import { createRule } from '../../../../tasks/api_calls/rules'; import { getNewRule } from '../../../../objects/rule'; import { - CORRELATIONS_ANCESTRY_SECTION, - CORRELATIONS_ANCESTRY_TABLE, - CORRELATIONS_CASES_SECTION, - CORRELATIONS_SESSION_SECTION, - CORRELATIONS_SOURCE_SECTION, + CORRELATIONS_ANCESTRY_SECTION_TABLE, + CORRELATIONS_ANCESTRY_SECTION_TITLE, + CORRELATIONS_CASES_SECTION_TABLE, + CORRELATIONS_CASES_SECTION_TITLE, + CORRELATIONS_SESSION_SECTION_TABLE, + CORRELATIONS_SESSION_SECTION_TITLE, DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON, } from '../../../../screens/expandable_flyout/alert_details_left_panel_correlations_tab'; import { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB, DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP, } from '../../../../screens/expandable_flyout/alert_details_left_panel'; -import { - expandCorrelationsSection, - openCorrelationsTab, -} from '../../../../tasks/expandable_flyout/alert_details_left_panel_correlations_tab'; +import { openCorrelationsTab } from '../../../../tasks/expandable_flyout/alert_details_left_panel_correlations_tab'; import { openInsightsTab } from '../../../../tasks/expandable_flyout/alert_details_left_panel'; import { expandDocumentDetailsExpandableFlyoutLeftSection } from '../../../../tasks/expandable_flyout/alert_details_right_panel'; import { @@ -71,23 +69,30 @@ describe( cy.log('should render all the correlations sections'); - cy.get(CORRELATIONS_ANCESTRY_SECTION) + cy.get(CORRELATIONS_ANCESTRY_SECTION_TITLE).scrollIntoView(); + cy.get(CORRELATIONS_ANCESTRY_SECTION_TITLE) .should('be.visible') - .and('have.text', '1 alert related by ancestry'); + .and('contain.text', '1 alert related by ancestry'); + cy.get(CORRELATIONS_ANCESTRY_SECTION_TABLE).should('be.visible'); - cy.get(CORRELATIONS_SOURCE_SECTION) - .should('be.visible') - .and('have.text', '0 alerts related by source event'); + // TODO get proper data to test this section + // cy.get(CORRELATIONS_SOURCE_SECTION).scrollIntoView(); + // cy.get(CORRELATIONS_SOURCE_SECTION) + // .should('be.visible') + // .and('contain.text', '0 alerts related by source event'); + // cy.get(CORRELATIONS_SOURCE_SECTION_TABLE).should('be.visible'); - cy.get(CORRELATIONS_SESSION_SECTION) + cy.get(CORRELATIONS_SESSION_SECTION_TITLE).scrollIntoView(); + cy.get(CORRELATIONS_SESSION_SECTION_TITLE) .should('be.visible') - .and('have.text', '1 alert related by session'); - - cy.get(CORRELATIONS_CASES_SECTION).should('be.visible').and('have.text', '1 related case'); + .and('contain.text', '1 alert related by session'); + cy.get(CORRELATIONS_SESSION_SECTION_TABLE).should('be.visible'); - expandCorrelationsSection(CORRELATIONS_ANCESTRY_SECTION); - - cy.get(CORRELATIONS_ANCESTRY_TABLE).should('be.visible'); + cy.get(CORRELATIONS_CASES_SECTION_TITLE).scrollIntoView(); + cy.get(CORRELATIONS_CASES_SECTION_TITLE) + .should('be.visible') + .and('contain.text', '1 related case'); + cy.get(CORRELATIONS_CASES_SECTION_TABLE).should('be.visible'); }); } ); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts index 050463b70ae505..18605cb5ce5b4a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts @@ -24,7 +24,9 @@ import { DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_ALERTS_BY_ANCESTRY, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_ALERTS_BY_SESSION, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_CASES, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_CONTENT, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT, @@ -292,20 +294,20 @@ describe( .should('be.visible') .within(() => { // TODO the order in which these appear is not deterministic currently, hence this can cause flakiness - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) - .eq(0) + cy.get( + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_ALERTS_BY_ANCESTRY + ) .should('be.visible') .and('have.text', '1 alert related by ancestry'); - // cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) - // .eq(2) + // cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_ALERTS_BY_SAME_SOURCE_EVENT) // .should('be.visible') // .and('have.text', '1 alert related by the same source event'); // TODO work on getting proper data to display some same source data here - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) - .eq(2) + cy.get( + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_ALERTS_BY_SESSION + ) .should('be.visible') .and('have.text', '1 alert related by session'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) - .eq(1) + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_CASES) .should('be.visible') .and('have.text', '1 related case'); }); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_correlations_tab.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_correlations_tab.ts index 2e01659976b1eb..d145e13889fc35 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_correlations_tab.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_correlations_tab.ts @@ -8,33 +8,45 @@ import { INSIGHTS_TAB_CORRELATIONS_BUTTON_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/left/tabs/test_ids'; import { CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID, - CORRELATIONS_DETAILS_BY_ANCESTRY_TABLE_TEST_ID, CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID, CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID, CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID, } from '@kbn/security-solution-plugin/public/flyout/left/components/test_ids'; +import { EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/shared/components/test_ids'; import { getDataTestSubjectSelector } from '../../helpers/common'; export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON = getDataTestSubjectSelector( INSIGHTS_TAB_CORRELATIONS_BUTTON_TEST_ID ); -export const CORRELATIONS_ANCESTRY_SECTION = getDataTestSubjectSelector( - CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID +export const CORRELATIONS_ANCESTRY_SECTION_TITLE = getDataTestSubjectSelector( + EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID) ); -export const CORRELATIONS_SOURCE_SECTION = getDataTestSubjectSelector( - CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID +export const CORRELATIONS_ANCESTRY_SECTION_TABLE = getDataTestSubjectSelector( + `${CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID}Table` ); -export const CORRELATIONS_SESSION_SECTION = getDataTestSubjectSelector( - CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID +export const CORRELATIONS_SOURCE_SECTION_TITLE = getDataTestSubjectSelector( + EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID) ); -export const CORRELATIONS_CASES_SECTION = getDataTestSubjectSelector( - CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID +export const CORRELATIONS_SOURCE_SECTION_TABLE = getDataTestSubjectSelector( + `${CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID}Table` ); -export const CORRELATIONS_ANCESTRY_TABLE = getDataTestSubjectSelector( - CORRELATIONS_DETAILS_BY_ANCESTRY_TABLE_TEST_ID +export const CORRELATIONS_SESSION_SECTION_TITLE = getDataTestSubjectSelector( + EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID) +); + +export const CORRELATIONS_SESSION_SECTION_TABLE = getDataTestSubjectSelector( + `${CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID}Table` +); + +export const CORRELATIONS_CASES_SECTION_TITLE = getDataTestSubjectSelector( + EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID) +); + +export const CORRELATIONS_CASES_SECTION_TABLE = getDataTestSubjectSelector( + `${CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID}Table` ); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_entities_tab.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_entities_tab.ts index 20e8940aa54883..5374943cce29c3 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_entities_tab.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_entities_tab.ts @@ -11,6 +11,7 @@ import { USER_DETAILS_TEST_ID, } from '@kbn/security-solution-plugin/public/flyout/left/components/test_ids'; import { INSIGHTS_TAB_ENTITIES_BUTTON_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/left/tabs/test_ids'; +import { EXPANDABLE_PANEL_CONTENT_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/shared/components/test_ids'; import { getDataTestSubjectSelector } from '../../helpers/common'; export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON = getDataTestSubjectSelector( @@ -19,7 +20,9 @@ export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON = getDataTestS export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT = getDataTestSubjectSelector(ENTITIES_DETAILS_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS = - getDataTestSubjectSelector(USER_DETAILS_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS = - getDataTestSubjectSelector(HOST_DETAILS_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS = getDataTestSubjectSelector( + EXPANDABLE_PANEL_CONTENT_TEST_ID(USER_DETAILS_TEST_ID) +); +export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS = getDataTestSubjectSelector( + EXPANDABLE_PANEL_CONTENT_TEST_ID(HOST_DETAILS_TEST_ID) +); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts index ebfb40a3091f5f..2199dc6d8b55da 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts @@ -5,6 +5,10 @@ * 2.0. */ +import { + EXPANDABLE_PANEL_CONTENT_TEST_ID, + EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID, +} from '@kbn/security-solution-plugin/public/flyout/shared/components/test_ids'; import { ABOUT_SECTION_CONTENT_TEST_ID, ABOUT_SECTION_HEADER_TEST_ID, @@ -13,10 +17,7 @@ import { RULE_SUMMARY_BUTTON_TEST_ID, HIGHLIGHTED_FIELDS_DETAILS_TEST_ID, HIGHLIGHTED_FIELDS_TITLE_TEST_ID, - INSIGHTS_CORRELATIONS_CONTENT_TEST_ID, INSIGHTS_HEADER_TEST_ID, - INSIGHTS_PREVALENCE_CONTENT_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_CONTENT_TEST_ID, INVESTIGATION_GUIDE_BUTTON_TEST_ID, INVESTIGATION_SECTION_CONTENT_TEST_ID, INVESTIGATION_SECTION_HEADER_TEST_ID, @@ -28,16 +29,17 @@ import { HIGHLIGHTED_FIELDS_CELL_TEST_ID, RESPONSE_SECTION_HEADER_TEST_ID, RESPONSE_EMPTY_TEST_ID, - INSIGHTS_ENTITIES_TITLE_LINK_TEST_ID, - INSIGHTS_ENTITIES_CONTENT_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_TITLE_LINK_TEST_ID, - INSIGHTS_CORRELATIONS_TITLE_LINK_TEST_ID, - INSIGHTS_PREVALENCE_TITLE_LINK_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_VALUE_TEST_ID, - INSIGHTS_CORRELATIONS_VALUE_TEST_ID, - ANALYZER_PREVIEW_CONTENT_TEST_ID, - SESSION_PREVIEW_CONTENT_TEST_ID, - INSIGHTS_PREVALENCE_VALUE_TEST_ID, + INSIGHTS_THREAT_INTELLIGENCE_TEST_ID, + INSIGHTS_CORRELATIONS_TEST_ID, + INSIGHTS_PREVALENCE_TEST_ID, + ANALYZER_PREVIEW_TEST_ID, + SUMMARY_ROW_VALUE_TEST_ID, + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID, + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID, + INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID, + INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID, + INSIGHTS_ENTITIES_TEST_ID, + SESSION_PREVIEW_TEST_ID, REASON_DETAILS_PREVIEW_BUTTON_TEST_ID, } from '@kbn/security-solution-plugin/public/flyout/right/components/test_ids'; import { getDataTestSubjectSelector } from '../../helpers/common'; @@ -95,45 +97,67 @@ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_SECTION_HEADER = /* Insights Entities */ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER = - getDataTestSubjectSelector(INSIGHTS_ENTITIES_TITLE_LINK_TEST_ID); + getDataTestSubjectSelector(EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_ENTITIES_TEST_ID)); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_CONTENT = - getDataTestSubjectSelector(INSIGHTS_ENTITIES_CONTENT_TEST_ID); + getDataTestSubjectSelector(EXPANDABLE_PANEL_CONTENT_TEST_ID(INSIGHTS_ENTITIES_TEST_ID)); /* Insights Threat Intelligence */ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER = - getDataTestSubjectSelector(INSIGHTS_THREAT_INTELLIGENCE_TITLE_LINK_TEST_ID); + getDataTestSubjectSelector( + EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_THREAT_INTELLIGENCE_TEST_ID) + ); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_CONTENT = - getDataTestSubjectSelector(INSIGHTS_THREAT_INTELLIGENCE_CONTENT_TEST_ID); + getDataTestSubjectSelector( + EXPANDABLE_PANEL_CONTENT_TEST_ID(INSIGHTS_THREAT_INTELLIGENCE_TEST_ID) + ); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES = - getDataTestSubjectSelector(INSIGHTS_THREAT_INTELLIGENCE_VALUE_TEST_ID); + getDataTestSubjectSelector(SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_THREAT_INTELLIGENCE_TEST_ID)); /* Insights Correlations */ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER = - getDataTestSubjectSelector(INSIGHTS_CORRELATIONS_TITLE_LINK_TEST_ID); + getDataTestSubjectSelector( + EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_CORRELATIONS_TEST_ID) + ); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT = - getDataTestSubjectSelector(INSIGHTS_CORRELATIONS_CONTENT_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES = - getDataTestSubjectSelector(INSIGHTS_CORRELATIONS_VALUE_TEST_ID); + getDataTestSubjectSelector(EXPANDABLE_PANEL_CONTENT_TEST_ID(INSIGHTS_CORRELATIONS_TEST_ID)); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_ALERTS_BY_ANCESTRY = + getDataTestSubjectSelector( + SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID) + ); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_ALERTS_BY_SAME_SOURCE_EVENT = + getDataTestSubjectSelector( + SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID) + ); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_ALERTS_BY_SESSION = + getDataTestSubjectSelector( + SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID) + ); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_CASES = + getDataTestSubjectSelector( + SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID) + ); /* Insights Prevalence */ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER = - getDataTestSubjectSelector(INSIGHTS_PREVALENCE_TITLE_LINK_TEST_ID); + getDataTestSubjectSelector( + EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID) + ); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT = - getDataTestSubjectSelector(INSIGHTS_PREVALENCE_CONTENT_TEST_ID); + getDataTestSubjectSelector(EXPANDABLE_PANEL_CONTENT_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID)); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VALUES = - getDataTestSubjectSelector(INSIGHTS_PREVALENCE_VALUE_TEST_ID); + getDataTestSubjectSelector(INSIGHTS_PREVALENCE_TEST_ID); /* Visualization section */ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_VISUALIZATIONS_SECTION_HEADER = getDataTestSubjectSelector(VISUALIZATIONS_SECTION_HEADER_TEST_ID); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_PREVIEW_CONTENT = - getDataTestSubjectSelector(ANALYZER_PREVIEW_CONTENT_TEST_ID); + getDataTestSubjectSelector(EXPANDABLE_PANEL_CONTENT_TEST_ID(ANALYZER_PREVIEW_TEST_ID)); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW_CONTENT = - getDataTestSubjectSelector(SESSION_PREVIEW_CONTENT_TEST_ID); + getDataTestSubjectSelector(EXPANDABLE_PANEL_CONTENT_TEST_ID(SESSION_PREVIEW_TEST_ID)); /* Response section */ diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts b/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts index b94a8be090fa40..3dfc2dc4e9a302 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts @@ -5,11 +5,12 @@ * 2.0. */ +import { EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/shared/components/test_ids'; import { - INSIGHTS_CORRELATIONS_TITLE_LINK_TEST_ID, - INSIGHTS_ENTITIES_TITLE_LINK_TEST_ID, - INSIGHTS_PREVALENCE_TITLE_LINK_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_TITLE_LINK_TEST_ID, + INSIGHTS_CORRELATIONS_TEST_ID, + INSIGHTS_ENTITIES_TEST_ID, + INSIGHTS_PREVALENCE_TEST_ID, + INSIGHTS_THREAT_INTELLIGENCE_TEST_ID, } from '@kbn/security-solution-plugin/public/flyout/right/components/test_ids'; import { DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ABOUT_SECTION_HEADER, @@ -60,32 +61,36 @@ export const toggleOverviewTabInsightsSection = () => { * Click on the header in the right section, Insights, Entities */ export const navigateToEntitiesDetails = () => { - cy.get(INSIGHTS_ENTITIES_TITLE_LINK_TEST_ID).scrollIntoView(); - cy.get(INSIGHTS_ENTITIES_TITLE_LINK_TEST_ID).should('be.visible').click(); + const TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_ENTITIES_TEST_ID); + cy.get(TEST_ID).scrollIntoView(); + cy.get(TEST_ID).should('be.visible').click(); }; /** * Click on the header in the right section, Insights, Threat Intelligence */ export const navigateToThreatIntelligenceDetails = () => { - cy.get(INSIGHTS_THREAT_INTELLIGENCE_TITLE_LINK_TEST_ID).scrollIntoView(); - cy.get(INSIGHTS_THREAT_INTELLIGENCE_TITLE_LINK_TEST_ID).should('be.visible').click(); + const TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_THREAT_INTELLIGENCE_TEST_ID); + cy.get(TEST_ID).scrollIntoView(); + cy.get(TEST_ID).should('be.visible').click(); }; /** * Click on the header in the right section, Insights, Correlations */ export const navigateToCorrelationsDetails = () => { - cy.get(INSIGHTS_CORRELATIONS_TITLE_LINK_TEST_ID).scrollIntoView(); - cy.get(INSIGHTS_CORRELATIONS_TITLE_LINK_TEST_ID).should('be.visible').click(); + const TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_CORRELATIONS_TEST_ID); + cy.get(TEST_ID).scrollIntoView(); + cy.get(TEST_ID).should('be.visible').click(); }; /** * Click on the view all button under the right section, Insights, Prevalence */ export const navigateToPrevalenceDetails = () => { - cy.get(INSIGHTS_PREVALENCE_TITLE_LINK_TEST_ID).scrollIntoView(); - cy.get(INSIGHTS_PREVALENCE_TITLE_LINK_TEST_ID).should('be.visible').click(); + const TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(INSIGHTS_PREVALENCE_TEST_ID); + cy.get(TEST_ID).scrollIntoView(); + cy.get(TEST_ID).should('be.visible').click(); }; /* Visualizations section */