From 170024dcd41c210ba02dbf9ae5e6cbb3d6ee12cd Mon Sep 17 00:00:00 2001 From: Philippe Oberti Date: Wed, 13 Sep 2023 17:42:43 +0200 Subject: [PATCH] Expandable flyout context (#165662) --- .../public/flyout/isolate_host/context.tsx | 48 ++---- .../left/components/analyze_graph.test.tsx | 19 +-- .../flyout/left/components/analyze_graph.tsx | 17 +- .../components/investigation_guide.test.tsx | 19 +-- .../left/components/investigation_guide.tsx | 6 +- .../components/prevalence_details.test.tsx | 66 +------- .../left/components/prevalence_details.tsx | 21 +-- .../left/components/suppressed_alerts.tsx | 2 +- .../flyout/left/components/translations.ts | 7 - .../public/flyout/left/context.tsx | 136 ++++++++-------- .../use_threat_intelligence_details.test.ts | 16 +- .../flyout/left/tabs/investigation_tab.tsx | 6 - .../components/alert_reason_preview.test.tsx | 13 -- .../components/alert_reason_preview.tsx | 9 +- .../public/flyout/preview/context.tsx | 88 +++++------ .../analyzer_preview_container.test.tsx | 1 - .../components/analyzer_preview_container.tsx | 2 +- .../right/components/description.test.tsx | 13 +- .../flyout/right/components/description.tsx | 4 - .../components/entities_overview.test.tsx | 51 ------ .../right/components/entities_overview.tsx | 4 - .../components/highlighted_fields.test.tsx | 20 --- .../right/components/highlighted_fields.tsx | 2 +- .../components/insights_section.test.tsx | 3 +- .../components/investigation_guide.test.tsx | 20 +-- .../right/components/investigation_guide.tsx | 6 +- .../components/investigation_section.test.tsx | 5 +- .../components/prevalence_overview.test.tsx | 48 ------ .../right/components/prevalence_overview.tsx | 14 +- .../flyout/right/components/reason.test.tsx | 11 -- .../public/flyout/right/components/reason.tsx | 4 - .../session_preview_container.test.tsx | 1 - .../public/flyout/right/components/status.tsx | 2 +- .../threat_intelligence_overview.test.tsx | 34 +--- .../threat_intelligence_overview.tsx | 15 +- .../public/flyout/right/context.tsx | 148 ++++++++---------- .../public/flyout/right/footer.tsx | 4 - .../flyout/right/hooks/use_assistant.ts | 2 +- .../use_fetch_threat_intelligence.test.tsx | 26 --- .../hooks/use_fetch_threat_intelligence.ts | 7 +- .../right/mocks/mock_right_panel_context.ts | 13 +- .../flyout/right/tabs/json_tab.test.tsx | 20 +-- .../public/flyout/right/tabs/json_tab.tsx | 16 -- .../flyout/right/tabs/table_tab.test.tsx | 62 +------- .../public/flyout/right/tabs/table_tab.tsx | 16 -- .../shared/components/flyout_error.test.tsx | 21 +++ .../flyout/shared/components/flyout_error.tsx | 28 ++++ .../shared/components/flyout_loading.test.tsx | 18 +++ .../shared/components/flyout_loading.tsx | 27 ++++ .../shared/hooks/use_event_details.test.tsx | 51 ++++++ .../flyout/shared/hooks/use_event_details.ts | 104 ++++++++++++ .../shared/hooks/use_highlighted_fields.ts | 4 +- .../shared/hooks/use_investigation_guide.ts | 2 +- .../flyout/shared/hooks/use_prevalence.ts | 2 +- .../use_show_related_alerts_by_ancestry.ts | 8 +- .../public/flyout/shared/test_ids.ts | 13 ++ .../public/flyout/shared/translations.ts | 4 + .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../timelines/discover/cell_actions.cy.ts | 2 +- 61 files changed, 502 insertions(+), 832 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/flyout/shared/components/flyout_error.test.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/shared/components/flyout_error.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.test.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.test.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.ts create mode 100644 x-pack/plugins/security_solution/public/flyout/shared/test_ids.ts diff --git a/x-pack/plugins/security_solution/public/flyout/isolate_host/context.tsx b/x-pack/plugins/security_solution/public/flyout/isolate_host/context.tsx index 6451437646a53..17d31ebd002af 100644 --- a/x-pack/plugins/security_solution/public/flyout/isolate_host/context.tsx +++ b/x-pack/plugins/security_solution/public/flyout/isolate_host/context.tsx @@ -6,17 +6,11 @@ */ import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { css } from '@emotion/react'; import React, { createContext, memo, useContext, useMemo } from 'react'; -import { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; -import { useTimelineEventsDetails } from '../../timelines/containers/details'; -import { getAlertIndexAlias } from '../../timelines/components/side_panel/event_details/helpers'; -import { useSpaceId } from '../../common/hooks/use_space_id'; -import { useRouteSpy } from '../../common/utils/route/use_route_spy'; -import { SecurityPageName } from '../../../common/constants'; -import { SourcererScopeName } from '../../common/store/sourcerer/model'; -import { useSourcererDataView } from '../../common/containers/sourcerer'; +import { useEventDetails } from '../shared/hooks/use_event_details'; +import { FlyoutError } from '../shared/components/flyout_error'; +import { FlyoutLoading } from '../shared/components/flyout_loading'; import type { IsolateHostPanelProps } from '.'; export interface IsolateHostPanelContext { @@ -35,7 +29,7 @@ export interface IsolateHostPanelContext { /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; /** * Isolate action, either 'isolateHost' or 'unisolateHost' */ @@ -55,26 +49,11 @@ export type IsolateHostPanelProviderProps = { export const IsolateHostPanelProvider = memo( ({ id, indexName, scopeId, isolateAction, children }: IsolateHostPanelProviderProps) => { - const currentSpaceId = useSpaceId(); - // TODO Replace getAlertIndexAlias way to retrieving the eventIndex with the GET /_alias - // https://github.com/elastic/kibana/issues/113063 - const eventIndex = indexName ? getAlertIndexAlias(indexName, currentSpaceId) ?? indexName : ''; - const [{ pageName }] = useRouteSpy(); - const sourcererScope = - pageName === SecurityPageName.detections - ? SourcererScopeName.detections - : SourcererScopeName.default; - const sourcererDataView = useSourcererDataView(sourcererScope); - const [loading, dataFormattedForFieldBrowser] = useTimelineEventsDetails({ - indexName: eventIndex, - eventId: id ?? '', - runtimeMappings: sourcererDataView.runtimeMappings, - skip: !id, - }); + const { dataFormattedForFieldBrowser, loading } = useEventDetails({ eventId: id, indexName }); const contextValue = useMemo( () => - id && indexName && scopeId && isolateAction + id && indexName && scopeId && isolateAction && dataFormattedForFieldBrowser ? { eventId: id, indexName, @@ -87,16 +66,11 @@ export const IsolateHostPanelProvider = memo( ); if (loading) { - return ( - - - - ); + return ; + } + + if (!contextValue) { + return ; } return ( diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.test.tsx index 45710eb518428..d0a18279805cd 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.test.tsx @@ -11,7 +11,7 @@ import '@testing-library/jest-dom'; import { LeftPanelContext } from '../context'; import { TestProviders } from '../../../common/mock'; import { AnalyzeGraph } from './analyze_graph'; -import { ANALYZE_GRAPH_ERROR_TEST_ID, ANALYZER_GRAPH_TEST_ID } from './test_ids'; +import { ANALYZER_GRAPH_TEST_ID } from './test_ids'; jest.mock('react-router-dom', () => { const actual = jest.requireActual('react-router-dom'); @@ -45,21 +45,4 @@ describe('', () => { ); expect(wrapper.getByTestId(ANALYZER_GRAPH_TEST_ID)).toBeInTheDocument(); }); - - it('should render error message on null eventId', () => { - const contextValue = { - eventId: null, - } as unknown as LeftPanelContext; - - const wrapper = render( - - - - - - ); - expect(wrapper.getByTestId(ANALYZE_GRAPH_ERROR_TEST_ID)).toBeInTheDocument(); - expect(wrapper.getByText('Unable to display analyzer')).toBeInTheDocument(); - expect(wrapper.getByText('There was an error displaying analyzer')).toBeInTheDocument(); - }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.tsx index 63a75bd163d37..eee22ea2f36fa 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/analyze_graph.tsx @@ -7,14 +7,11 @@ import type { FC } from 'react'; import React, { useMemo } from 'react'; -import { EuiEmptyPrompt } from '@elastic/eui'; -import { ANALYZER_ERROR_MESSAGE } from './translations'; import { useLeftPanelContext } from '../context'; -import { ANALYZE_GRAPH_ERROR_TEST_ID, ANALYZER_GRAPH_TEST_ID } from './test_ids'; +import { ANALYZER_GRAPH_TEST_ID } from './test_ids'; import { Resolver } from '../../../resolver/view'; import { useTimelineDataFilters } from '../../../timelines/containers/use_timeline_data_filters'; -import { ERROR_TITLE, ERROR_MESSAGE } from '../../shared/translations'; import { isActiveTimeline } from '../../../helpers'; export const ANALYZE_GRAPH_ID = 'analyze_graph'; @@ -30,18 +27,6 @@ export const AnalyzeGraph: FC = () => { ); const filters = useMemo(() => ({ from, to }), [from, to]); - if (!eventId) { - return ( - {ERROR_TITLE(ANALYZER_ERROR_MESSAGE)}} - body={

{ERROR_MESSAGE(ANALYZER_ERROR_MESSAGE)}

} - data-test-subj={ANALYZE_GRAPH_ERROR_TEST_ID} - /> - ); - } - return (
', () => { expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument(); }); - it('should render null when dataFormattedForFieldBrowser is null', () => { - const mockContext = { - ...mockContextValue, - dataFormattedForFieldBrowser: null, - }; - (useInvestigationGuide as jest.Mock).mockReturnValue({ - loading: false, - error: false, - }); - const { container } = render(renderInvestigationGuide(mockContext)); - expect(container).toBeEmptyDOMElement(); - }); - - it('should render null useInvestigationGuide errors out', () => { + it('should render no data message when useInvestigationGuide errors out', () => { (useInvestigationGuide as jest.Mock).mockReturnValue({ loading: false, error: true, }); - const { container } = render(renderInvestigationGuide()); - expect(container).toBeEmptyDOMElement(); + const { getByTestId } = render(renderInvestigationGuide()); + expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.tsx index 2d0fe038f3c63..8cf39210bbac7 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.tsx @@ -26,10 +26,6 @@ export const InvestigationGuide: React.FC = () => { dataFormattedForFieldBrowser, }); - if (!dataFormattedForFieldBrowser || error) { - return null; - } - if (loading) { return ( { return ( <> - {basicAlertData.ruleId && ruleNote ? ( + {!error && basicAlertData.ruleId && ruleNote ? ( { expect(getByTestId(PREVALENCE_DETAILS_LOADING_TEST_ID)).toBeInTheDocument(); }); - it('should render error if call errors out', () => { + it('should render no data message if call errors out', () => { (usePrevalence as jest.Mock).mockReturnValue({ loading: false, error: true, @@ -231,66 +231,6 @@ describe('PrevalenceDetails', () => { ); - expect(getByTestId(PREVALENCE_DETAILS_TABLE_ERROR_TEST_ID)).toBeInTheDocument(); - }); - - it('should render error if event is null', () => { - const contextValue = { - ...panelContextValue, - eventId: null, - } as unknown as LeftPanelContext; - (usePrevalence as jest.Mock).mockReturnValue({ - loading: false, - error: true, - data: [], - }); - - const { getByTestId } = render( - - - - ); - - expect(getByTestId(PREVALENCE_DETAILS_TABLE_ERROR_TEST_ID)).toBeInTheDocument(); - }); - - it('should render error if dataFormattedForFieldBrowser is null', () => { - const contextValue = { - ...panelContextValue, - dataFormattedForFieldBrowser: null, - }; - (usePrevalence as jest.Mock).mockReturnValue({ - loading: false, - error: true, - data: [], - }); - - const { getByTestId } = render( - - - - ); - - expect(getByTestId(PREVALENCE_DETAILS_TABLE_ERROR_TEST_ID)).toBeInTheDocument(); - }); - - it('should render error if browserFields is null', () => { - const contextValue = { - ...panelContextValue, - browserFields: null, - }; - (usePrevalence as jest.Mock).mockReturnValue({ - loading: false, - error: true, - data: [], - }); - - const { getByTestId } = render( - - - - ); - - expect(getByTestId(PREVALENCE_DETAILS_TABLE_ERROR_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(`${PREVALENCE_DETAILS_TABLE_NO_DATA_TEST_ID}Error`)).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx index c58c138264ef5..cd4283613a453 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx @@ -10,7 +10,6 @@ import React, { useMemo, useState } from 'react'; import type { EuiBasicTableColumn, OnTimeChangeProps } from '@elastic/eui'; import { EuiCallOut, - EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, @@ -28,10 +27,8 @@ import { useLicense } from '../../../common/hooks/use_license'; import { InvestigateInTimelineButton } from '../../../common/components/event_details/table/investigate_in_timeline_button'; import type { PrevalenceData } from '../../shared/hooks/use_prevalence'; import { usePrevalence } from '../../shared/hooks/use_prevalence'; -import { ERROR_MESSAGE, ERROR_TITLE } from '../../shared/translations'; import { HOST_TITLE, - PREVALENCE_ERROR_MESSAGE, PREVALENCE_TABLE_ALERT_COUNT_COLUMN_TITLE, PREVALENCE_TABLE_COUNT_COLUMN_TITLE, PREVALENCE_TABLE_DOC_COUNT_COLUMN_TITLE, @@ -49,7 +46,6 @@ import { PREVALENCE_DETAILS_LOADING_TEST_ID, PREVALENCE_DETAILS_TABLE_ALERT_COUNT_CELL_TEST_ID, PREVALENCE_DETAILS_TABLE_DOC_COUNT_CELL_TEST_ID, - PREVALENCE_DETAILS_TABLE_ERROR_TEST_ID, PREVALENCE_DETAILS_TABLE_HOST_PREVALENCE_CELL_TEST_ID, PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID, PREVALENCE_DETAILS_TABLE_FIELD_CELL_TEST_ID, @@ -208,8 +204,7 @@ const columns: Array> = [ * Prevalence table displayed in the document details expandable flyout left section under the Insights tab */ export const PrevalenceDetails: React.FC = () => { - const { browserFields, dataFormattedForFieldBrowser, eventId, investigationFields } = - useLeftPanelContext(); + const { dataFormattedForFieldBrowser, investigationFields } = useLeftPanelContext(); const isPlatinumPlus = useLicense().isPlatinumPlus(); @@ -273,18 +268,6 @@ export const PrevalenceDetails: React.FC = () => { ); } - if (!eventId || !dataFormattedForFieldBrowser || !browserFields || error) { - return ( - {ERROR_TITLE(PREVALENCE_ERROR_MESSAGE)}} - body={

{ERROR_MESSAGE(PREVALENCE_ERROR_MESSAGE)}

} - data-test-subj={PREVALENCE_DETAILS_TABLE_ERROR_TEST_ID} - /> - ); - } - const upsell = ( <> @@ -309,7 +292,7 @@ export const PrevalenceDetails: React.FC = () => { return ( <> - {!isPlatinumPlus && upsell} + {!error && !isPlatinumPlus && upsell} values: { hostName }, }); -export const PREVALENCE_ERROR_MESSAGE = i18n.translate( - 'xpack.securitySolution.flyout.prevalenceErrorMessage', - { - defaultMessage: 'prevalence', - } -); - export const PREVALENCE_NO_DATA_MESSAGE = i18n.translate( 'xpack.securitySolution.flyout.prevalenceNoDataMessage', { diff --git a/x-pack/plugins/security_solution/public/flyout/left/context.tsx b/x-pack/plugins/security_solution/public/flyout/left/context.tsx index 15de2dfc9a78d..a791dcbf5fb5a 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/context.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/context.tsx @@ -6,24 +6,15 @@ */ import type { BrowserFields, TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { css } from '@emotion/react'; -import React, { createContext, useContext, useMemo } from 'react'; -import { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import React, { createContext, memo, useContext, useMemo } from 'react'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { useEventDetails } from '../shared/hooks/use_event_details'; +import { FlyoutError } from '../shared/components/flyout_error'; +import { FlyoutLoading } from '../shared/components/flyout_loading'; import type { SearchHit } from '../../../common/search_strategy'; import type { LeftPanelProps } from '.'; import type { GetFieldsData } from '../../common/hooks/use_get_fields_data'; -import { useGetFieldsData } from '../../common/hooks/use_get_fields_data'; -import { useTimelineEventsDetails } from '../../timelines/containers/details'; -import { - getAlertIndexAlias, - useBasicDataFromDetailsData, -} from '../../timelines/components/side_panel/event_details/helpers'; -import { useSpaceId } from '../../common/hooks/use_space_id'; -import { useRouteSpy } from '../../common/utils/route/use_route_spy'; -import { SecurityPageName } from '../../../common/constants'; -import { SourcererScopeName } from '../../common/store/sourcerer/model'; -import { useSourcererDataView } from '../../common/containers/sourcerer'; +import { useBasicDataFromDetailsData } from '../../timelines/components/side_panel/event_details/helpers'; import { useRuleWithFallback } from '../../detection_engine/rule_management/logic/use_rule_with_fallback'; export interface LeftPanelContext { @@ -42,19 +33,19 @@ export interface LeftPanelContext { /** * An object containing fields by type */ - browserFields: BrowserFields | null; + browserFields: BrowserFields; /** * An object with top level fields from the ECS object */ - dataAsNestedObject: Ecs | null; + dataAsNestedObject: Ecs; /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; /** * The actual raw document object */ - searchHit: SearchHit | undefined; + searchHit: SearchHit; /** * User defined fields to highlight (defined on the rule) */ @@ -74,69 +65,66 @@ export type LeftPanelProviderProps = { children: React.ReactNode; } & Partial; -export const LeftPanelProvider = ({ id, indexName, scopeId, children }: LeftPanelProviderProps) => { - const currentSpaceId = useSpaceId(); - const eventIndex = indexName ? getAlertIndexAlias(indexName, currentSpaceId) ?? indexName : ''; - const [{ pageName }] = useRouteSpy(); - const sourcererScope = - pageName === SecurityPageName.detections - ? SourcererScopeName.detections - : SourcererScopeName.default; - const sourcererDataView = useSourcererDataView(sourcererScope); - const [loading, dataFormattedForFieldBrowser, searchHit, dataAsNestedObject] = - useTimelineEventsDetails({ - indexName: eventIndex, - eventId: id ?? '', - runtimeMappings: sourcererDataView.runtimeMappings, - skip: !id, - }); - const getFieldsData = useGetFieldsData(searchHit?.fields); - const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); - const { rule: maybeRule } = useRuleWithFallback(ruleId); - - const contextValue = useMemo( - () => - id && indexName && scopeId - ? { - eventId: id, - indexName, - scopeId, - browserFields: sourcererDataView.browserFields, - dataAsNestedObject, - dataFormattedForFieldBrowser, - searchHit, - investigationFields: maybeRule?.investigation_fields?.field_names ?? [], - getFieldsData, - } - : undefined, - [ - id, - indexName, - scopeId, - sourcererDataView.browserFields, +export const LeftPanelProvider = memo( + ({ id, indexName, scopeId, children }: LeftPanelProviderProps) => { + const { + browserFields, dataAsNestedObject, dataFormattedForFieldBrowser, - searchHit, - maybeRule?.investigation_fields, getFieldsData, - ] - ); + loading, + searchHit, + } = useEventDetails({ eventId: id, indexName }); + + const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); + const { rule: maybeRule } = useRuleWithFallback(ruleId); - if (loading) { - return ( - - - + const contextValue = useMemo( + () => + id && + indexName && + scopeId && + dataAsNestedObject && + dataFormattedForFieldBrowser && + searchHit + ? { + eventId: id, + indexName, + scopeId, + browserFields, + dataAsNestedObject, + dataFormattedForFieldBrowser, + searchHit, + investigationFields: maybeRule?.investigation_fields?.field_names ?? [], + getFieldsData, + } + : undefined, + [ + id, + indexName, + scopeId, + browserFields, + dataAsNestedObject, + dataFormattedForFieldBrowser, + searchHit, + maybeRule?.investigation_fields, + getFieldsData, + ] ); + + if (loading) { + return ; + } + + if (!contextValue) { + return ; + } + + return {children}; } +); - return {children}; -}; +LeftPanelProvider.displayName = 'LeftPanelProvider'; export const useLeftPanelContext = () => { const contextValue = useContext(LeftPanelContext); diff --git a/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.test.ts b/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.test.ts index 1e92ff0a6bd45..14319c8fa4404 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.test.ts +++ b/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.test.ts @@ -19,6 +19,7 @@ import { type GetBasicDataFromDetailsData, useBasicDataFromDetailsData, } from '../../../timelines/components/side_panel/event_details/helpers'; +import { mockContextValue } from '../mocks/mock_context'; jest.mock('../../../timelines/containers/details'); jest.mock('../../../common/containers/sourcerer'); @@ -63,20 +64,7 @@ describe('useThreatIntelligenceDetails', () => { () => {}, ]); - jest.mocked(useLeftPanelContext).mockReturnValue({ - indexName: 'test-index', - eventId: 'test-event-id', - getFieldsData: () => null, - dataFormattedForFieldBrowser: null, - scopeId: 'test-scope-id', - browserFields: null, - searchHit: { - _id: 'testId', - _index: 'testIndex', - }, - dataAsNestedObject: null, - investigationFields: [], - }); + jest.mocked(useLeftPanelContext).mockReturnValue(mockContextValue); }); afterEach(() => { diff --git a/x-pack/plugins/security_solution/public/flyout/left/tabs/investigation_tab.tsx b/x-pack/plugins/security_solution/public/flyout/left/tabs/investigation_tab.tsx index ce0629d790b2b..f52c25cf586d9 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/tabs/investigation_tab.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/tabs/investigation_tab.tsx @@ -9,17 +9,11 @@ import React, { memo } from 'react'; import { EuiPanel } from '@elastic/eui'; import { InvestigationGuide } from '../components/investigation_guide'; import { INVESTIGATION_TAB_CONTENT_TEST_ID } from './test_ids'; -import { useLeftPanelContext } from '../context'; /** * Investigations view displayed in the document details expandable flyout left section */ export const InvestigationTab: React.FC = memo(() => { - const { dataFormattedForFieldBrowser } = useLeftPanelContext(); - if (dataFormattedForFieldBrowser == null) { - return null; - } - return ( diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx index 30076fd3ca1d2..f84983e5ce13b 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx @@ -31,17 +31,4 @@ describe('', () => { ); expect(getByTestId(ALERT_REASON_PREVIEW_BODY_TEST_ID)).toBeInTheDocument(); }); - - it('should render null is dataAsNestedObject is null', () => { - const contextValue = { - ...mockContextValue, - dataAsNestedObject: null, - }; - const { queryByTestId } = render( - - - - ); - expect(queryByTestId(ALERT_REASON_PREVIEW_BODY_TEST_ID)).not.toBeInTheDocument(); - }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx index 985c2bb288fa2..e7460c1042788 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx @@ -29,16 +29,13 @@ export const AlertReasonPreview: React.FC = () => { const { dataAsNestedObject, scopeId } = usePreviewPanelContext(); const renderer = useMemo( - () => - dataAsNestedObject != null - ? getRowRenderer({ data: dataAsNestedObject, rowRenderers: defaultRowRenderers }) - : null, + () => getRowRenderer({ data: dataAsNestedObject, rowRenderers: defaultRowRenderers }), [dataAsNestedObject] ); const rowRenderer = useMemo( () => - renderer && dataAsNestedObject + renderer ? renderer.renderRow({ contextId: 'event-details', data: dataAsNestedObject, @@ -49,7 +46,7 @@ export const AlertReasonPreview: React.FC = () => { [renderer, dataAsNestedObject, scopeId] ); - if (!dataAsNestedObject || !renderer) { + if (!renderer) { return null; } diff --git a/x-pack/plugins/security_solution/public/flyout/preview/context.tsx b/x-pack/plugins/security_solution/public/flyout/preview/context.tsx index 005ef1dcdb258..c99fbbd0456b9 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/context.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/context.tsx @@ -5,17 +5,13 @@ * 2.0. */ -import React, { createContext, useContext, useMemo } from 'react'; +import React, { createContext, memo, useContext, useMemo } from 'react'; import type { DataViewBase } from '@kbn/es-query'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import { SecurityPageName } from '@kbn/security-solution-navigation'; +import { useEventDetails } from '../shared/hooks/use_event_details'; +import { FlyoutError } from '../shared/components/flyout_error'; +import { FlyoutLoading } from '../shared/components/flyout_loading'; import type { PreviewPanelProps } from '.'; -import { SourcererScopeName } from '../../common/store/sourcerer/model'; -import { useSourcererDataView } from '../../common/containers/sourcerer'; -import { useTimelineEventsDetails } from '../../timelines/containers/details'; -import { getAlertIndexAlias } from '../../timelines/components/side_panel/event_details/helpers'; -import { useSpaceId } from '../../common/hooks/use_space_id'; -import { useRouteSpy } from '../../common/utils/route/use_route_spy'; export interface PreviewPanelContext { /** @@ -41,7 +37,7 @@ export interface PreviewPanelContext { /** * An object with top level fields from the ECS object */ - dataAsNestedObject: Ecs | null; + dataAsNestedObject: Ecs; } export const PreviewPanelContext = createContext(undefined); @@ -53,47 +49,43 @@ export type PreviewPanelProviderProps = { children: React.ReactNode; } & Partial; -export const PreviewPanelProvider = ({ - id, - indexName, - scopeId, - ruleId, - children, -}: PreviewPanelProviderProps) => { - const currentSpaceId = useSpaceId(); - const eventIndex = indexName ? getAlertIndexAlias(indexName, currentSpaceId) ?? indexName : ''; - const [{ pageName }] = useRouteSpy(); - const sourcererScope = - pageName === SecurityPageName.detections - ? SourcererScopeName.detections - : SourcererScopeName.default; - const sourcererDataView = useSourcererDataView(sourcererScope); - const [_, __, ___, dataAsNestedObject] = useTimelineEventsDetails({ - indexName: eventIndex, - eventId: id ?? '', - runtimeMappings: sourcererDataView.runtimeMappings, - skip: !id, - }); +export const PreviewPanelProvider = memo( + ({ id, indexName, scopeId, ruleId, children }: PreviewPanelProviderProps) => { + const { dataAsNestedObject, indexPattern, loading } = useEventDetails({ + eventId: id, + indexName, + }); - const contextValue = useMemo( - () => - id && indexName && scopeId - ? { - eventId: id, - indexName, - scopeId, - ruleId: ruleId ?? '', - indexPattern: sourcererDataView.indexPattern, - dataAsNestedObject, - } - : undefined, - [id, indexName, scopeId, ruleId, sourcererDataView.indexPattern, dataAsNestedObject] - ); + const contextValue = useMemo( + () => + id && indexName && scopeId && dataAsNestedObject + ? { + eventId: id, + indexName, + scopeId, + ruleId: ruleId ?? '', + indexPattern, + dataAsNestedObject, + } + : undefined, + [id, indexName, scopeId, ruleId, indexPattern, dataAsNestedObject] + ); - return ( - {children} - ); -}; + if (loading) { + return ; + } + + if (!contextValue) { + return ; + } + + return ( + {children} + ); + } +); + +PreviewPanelProvider.displayName = 'PreviewPanelProvider'; export const usePreviewPanelContext = (): PreviewPanelContext => { const contextValue = useContext(PreviewPanelContext); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.test.tsx index 9892cfe3adf25..cecddeec1b987 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.test.tsx @@ -35,7 +35,6 @@ jest.mock('react-router-dom', () => { }); const panelContextValue = { - dataAsNestedObject: null, dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, } as unknown as RightPanelContext; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.tsx index 632a34e169f71..ede2169fce268 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.tsx @@ -31,7 +31,7 @@ export const AnalyzerPreviewContainer: React.FC = () => { const { dataAsNestedObject } = useRightPanelContext(); // decide whether to show the analyzer preview or not - const isEnabled = isInvestigateInResolverActionEnabled(dataAsNestedObject || undefined); + const isEnabled = isInvestigateInResolverActionEnabled(dataAsNestedObject); const dispatch = useDispatch(); const { startTransaction } = useStartTransaction(); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx index f80d7c1939661..48b12f31599cf 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx @@ -48,7 +48,7 @@ const flyoutContextValue = { openPreviewPanel: jest.fn(), } as unknown as ExpandableFlyoutContext; -const panelContextValue = (dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null) => +const panelContextValue = (dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]) => ({ eventId: 'event id', indexName: 'indexName', @@ -94,17 +94,6 @@ describe('', () => { expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toHaveTextContent(DOCUMENT_DESCRIPTION_TITLE); }); - it('should render null if dataFormattedForFieldBrowser is null', () => { - const panelContext = { - ...panelContextValue([ruleUuid, ruleDescription, ruleName]), - dataFormattedForFieldBrowser: null, - } as unknown as RightPanelContext; - - const { container } = renderDescription(panelContext); - - expect(container).toBeEmptyDOMElement(); - }); - it('should open preview panel when clicking on button', () => { const panelContext = panelContextValue([ruleUuid, ruleDescription, ruleName]); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx index 6c4d49d9d6b4c..521609a4c267c 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx @@ -73,10 +73,6 @@ export const Description: FC = () => { [ruleName, openRulePreview, ruleId] ); - if (!dataFormattedForFieldBrowser) { - return null; - } - const hasRuleDescription = ruleDescription && ruleDescription.length > 0; return ( 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 528b839bb218c..9c79f100bf9e2 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 @@ -117,55 +117,4 @@ describe('', () => { expect(queryByTestId(INSIGHTS_ENTITIES_NO_DATA_TEST_ID)).toBeInTheDocument(); }); - - it('should not render if eventId is null', () => { - const contextValue = { - ...mockContextValue, - eventId: null, - } as unknown as RightPanelContext; - - const { container } = render( - - - - - - ); - - expect(container).toBeEmptyDOMElement(); - }); - - it('should not render if indexName is null', () => { - const contextValue = { - ...mockContextValue, - indexName: null, - } as unknown as RightPanelContext; - - const { container } = render( - - - - - - ); - - expect(container).toBeEmptyDOMElement(); - }); - - it('should not render if scopeId is null', () => { - const contextValue = { - ...mockContextValue, - scopeId: null, - } as unknown as RightPanelContext; - - const { container } = render( - - - - - - ); - - expect(container).toBeEmptyDOMElement(); - }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.tsx index efab7fa9f6d03..920b54be950c1 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.tsx @@ -42,10 +42,6 @@ export const EntitiesOverview: React.FC = () => { }); }, [eventId, openLeftPanel, indexName, scopeId]); - if (!eventId || !indexName || !scopeId) { - return null; - } - return ( <> ', () => { expect(container).toBeEmptyDOMElement(); }); - - it('should render empty component if dataFormattedForFieldBrowser is null', () => { - const panelContextValue = { - dataFormattedForFieldBrowser: null, - scopeId: 'scopeId', - } as unknown as RightPanelContext; - (useHighlightedFields as jest.Mock).mockReturnValue({ - field: { - values: ['value'], - }, - }); - - const { container } = render( - - - - ); - - expect(container).toBeEmptyDOMElement(); - }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx index 8a1724dd98d5a..f6944df60396a 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx @@ -100,7 +100,7 @@ export const HighlightedFields: FC = () => { [highlightedFields, scopeId] ); - if (!dataFormattedForFieldBrowser || items.length === 0) { + if (items.length === 0) { return null; } diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/insights_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/insights_section.test.tsx index 9f6c5b20b2b07..85bf1d891e344 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/insights_section.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/insights_section.test.tsx @@ -10,7 +10,7 @@ import { render } from '@testing-library/react'; import { RightPanelContext } from '../context'; import { INSIGHTS_HEADER_TEST_ID } from './test_ids'; import { TestProviders } from '../../../common/mock'; -import { mockGetFieldsData } from '../mocks/mock_context'; +import { mockDataFormattedForFieldBrowser, mockGetFieldsData } from '../mocks/mock_context'; import { InsightsSection } from './insights_section'; import { useAlertPrevalence } from '../../../common/containers/alerts/use_alert_prevalence'; @@ -63,6 +63,7 @@ describe('', () => { it('should render insights component as expanded when expanded is true', () => { const contextValue = { eventId: 'some_Id', + dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, getFieldsData: mockGetFieldsData, } as unknown as RightPanelContext; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.test.tsx index ecac8547c6a51..a3d6c932b6f14 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.test.tsx @@ -67,28 +67,14 @@ describe('', () => { expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument(); }); - it('should not render investigation guide button when dataFormattedForFieldBrowser is null', () => { - (useInvestigationGuide as jest.Mock).mockReturnValue({ - loading: false, - error: false, - show: false, - }); - const mockContext = { - ...mockContextValue, - dataFormattedForFieldBrowser: null, - }; - const { container } = render(renderInvestigationGuide(mockContext)); - expect(container).toBeEmptyDOMElement(); - }); - - it('should render null when useInvestigationGuide errors out', () => { + it('should render no data message when useInvestigationGuide errors out', () => { (useInvestigationGuide as jest.Mock).mockReturnValue({ loading: false, error: true, show: false, }); - const { container } = render(renderInvestigationGuide()); - expect(container).toBeEmptyDOMElement(); + const { getByTestId } = render(renderInvestigationGuide()); + expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.tsx index 4816577495c70..2175e67bbef53 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.tsx @@ -48,10 +48,6 @@ export const InvestigationGuide: React.FC = () => { }); }, [eventId, indexName, openLeftPanel, scopeId]); - if (!dataFormattedForFieldBrowser || error) { - return null; - } - if (loading) { return ( { - {basicAlertData.ruleId && ruleNote ? ( + {!error && basicAlertData.ruleId && ruleNote ? ( ', () => { beforeEach(() => { 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 96abc4689771e..affaaca1e27bf 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 @@ -129,54 +129,6 @@ describe('', () => { expect(queryByTestId(valueDataTestSubj2)).not.toBeInTheDocument(); }); - it('should render null if eventId is null', () => { - (usePrevalence as jest.Mock).mockReturnValue({ - loading: false, - error: false, - data: [], - }); - const contextValue = { - ...mockContextValue, - eventId: null, - } as unknown as RightPanelContext; - - const { container } = render(renderPrevalenceOverview(contextValue)); - - expect(container).toBeEmptyDOMElement(); - }); - - it('should render null if browserFields is null', () => { - (usePrevalence as jest.Mock).mockReturnValue({ - loading: false, - error: false, - data: [], - }); - const contextValue = { - ...mockContextValue, - browserFields: null, - }; - - const { container } = render(renderPrevalenceOverview(contextValue)); - - expect(container).toBeEmptyDOMElement(); - }); - - it('should render null if dataFormattedForFieldBrowser is null', () => { - (usePrevalence as jest.Mock).mockReturnValue({ - loading: false, - error: false, - data: [], - }); - const contextValue = { - ...mockContextValue, - dataFormattedForFieldBrowser: null, - }; - - const { container } = render(renderPrevalenceOverview(contextValue)); - - expect(container).toBeEmptyDOMElement(); - }); - it('should navigate to left section Insights tab when clicking on button', () => { (usePrevalence as jest.Mock).mockReturnValue({ loading: false, diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx index 309925c21d98b..53e2d636d4bb6 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx @@ -27,14 +27,8 @@ const DEFAULT_TO = 'now'; * The component fetches the necessary data at once. The loading and error states are handled by the ExpandablePanel component. */ export const PrevalenceOverview: FC = () => { - const { - eventId, - indexName, - browserFields, - dataFormattedForFieldBrowser, - scopeId, - investigationFields, - } = useRightPanelContext(); + const { eventId, indexName, dataFormattedForFieldBrowser, scopeId, investigationFields } = + useRightPanelContext(); const { openLeftPanel } = useExpandableFlyoutContext(); const goToCorrelationsTab = useCallback(() => { @@ -73,10 +67,6 @@ export const PrevalenceOverview: FC = () => { [data] ); - if (!eventId || !browserFields || !dataFormattedForFieldBrowser) { - return null; - } - return ( ', () => { expect(getByTestId(REASON_TITLE_TEST_ID)).toBeInTheDocument(); }); - it('should render null if dataFormattedForFieldBrowser is null', () => { - const panelContext = { - ...panelContextValue, - dataFormattedForFieldBrowser: null, - } as unknown as RightPanelContext; - - const { container } = renderReason(panelContext); - - expect(container).toBeEmptyDOMElement(); - }); - it('should render no reason if the field is null', () => { const panelContext = { ...panelContextValue, diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx index b356809917973..e7cd3c7fddf11 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx @@ -70,10 +70,6 @@ export const Reason: FC = () => { [openRulePreview] ); - if (!dataFormattedForFieldBrowser) { - return null; - } - return ( diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.test.tsx index 8db5d4a9cacf5..0568c740f1de4 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.test.tsx @@ -26,7 +26,6 @@ jest.mock('../hooks/use_session_preview'); jest.mock('../../../common/hooks/use_license'); const panelContextValue = { - dataAsNestedObject: null, getFieldsData: mockGetFieldsData, } as unknown as RightPanelContext; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/status.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/status.tsx index fa3a0c6f2ab14..359b889398261 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/status.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/status.tsx @@ -47,7 +47,7 @@ export const DocumentStatus: FC = () => { eventId, contextId: scopeId, scopeId, - browserFields: browserFields || {}, + browserFields, item, }) ); 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 70d8fce2d5c64..4d3d3334862ba 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 @@ -14,10 +14,7 @@ import { ThreatIntelligenceOverview } from './threat_intelligence_overview'; import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; import { useFetchThreatIntelligence } from '../hooks/use_fetch_threat_intelligence'; import { THREAT_INTELLIGENCE_TAB_ID } from '../../left/components/threat_intelligence_details'; -import { - INSIGHTS_THREAT_INTELLIGENCE_CONTAINER_TEST_ID, - INSIGHTS_THREAT_INTELLIGENCE_TEST_ID, -} from './test_ids'; +import { INSIGHTS_THREAT_INTELLIGENCE_TEST_ID } from './test_ids'; import { EXPANDABLE_PANEL_CONTENT_TEST_ID, EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID, @@ -142,35 +139,6 @@ describe('', () => { expect(getAllByTestId(LOADING_TEST_ID)).toHaveLength(2); }); - it('should render null when eventId is null', () => { - (useFetchThreatIntelligence as jest.Mock).mockReturnValue({ - loading: false, - }); - const contextValue = { - ...panelContextValue, - eventId: null, - } as unknown as RightPanelContext; - - const { getByTestId } = render(renderThreatIntelligenceOverview(contextValue)); - - expect(getByTestId(INSIGHTS_THREAT_INTELLIGENCE_CONTAINER_TEST_ID)).toBeEmptyDOMElement(); - }); - - it('should render null when dataFormattedForFieldBrowser is null', () => { - (useFetchThreatIntelligence as jest.Mock).mockReturnValue({ - loading: false, - error: true, - }); - const contextValue = { - ...panelContextValue, - dataFormattedForFieldBrowser: null, - } as unknown as RightPanelContext; - - const { getByTestId } = render(renderThreatIntelligenceOverview(contextValue)); - - expect(getByTestId(INSIGHTS_THREAT_INTELLIGENCE_CONTAINER_TEST_ID)).toBeEmptyDOMElement(); - }); - it('should navigate to left section Insights tab when clicking on button', () => { (useFetchThreatIntelligence as jest.Mock).mockReturnValue({ loading: false, diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.tsx index 6e8c1f3c6dded..42253e61effe2 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.tsx @@ -48,17 +48,10 @@ export const ThreatIntelligenceOverview: FC = () => { }); }, [eventId, openLeftPanel, indexName, scopeId]); - const { - loading: threatIntelLoading, - error: threatIntelError, - threatMatchesCount, - threatEnrichmentsCount, - } = useFetchThreatIntelligence({ + const { loading, threatMatchesCount, threatEnrichmentsCount } = useFetchThreatIntelligence({ dataFormattedForFieldBrowser, }); - const error: boolean = !eventId || !dataFormattedForFieldBrowser || threatIntelError; - return ( { data-test-subj={`${INSIGHTS_THREAT_INTELLIGENCE_TEST_ID}Container`} > ; -export const RightPanelProvider = ({ - id, - indexName, - scopeId, - children, -}: RightPanelProviderProps) => { - const currentSpaceId = useSpaceId(); - // TODO Replace getAlertIndexAlias way to retrieving the eventIndex with the GET /_alias - // https://github.com/elastic/kibana/issues/113063 - const eventIndex = indexName ? getAlertIndexAlias(indexName, currentSpaceId) ?? indexName : ''; - const [{ pageName }] = useRouteSpy(); - const sourcererScope = - pageName === SecurityPageName.detections - ? SourcererScopeName.detections - : SourcererScopeName.default; - const sourcererDataView = useSourcererDataView(sourcererScope); - const [loading, dataFormattedForFieldBrowser, searchHit, dataAsNestedObject, refetchFlyoutData] = - useTimelineEventsDetails({ - indexName: eventIndex, - eventId: id ?? '', - runtimeMappings: sourcererDataView.runtimeMappings, - skip: !id, - }); - const getFieldsData = useGetFieldsData(searchHit?.fields); - const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); - const { rule: maybeRule } = useRuleWithFallback(ruleId); - - const contextValue = useMemo( - () => - id && indexName && scopeId - ? { - eventId: id, - indexName, - scopeId, - browserFields: sourcererDataView.browserFields, - dataAsNestedObject, - dataFormattedForFieldBrowser, - searchHit, - investigationFields: maybeRule?.investigation_fields?.field_names ?? [], - refetchFlyoutData, - getFieldsData, - } - : undefined, - [ - id, - maybeRule, - indexName, - scopeId, - sourcererDataView.browserFields, +export const RightPanelProvider = memo( + ({ id, indexName, scopeId, children }: RightPanelProviderProps) => { + const { + browserFields, dataAsNestedObject, dataFormattedForFieldBrowser, - searchHit, - refetchFlyoutData, getFieldsData, - ] - ); + loading, + refetchFlyoutData, + searchHit, + } = useEventDetails({ eventId: id, indexName }); - if (loading) { - return ( - - - + const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); + const { rule: maybeRule } = useRuleWithFallback(ruleId); + + const contextValue = useMemo( + () => + id && + indexName && + scopeId && + dataAsNestedObject && + dataFormattedForFieldBrowser && + searchHit + ? { + eventId: id, + indexName, + scopeId, + browserFields, + dataAsNestedObject, + dataFormattedForFieldBrowser, + searchHit, + investigationFields: maybeRule?.investigation_fields?.field_names ?? [], + refetchFlyoutData, + getFieldsData, + } + : undefined, + [ + id, + maybeRule, + indexName, + scopeId, + browserFields, + dataAsNestedObject, + dataFormattedForFieldBrowser, + searchHit, + refetchFlyoutData, + getFieldsData, + ] ); + + if (loading) { + return ; + } + + if (!contextValue) { + return ; + } + + return {children}; } +); - return {children}; -}; +RightPanelProvider.displayName = 'RightPanelProvider'; export const useRightPanelContext = (): RightPanelContext => { const contextValue = useContext(RightPanelContext); diff --git a/x-pack/plugins/security_solution/public/flyout/right/footer.tsx b/x-pack/plugins/security_solution/public/flyout/right/footer.tsx index b411470ee386f..b11f1ad1f0013 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/footer.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/footer.tsx @@ -44,10 +44,6 @@ export const PanelFooter: FC = () => { [eventId, indexName, openRightPanel, scopeId, showHostIsolationPanel] ); - if (!dataFormattedForFieldBrowser || !dataAsNestedObject) { - return null; - } - return ( { hookResult = renderHook(() => useFetchThreatIntelligence({ dataFormattedForFieldBrowser })); expect(hookResult.result.current.loading).toEqual(false); - expect(hookResult.result.current.error).toEqual(false); expect(hookResult.result.current.threatMatches).toHaveLength(1); expect(hookResult.result.current.threatMatchesCount).toEqual(1); expect(hookResult.result.current.threatEnrichments).toHaveLength(1); @@ -121,7 +120,6 @@ describe('useFetchThreatIntelligence', () => { hookResult = renderHook(() => useFetchThreatIntelligence({ dataFormattedForFieldBrowser })); expect(hookResult.result.current.loading).toEqual(false); - expect(hookResult.result.current.error).toEqual(false); expect(hookResult.result.current.threatMatches).toHaveLength(2); expect(hookResult.result.current.threatMatchesCount).toEqual(2); expect(hookResult.result.current.threatEnrichments).toHaveLength(2); @@ -148,7 +146,6 @@ describe('useFetchThreatIntelligence', () => { hookResult = renderHook(() => useFetchThreatIntelligence({ dataFormattedForFieldBrowser })); expect(hookResult.result.current.loading).toEqual(false); - expect(hookResult.result.current.error).toEqual(false); expect(hookResult.result.current.threatMatches).toHaveLength(1); expect(hookResult.result.current.threatMatchesCount).toEqual(1); expect(hookResult.result.current.threatEnrichments).toEqual(undefined); @@ -176,7 +173,6 @@ describe('useFetchThreatIntelligence', () => { hookResult = renderHook(() => useFetchThreatIntelligence({ dataFormattedForFieldBrowser })); expect(hookResult.result.current.loading).toEqual(false); - expect(hookResult.result.current.error).toEqual(false); expect(hookResult.result.current.threatMatches).toEqual(undefined); expect(hookResult.result.current.threatMatchesCount).toEqual(0); expect(hookResult.result.current.threatEnrichments).toHaveLength(1); @@ -192,28 +188,6 @@ describe('useFetchThreatIntelligence', () => { hookResult = renderHook(() => useFetchThreatIntelligence({ dataFormattedForFieldBrowser })); expect(hookResult.result.current.loading).toEqual(true); - expect(hookResult.result.current.error).toEqual(false); - expect(hookResult.result.current.threatMatches).toEqual(undefined); - expect(hookResult.result.current.threatMatchesCount).toEqual(0); - expect(hookResult.result.current.threatEnrichments).toEqual(undefined); - expect(hookResult.result.current.threatEnrichmentsCount).toEqual(0); - }); - - it('should return error true', () => { - (useInvestigationTimeEnrichment as jest.Mock).mockReturnValue({ - result: { - enrichments: [], - totalCount: 0, - }, - loading: false, - }); - - hookResult = renderHook(() => - useFetchThreatIntelligence({ dataFormattedForFieldBrowser: null }) - ); - - expect(hookResult.result.current.loading).toEqual(false); - expect(hookResult.result.current.error).toEqual(true); expect(hookResult.result.current.threatMatches).toEqual(undefined); expect(hookResult.result.current.threatMatchesCount).toEqual(0); expect(hookResult.result.current.threatEnrichments).toEqual(undefined); diff --git a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_threat_intelligence.ts b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_threat_intelligence.ts index 37971e755f48b..3b495ad52bc60 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_threat_intelligence.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_fetch_threat_intelligence.ts @@ -23,7 +23,7 @@ export interface UseThreatIntelligenceParams { /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; } export interface UseThreatIntelligenceResult { @@ -31,10 +31,6 @@ export interface UseThreatIntelligenceResult { * Returns true while the threat intelligence data is being queried */ loading: boolean; - /** - * Returns true if the dataFormattedForFieldBrowser property is null - */ - error: boolean; /** * Threat matches (from an indicator match rule) */ @@ -99,7 +95,6 @@ export const useFetchThreatIntelligence = ({ return { loading, - error: !dataFormattedForFieldBrowser, threatMatches, threatMatchesCount: (threatMatches || []).length, threatEnrichments, diff --git a/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_right_panel_context.ts b/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_right_panel_context.ts index 38b69703356c0..33144d8c405cd 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_right_panel_context.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_right_panel_context.ts @@ -5,8 +5,15 @@ * 2.0. */ +import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import type { SearchHit } from '../../../../common/search_strategy'; import type { RightPanelContext } from '../context'; -import { mockDataFormattedForFieldBrowser, mockGetFieldsData } from './mock_context'; +import { + mockDataAsNestedObject, + mockDataFormattedForFieldBrowser, + mockGetFieldsData, + mockSearchHit, +} from './mock_context'; /** * Mock contextValue for right panel context @@ -18,8 +25,8 @@ export const mockContextValue: RightPanelContext = { getFieldsData: mockGetFieldsData, dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, browserFields: {}, - dataAsNestedObject: null, - searchHit: undefined, + dataAsNestedObject: mockDataAsNestedObject as unknown as Ecs, + searchHit: mockSearchHit as unknown as SearchHit, investigationFields: [], refetchFlyoutData: jest.fn(), }; diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.test.tsx index f714a3c7839e5..dac048913c49b 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { RightPanelContext } from '../context'; import { JsonTab } from './json_tab'; -import { JSON_TAB_ERROR_TEST_ID, JSON_TAB_CONTENT_TEST_ID } from './test_ids'; +import { JSON_TAB_CONTENT_TEST_ID } from './test_ids'; describe('', () => { it('should render code block component', () => { @@ -27,22 +27,4 @@ describe('', () => { expect(getByTestId(JSON_TAB_CONTENT_TEST_ID)).toBeInTheDocument(); }); - - it('should render error message on invalid searchHit', () => { - const contextValue = { - searchHit: null, - } as unknown as RightPanelContext; - - const { getByTestId, getByText } = render( - - - - ); - - expect(getByTestId(JSON_TAB_ERROR_TEST_ID)).toBeInTheDocument(); - expect(getByText('Unable to display document information')).toBeInTheDocument(); - expect( - getByText('There was an error displaying the document fields and values') - ).toBeInTheDocument(); - }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.tsx b/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.tsx index 54322959d5326..b49bbca6f5705 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.tsx @@ -5,12 +5,8 @@ * 2.0. */ -import { EuiEmptyPrompt } from '@elastic/eui'; import type { FC } from 'react'; import React, { memo } from 'react'; -import { DOCUMENT_ERROR_DETAILS, DOCUMENT_ERROR_TITLE } from './translations'; -import { JSON_TAB_ERROR_TEST_ID } from './test_ids'; -import { ERROR_TITLE, ERROR_MESSAGE } from '../../shared/translations'; import { JsonView } from '../../../common/components/event_details/json_view'; import { useRightPanelContext } from '../context'; @@ -20,18 +16,6 @@ import { useRightPanelContext } from '../context'; export const JsonTab: FC = memo(() => { const { searchHit } = useRightPanelContext(); - if (!searchHit) { - return ( - {ERROR_TITLE(DOCUMENT_ERROR_TITLE)}} - body={

{ERROR_MESSAGE(DOCUMENT_ERROR_DETAILS)}

} - data-test-subj={JSON_TAB_ERROR_TEST_ID} - /> - ); - } - return ; }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.test.tsx index 16e0d84cd083a..d93cf67abc620 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { RightPanelContext } from '../context'; -import { TABLE_TAB_ERROR_TEST_ID, TABLE_TAB_CONTENT_TEST_ID } from './test_ids'; +import { TABLE_TAB_CONTENT_TEST_ID } from './test_ids'; import { TableTab } from './table_tab'; import { TestProviders } from '../../../common/mock'; @@ -40,64 +40,4 @@ describe('', () => { expect(getByTestId(TABLE_TAB_CONTENT_TEST_ID)).toBeInTheDocument(); }); - - it('should render error message on null browserFields', () => { - const contextValue = { - eventId: 'some_Id', - browserFields: null, - dataFormattedForFieldBrowser: [], - } as unknown as RightPanelContext; - - const { getByTestId, getByText } = render( - - - - ); - - expect(getByTestId(TABLE_TAB_ERROR_TEST_ID)).toBeInTheDocument(); - expect(getByText('Unable to display document information')).toBeInTheDocument(); - expect( - getByText('There was an error displaying the document fields and values') - ).toBeInTheDocument(); - }); - - it('should render error message on null dataFormattedForFieldBrowser', () => { - const contextValue = { - eventId: 'some_Id', - browserFields: {}, - dataFormattedForFieldBrowser: null, - } as unknown as RightPanelContext; - - const { getByTestId, getByText } = render( - - - - ); - - expect(getByTestId(TABLE_TAB_ERROR_TEST_ID)).toBeInTheDocument(); - expect(getByText('Unable to display document information')).toBeInTheDocument(); - expect( - getByText('There was an error displaying the document fields and values') - ).toBeInTheDocument(); - }); - - it('should render error message on null eventId', () => { - const contextValue = { - eventId: null, - browserFields: {}, - dataFormattedForFieldBrowser: [], - } as unknown as RightPanelContext; - - const { getByTestId, getByText } = render( - - - - ); - - expect(getByTestId(TABLE_TAB_ERROR_TEST_ID)).toBeInTheDocument(); - expect(getByText('Unable to display document information')).toBeInTheDocument(); - expect( - getByText('There was an error displaying the document fields and values') - ).toBeInTheDocument(); - }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.tsx b/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.tsx index 7f3d0f45d63af..4f0fac3097679 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/table_tab.tsx @@ -7,13 +7,9 @@ import type { FC } from 'react'; import React, { memo } from 'react'; -import { EuiEmptyPrompt } from '@elastic/eui'; -import { DOCUMENT_ERROR_DETAILS, DOCUMENT_ERROR_TITLE } from './translations'; -import { ERROR_TITLE, ERROR_MESSAGE } from '../../shared/translations'; import { TimelineTabs } from '../../../../common/types'; import { EventFieldsBrowser } from '../../../common/components/event_details/event_fields_browser'; import { useRightPanelContext } from '../context'; -import { TABLE_TAB_ERROR_TEST_ID } from './test_ids'; /** * Table view displayed in the document details expandable flyout right section @@ -21,18 +17,6 @@ import { TABLE_TAB_ERROR_TEST_ID } from './test_ids'; export const TableTab: FC = memo(() => { const { browserFields, dataFormattedForFieldBrowser, eventId } = useRightPanelContext(); - if (!browserFields || !eventId || !dataFormattedForFieldBrowser) { - return ( - {ERROR_TITLE(DOCUMENT_ERROR_TITLE)}} - body={

{ERROR_MESSAGE(DOCUMENT_ERROR_DETAILS)}

} - data-test-subj={TABLE_TAB_ERROR_TEST_ID} - /> - ); - } - return ( ', () => { + it('should render error title and body', () => { + const { getByTestId } = render(); + expect(getByTestId(FLYOUT_ERROR_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(FLYOUT_ERROR_TEST_ID)).toHaveTextContent(ERROR_TITLE(FLYOUT_ERROR)); + expect(getByTestId(FLYOUT_ERROR_TEST_ID)).toHaveTextContent(ERROR_MESSAGE(FLYOUT_ERROR)); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_error.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_error.tsx new file mode 100644 index 0000000000000..700ba9850b75d --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_error.tsx @@ -0,0 +1,28 @@ +/* + * 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 { EuiEmptyPrompt, EuiFlexItem } from '@elastic/eui'; +import { ERROR_MESSAGE, ERROR_TITLE, FLYOUT_ERROR } from '../translations'; +import { FLYOUT_ERROR_TEST_ID } from '../test_ids'; + +/** + * Use this when you need to show an error state in the flyout + */ +export const FlyoutError: React.VFC = () => ( + + {ERROR_TITLE(FLYOUT_ERROR)}} + body={

{ERROR_MESSAGE(FLYOUT_ERROR)}

} + data-test-subj={FLYOUT_ERROR_TEST_ID} + /> +
+); + +FlyoutError.displayName = 'FlyoutError'; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.test.tsx new file mode 100644 index 0000000000000..e7889d30526c5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.test.tsx @@ -0,0 +1,18 @@ +/* + * 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 { FLYOUT_LOADING_TEST_ID } from '../test_ids'; +import { FlyoutLoading } from './flyout_loading'; + +describe('', () => { + it('should render loading', () => { + const { getByTestId } = render(); + expect(getByTestId(FLYOUT_LOADING_TEST_ID)).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.tsx new file mode 100644 index 0000000000000..e1186c6257efd --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.tsx @@ -0,0 +1,27 @@ +/* + * 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 { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { FLYOUT_LOADING_TEST_ID } from '../test_ids'; + +/** + * Use this when you need to show a loading state in the flyout + */ +export const FlyoutLoading: React.VFC = () => ( + + + +); + +FlyoutLoading.displayName = 'FlyoutLoading'; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.test.tsx new file mode 100644 index 0000000000000..0a092271e1ddd --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.test.tsx @@ -0,0 +1,51 @@ +/* + * 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 { UseEventDetailsParams, UseEventDetailsResult } from './use_event_details'; +import { useEventDetails } from './use_event_details'; +import { useSpaceId } from '../../../common/hooks/use_space_id'; +import { useRouteSpy } from '../../../common/utils/route/use_route_spy'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; +import { useTimelineEventsDetails } from '../../../timelines/containers/details'; +import { useGetFieldsData } from '../../../common/hooks/use_get_fields_data'; + +jest.mock('../../../common/hooks/use_space_id'); +jest.mock('../../../common/utils/route/use_route_spy'); +jest.mock('../../../common/containers/sourcerer'); +jest.mock('../../../timelines/containers/details'); +jest.mock('../../../common/hooks/use_get_fields_data'); + +const eventId = 'eventId'; +const indexName = 'indexName'; + +describe('useEventDetails', () => { + let hookResult: RenderHookResult; + + it('should return all properties', () => { + jest.mocked(useSpaceId).mockReturnValue('default'); + (useRouteSpy as jest.Mock).mockReturnValue([{ pageName: 'alerts' }]); + (useSourcererDataView as jest.Mock).mockReturnValue({ + browserFields: {}, + indexPattern: {}, + }); + (useTimelineEventsDetails as jest.Mock).mockReturnValue([false, [], {}, {}, jest.fn()]); + jest.mocked(useGetFieldsData).mockReturnValue((field: string) => field); + + hookResult = renderHook(() => useEventDetails({ eventId, indexName })); + + expect(hookResult.result.current.browserFields).toEqual({}); + expect(hookResult.result.current.dataAsNestedObject).toEqual({}); + expect(hookResult.result.current.dataFormattedForFieldBrowser).toEqual([]); + expect(hookResult.result.current.getFieldsData('test')).toEqual('test'); + expect(hookResult.result.current.indexPattern).toEqual({}); + expect(hookResult.result.current.loading).toEqual(false); + expect(hookResult.result.current.refetchFlyoutData()).toEqual(undefined); + expect(hookResult.result.current.searchHit).toEqual({}); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.ts new file mode 100644 index 0000000000000..91e371cd3b0b6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_event_details.ts @@ -0,0 +1,104 @@ +/* + * 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 { BrowserFields, TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { SecurityPageName } from '@kbn/security-solution-navigation'; +import type { DataViewBase } from '@kbn/es-query'; +import { useSpaceId } from '../../../common/hooks/use_space_id'; +import { getAlertIndexAlias } from '../../../timelines/components/side_panel/event_details/helpers'; +import { useRouteSpy } from '../../../common/utils/route/use_route_spy'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; +import { useTimelineEventsDetails } from '../../../timelines/containers/details'; +import { useGetFieldsData } from '../../../common/hooks/use_get_fields_data'; +import type { SearchHit } from '../../../../common/search_strategy'; +import type { GetFieldsData } from '../../../common/hooks/use_get_fields_data'; + +export interface UseEventDetailsParams { + /** + * Id of the document + */ + eventId: string | undefined; + /** + * Name of the index used in the parent's page + */ + indexName: string | undefined; +} + +export interface UseEventDetailsResult { + /** + * An object containing fields by type + */ + browserFields: BrowserFields; + /** + * 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; + /** + * Retrieves searchHit values for the provided field + */ + getFieldsData: GetFieldsData; + /** + * Index pattern for rule details + */ + indexPattern: DataViewBase; + /** + * Whether the data is loading + */ + loading: boolean; + /** + * Promise to trigger a data refresh + */ + refetchFlyoutData: () => Promise; + /** + * The actual raw document object + */ + searchHit: SearchHit | undefined; +} + +/** + * Hook to retrieve event details for alert details flyout contexts + */ +export const useEventDetails = ({ + eventId, + indexName, +}: UseEventDetailsParams): UseEventDetailsResult => { + const currentSpaceId = useSpaceId(); + // TODO Replace getAlertIndexAlias way to retrieving the eventIndex with the GET /_alias + // https://github.com/elastic/kibana/issues/113063 + const eventIndex = indexName ? getAlertIndexAlias(indexName, currentSpaceId) ?? indexName : ''; + const [{ pageName }] = useRouteSpy(); + const sourcererScope = + pageName === SecurityPageName.detections + ? SourcererScopeName.detections + : SourcererScopeName.default; + const sourcererDataView = useSourcererDataView(sourcererScope); + const [loading, dataFormattedForFieldBrowser, searchHit, dataAsNestedObject, refetchFlyoutData] = + useTimelineEventsDetails({ + indexName: eventIndex, + eventId: eventId ?? '', + runtimeMappings: sourcererDataView.runtimeMappings, + skip: !eventId, + }); + const getFieldsData = useGetFieldsData(searchHit?.fields); + + return { + browserFields: sourcererDataView.browserFields, + dataAsNestedObject, + dataFormattedForFieldBrowser, + getFieldsData, + indexPattern: sourcererDataView.indexPattern, + loading, + refetchFlyoutData, + searchHit, + }; +}; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_highlighted_fields.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_highlighted_fields.ts index ab7de04a021ce..f9fa147c8395a 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_highlighted_fields.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_highlighted_fields.ts @@ -18,7 +18,7 @@ export interface UseHighlightedFieldsParams { /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; /** * An array of fields user has selected to highlight, defined on rule */ @@ -45,8 +45,6 @@ export const useHighlightedFields = ({ dataFormattedForFieldBrowser, investigationFields, }: UseHighlightedFieldsParams): UseHighlightedFieldsResult => { - if (!dataFormattedForFieldBrowser) return {}; - const eventCategories = getEventCategoriesFromData(dataFormattedForFieldBrowser); const eventCodeField = find( diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_investigation_guide.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_investigation_guide.ts index 66d2183ad64fe..cc546a43241d2 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_investigation_guide.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_investigation_guide.ts @@ -14,7 +14,7 @@ export interface UseInvestigationGuideParams { /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; } export interface UseInvestigationGuideResult { diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.ts index a7b110f92e740..a78295139d724 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.ts @@ -38,7 +38,7 @@ export interface UsePrevalenceParams { /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; /** * User defined fields to highlight (defined on the rule) */ 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 85fde8e6732b3..fe2ccb518abe0 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 @@ -24,11 +24,11 @@ export interface UseShowRelatedAlertsByAncestryParams { /** * An object with top level fields from the ECS object */ - dataAsNestedObject: Ecs | null; + dataAsNestedObject: Ecs; /** * An array of field objects with category and value */ - dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]; } export interface UseShowRelatedAlertsByAncestryResult { @@ -57,9 +57,7 @@ export const useShowRelatedAlertsByAncestry = ({ const isRelatedAlertsByProcessAncestryEnabled = useIsExperimentalFeatureEnabled( 'insightsRelatedAlertsByProcessAncestry' ); - const hasProcessEntityInfo = isInvestigateInResolverActionEnabled( - dataAsNestedObject || undefined - ); + const hasProcessEntityInfo = isInvestigateInResolverActionEnabled(dataAsNestedObject); const originalDocumentId = getField(getFieldsData(ANCESTOR_ID)); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/shared/test_ids.ts new file mode 100644 index 0000000000000..f49c1a34173b2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/test_ids.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. + */ + +/* Visualization tab */ + +const PREFIX = 'securitySolutionDocumentDetailsFlyout' as const; + +export const FLYOUT_ERROR_TEST_ID = `${PREFIX}Error` as const; +export const FLYOUT_LOADING_TEST_ID = `${PREFIX}Loading` as const; 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 b58e106cb26b1..5ecf772c5c0c0 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/translations.ts +++ b/x-pack/plugins/security_solution/public/flyout/shared/translations.ts @@ -7,6 +7,10 @@ import { i18n } from '@kbn/i18n'; +export const FLYOUT_ERROR = i18n.translate('xpack.securitySolution.flyout.documentDetails.error', { + defaultMessage: 'data', +}); + export const ERROR_TITLE = (title: string) => i18n.translate('xpack.securitySolution.flyout.errorTitle', { values: { title }, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 65002b4dbb7c8..0bb7f28f877ab 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -33165,7 +33165,6 @@ "xpack.securitySolution.flyout.entities.relatedHostsTitle": "Hôtes associés", "xpack.securitySolution.flyout.entities.relatedUsersTitle": "Utilisateurs associés", "xpack.securitySolution.flyout.entities.usersInfoTitle": "Informations sur l’utilisateur", - "xpack.securitySolution.flyout.prevalenceErrorMessage": "prévalence", "xpack.securitySolution.flyout.prevalenceTableAlertCountColumnTitle": "Nombre d'alertes", "xpack.securitySolution.flyout.prevalenceTableDocCountColumnTitle": "Compte du document", "xpack.securitySolution.flyout.response.title": "Réponses", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 140016969840e..a8cc0445a4a12 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -33164,7 +33164,6 @@ "xpack.securitySolution.flyout.entities.relatedHostsTitle": "関連するホスト", "xpack.securitySolution.flyout.entities.relatedUsersTitle": "関連するユーザー", "xpack.securitySolution.flyout.entities.usersInfoTitle": "ユーザー情報", - "xpack.securitySolution.flyout.prevalenceErrorMessage": "発生率", "xpack.securitySolution.flyout.prevalenceTableAlertCountColumnTitle": "アラート件数", "xpack.securitySolution.flyout.prevalenceTableDocCountColumnTitle": "ドキュメントカウント", "xpack.securitySolution.flyout.response.title": "対応", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 11a24a06c36c4..9c5c310d41a15 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -33160,7 +33160,6 @@ "xpack.securitySolution.flyout.entities.relatedHostsTitle": "相关主机", "xpack.securitySolution.flyout.entities.relatedUsersTitle": "相关用户", "xpack.securitySolution.flyout.entities.usersInfoTitle": "用户信息", - "xpack.securitySolution.flyout.prevalenceErrorMessage": "普及率", "xpack.securitySolution.flyout.prevalenceTableAlertCountColumnTitle": "告警计数", "xpack.securitySolution.flyout.prevalenceTableDocCountColumnTitle": "文档计数", "xpack.securitySolution.flyout.response.title": "响应", diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/discover/cell_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/discover/cell_actions.cy.ts index 468b3cdf2f6fc..4360df7bf6798 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/discover/cell_actions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/discover/cell_actions.cy.ts @@ -27,7 +27,7 @@ describe( `Discover Datagrid Cell Actions`, { env: { ftrConfig: { enableExperimental: ['discoverInTimeline'] } }, - tags: ['@ess', '@serverless', '@brokenInServerless'], + tags: ['@serverless', '@brokenInServerless'], }, () => { beforeEach(() => {