diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.test.tsx index 2eee16007f91c..951d9916892f6 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.test.tsx @@ -14,24 +14,58 @@ import { mockContextValue } from '../shared/mocks/mock_context'; import { DocumentDetailsContext } from '../shared/context'; import { PreviewPanelFooter } from './footer'; import { PREVIEW_FOOTER_TEST_ID, PREVIEW_FOOTER_LINK_TEST_ID } from './test_ids'; +import { FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID } from '../shared/components/test_ids'; import { createTelemetryServiceMock } from '../../../common/lib/telemetry/telemetry_service.mock'; +import { useKibana } from '../../../common/lib/kibana'; +import { useAlertExceptionActions } from '../../../detections/components/alerts_table/timeline_actions/use_add_exception_actions'; +import { useInvestigateInTimeline } from '../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline'; +import { useAddToCaseActions } from '../../../detections/components/alerts_table/timeline_actions/use_add_to_case_actions'; jest.mock('@kbn/expandable-flyout'); - -const mockedTelemetry = createTelemetryServiceMock(); -jest.mock('../../../common/lib/kibana', () => { +jest.mock('react-router-dom', () => { + const original = jest.requireActual('react-router-dom'); return { - useKibana: () => ({ - services: { - telemetry: mockedTelemetry, - }, - }), + ...original, + useLocation: jest.fn().mockReturnValue({ search: '' }), }; }); +jest.mock('../../../common/lib/kibana'); +jest.mock('../../../detections/components/alerts_table/timeline_actions/use_add_exception_actions'); +jest.mock( + '../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline' +); +jest.mock('../../../detections/components/alerts_table/timeline_actions/use_add_to_case_actions'); + +const mockedTelemetry = createTelemetryServiceMock(); + describe('', () => { - beforeAll(() => { + beforeEach(() => { jest.mocked(useExpandableFlyoutApi).mockReturnValue(mockFlyoutApi); + (useKibana as jest.Mock).mockReturnValue({ + services: { + osquery: { isOsqueryAvailable: jest.fn() }, + telemetry: mockedTelemetry, + cases: { hooks: { useIsAddToCaseOpen: jest.fn().mockReturnValue(false) } }, + }, + }); + (useAlertExceptionActions as jest.Mock).mockReturnValue({ exceptionActionItems: [] }); + (useInvestigateInTimeline as jest.Mock).mockReturnValue({ + investigateInTimelineActionItems: [], + }); + (useAddToCaseActions as jest.Mock).mockReturnValue({ addToCaseActionItems: [] }); + }); + + it('should not render the take action dropdown if preview mode', () => { + const { queryByTestId } = render( + + + + + + ); + + expect(queryByTestId(PREVIEW_FOOTER_TEST_ID)).not.toBeInTheDocument(); }); it('should render footer for alert', () => { @@ -43,7 +77,7 @@ describe('', () => { ); expect(getByTestId(PREVIEW_FOOTER_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(PREVIEW_FOOTER_TEST_ID)).toHaveTextContent('Show full alert details'); + expect(getByTestId(PREVIEW_FOOTER_LINK_TEST_ID)).toHaveTextContent('Show full alert details'); }); it('should render footer for event', () => { @@ -56,7 +90,21 @@ describe('', () => { ); - expect(getByTestId(PREVIEW_FOOTER_TEST_ID)).toHaveTextContent('Show full event details'); + expect(getByTestId(PREVIEW_FOOTER_LINK_TEST_ID)).toHaveTextContent('Show full event details'); + }); + + it('should render the take action button', () => { + (useInvestigateInTimeline as jest.Mock).mockReturnValue({ + investigateInTimelineActionItems: [{ name: 'test', onClick: jest.fn() }], + }); + const { getByTestId } = render( + + + + + + ); + expect(getByTestId(FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID)).toBeInTheDocument(); }); it('should open document details flyout when clicked', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.tsx index b2df6c096e279..1f05e368920f9 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.tsx @@ -5,13 +5,14 @@ * 2.0. */ -import { EuiLink, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import type { FC } from 'react'; import React, { useCallback, useMemo } from 'react'; +import { EuiLink, EuiFlyoutFooter, EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { TakeActionButton } from '../shared/components/take_action_button'; import { getField } from '../shared/utils'; import { EventKind } from '../shared/constants/event_kinds'; -import { FlyoutFooter } from '../../shared/components/flyout_footer'; import { DocumentDetailsRightPanelKey } from '../shared/constants/panel_keys'; import { useDocumentDetailsContext } from '../shared/context'; import { PREVIEW_FOOTER_TEST_ID, PREVIEW_FOOTER_LINK_TEST_ID } from './test_ids'; @@ -21,8 +22,8 @@ import { DocumentEventTypes } from '../../../common/lib/telemetry'; /** * Footer at the bottom of preview panel with a link to open document details flyout */ -export const PreviewPanelFooter = () => { - const { eventId, indexName, scopeId, getFieldsData } = useDocumentDetailsContext(); +export const PreviewPanelFooter: FC = () => { + const { eventId, indexName, scopeId, getFieldsData, isPreview } = useDocumentDetailsContext(); const { openFlyout } = useExpandableFlyoutApi(); const { telemetry } = useKibana().services; @@ -48,24 +49,36 @@ export const PreviewPanelFooter = () => { }); }, [openFlyout, eventId, indexName, scopeId, telemetry]); + const fullDetailsLink = useMemo( + () => ( + + <> + {i18n.translate('xpack.securitySolution.flyout.preview.openFlyoutLabel', { + values: { isAlert }, + defaultMessage: 'Show full {isAlert, select, true{alert} other{event}} details', + })} + + + ), + [isAlert, openDocumentFlyout] + ); + + if (isPreview) return null; + return ( - - - - - <> - {i18n.translate('xpack.securitySolution.flyout.preview.openFlyoutLabel', { - values: { isAlert }, - defaultMessage: 'Show full {isAlert, select, true{alert} other{event}} details', - })} - - - - - + + + + {fullDetailsLink} + + + + + + ); }; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.test.tsx index 9ece9b0e52495..026abf135e3ee 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.test.tsx @@ -11,12 +11,20 @@ import { TestProviders } from '../../../common/mock'; import { mockContextValue } from '../shared/mocks/mock_context'; import { DocumentDetailsContext } from '../shared/context'; import { FLYOUT_FOOTER_TEST_ID } from './test_ids'; +import { FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID } from '../shared/components/test_ids'; import { useKibana } from '../../../common/lib/kibana'; import { useAlertExceptionActions } from '../../../detections/components/alerts_table/timeline_actions/use_add_exception_actions'; import { useInvestigateInTimeline } from '../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline'; import { useAddToCaseActions } from '../../../detections/components/alerts_table/timeline_actions/use_add_to_case_actions'; jest.mock('../../../common/lib/kibana'); +jest.mock('react-router-dom', () => { + const original = jest.requireActual('react-router-dom'); + return { + ...original, + useLocation: jest.fn().mockReturnValue({ search: '' }), + }; +}); jest.mock('../../../detections/components/alerts_table/timeline_actions/use_add_exception_actions'); jest.mock( '../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline' @@ -39,14 +47,13 @@ describe('PanelFooter', () => { it('should render the take action dropdown', () => { (useKibana as jest.Mock).mockReturnValue({ services: { - osquery: { - isOsqueryAvailable: jest.fn(), - }, + osquery: { isOsqueryAvailable: jest.fn() }, + cases: { hooks: { useIsAddToCaseOpen: jest.fn().mockReturnValue(false) } }, }, }); (useAlertExceptionActions as jest.Mock).mockReturnValue({ exceptionActionItems: [] }); (useInvestigateInTimeline as jest.Mock).mockReturnValue({ - investigateInTimelineActionItems: [], + investigateInTimelineActionItems: [{ name: 'test', onClick: jest.fn() }], }); (useAddToCaseActions as jest.Mock).mockReturnValue({ addToCaseActionItems: [] }); @@ -58,5 +65,6 @@ describe('PanelFooter', () => { ); expect(wrapper.getByTestId(FLYOUT_FOOTER_TEST_ID)).toBeInTheDocument(); + expect(wrapper.getByTestId(FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID)).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.tsx index e5a5fb12915a6..ce955a0b87ddc 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.tsx @@ -6,47 +6,10 @@ */ import type { FC } from 'react'; -import React, { useCallback, useMemo, useState } from 'react'; -import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; -import { EuiFlexGroup, EuiFlexItem, useEuiTheme, EuiFlyoutFooter, EuiPanel } from '@elastic/eui'; -import { find } from 'lodash/fp'; +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiFlyoutFooter, EuiPanel } from '@elastic/eui'; import { FLYOUT_FOOTER_TEST_ID } from './test_ids'; -import type { Status } from '../../../../common/api/detection_engine'; -import { getAlertDetailsFieldValue } from '../../../common/lib/endpoint/utils/get_event_details_field_values'; -import { TakeActionDropdown } from './components/take_action_dropdown'; -import { AddExceptionFlyoutWrapper } from '../../../detections/components/alerts_table/timeline_actions/alert_context_menu'; -import { EventFiltersFlyout } from '../../../management/pages/event_filters/view/components/event_filters_flyout'; -import { OsqueryFlyout } from '../../../detections/components/osquery/osquery_flyout'; -import { useDocumentDetailsContext } from '../shared/context'; -import { useHostIsolation } from '../shared/hooks/use_host_isolation'; -import { DocumentDetailsIsolateHostPanelKey } from '../shared/constants/panel_keys'; -import { useRefetchByScope } from './hooks/use_refetch_by_scope'; -import { useExceptionFlyout } from '../../../detections/components/alerts_table/timeline_actions/use_add_exception_flyout'; -import { isActiveTimeline } from '../../../helpers'; -import { useEventFilterModal } from '../../../detections/components/alerts_table/timeline_actions/use_event_filter_modal'; - -interface AlertSummaryData { - /** - * Status of the alert (open, closed...) - */ - alertStatus: Status; - /** - * Id of the document - */ - eventId: string; - /** - * Id of the rule - */ - ruleId: string; - /** - * Property ruleId on the rule - */ - ruleRuleId: string; - /** - * Name of the rule - */ - ruleName: string; -} +import { TakeActionButton } from '../shared/components/take_action_button'; interface PanelFooterProps { /** @@ -59,179 +22,18 @@ interface PanelFooterProps { * Bottom section of the flyout that contains the take action button */ export const PanelFooter: FC = ({ isPreview }) => { - const { euiTheme } = useEuiTheme(); - // we need this flyout to be above the timeline flyout (which has a z-index of 1002) - const flyoutZIndex = useMemo( - () => ({ style: `z-index: ${(euiTheme.levels.flyout as number) + 3}` }), - [euiTheme] - ); - - const { closeFlyout, openRightPanel } = useExpandableFlyoutApi(); - const { - eventId, - indexName, - dataFormattedForFieldBrowser, - dataAsNestedObject, - refetchFlyoutData, - scopeId, - } = useDocumentDetailsContext(); - - // host isolation interaction - const { isHostIsolationPanelOpen, showHostIsolationPanel } = useHostIsolation(); - const showHostIsolationPanelCallback = useCallback( - (action: 'isolateHost' | 'unisolateHost' | undefined) => { - showHostIsolationPanel(action); - openRightPanel({ - id: DocumentDetailsIsolateHostPanelKey, - params: { - id: eventId, - indexName, - scopeId, - isolateAction: action, - }, - }); - }, - [eventId, indexName, openRightPanel, scopeId, showHostIsolationPanel] - ); - - const { refetch: refetchAll } = useRefetchByScope({ scopeId }); - - // exception interaction - const ruleIndexRaw = useMemo( - () => - find({ category: 'signal', field: 'signal.rule.index' }, dataFormattedForFieldBrowser) - ?.values ?? - find( - { category: 'kibana', field: 'kibana.alert.rule.parameters.index' }, - dataFormattedForFieldBrowser - )?.values, - [dataFormattedForFieldBrowser] - ); - const ruleIndex = useMemo( - (): string[] | undefined => (Array.isArray(ruleIndexRaw) ? ruleIndexRaw : undefined), - [ruleIndexRaw] - ); - const ruleDataViewIdRaw = useMemo( - () => - find({ category: 'signal', field: 'signal.rule.data_view_id' }, dataFormattedForFieldBrowser) - ?.values ?? - find( - { category: 'kibana', field: 'kibana.alert.rule.parameters.data_view_id' }, - dataFormattedForFieldBrowser - )?.values, - [dataFormattedForFieldBrowser] - ); - const ruleDataViewId = useMemo( - (): string | undefined => (Array.isArray(ruleDataViewIdRaw) ? ruleDataViewIdRaw[0] : undefined), - [ruleDataViewIdRaw] - ); - const alertSummaryData = useMemo( - () => - [ - { category: 'signal', field: 'signal.rule.id', name: 'ruleId' }, - { category: 'signal', field: 'signal.rule.rule_id', name: 'ruleRuleId' }, - { category: 'signal', field: 'signal.rule.name', name: 'ruleName' }, - { category: 'signal', field: 'kibana.alert.workflow_status', name: 'alertStatus' }, - { category: '_id', field: '_id', name: 'eventId' }, - ].reduce( - (acc, curr) => ({ - ...acc, - [curr.name]: getAlertDetailsFieldValue( - { category: curr.category, field: curr.field }, - dataFormattedForFieldBrowser - ), - }), - {} as AlertSummaryData - ), - [dataFormattedForFieldBrowser] - ); - const { - exceptionFlyoutType, - openAddExceptionFlyout, - onAddExceptionTypeClick, - onAddExceptionCancel, - onAddExceptionConfirm, - } = useExceptionFlyout({ - refetch: refetchAll, - isActiveTimelines: isActiveTimeline(scopeId), - }); - - // event filter interaction - const { closeAddEventFilterModal, isAddEventFilterModalOpen, onAddEventFilterClick } = - useEventFilterModal(); - - // osquery interaction - const [isOsqueryFlyoutOpenWithAgentId, setOsqueryFlyoutOpenWithAgentId] = useState( - null - ); - const closeOsqueryFlyout = useCallback(() => { - setOsqueryFlyoutOpenWithAgentId(null); - }, [setOsqueryFlyoutOpenWithAgentId]); - const alertId = useMemo( - () => (dataAsNestedObject?.kibana?.alert ? dataAsNestedObject?._id : null), - [dataAsNestedObject?._id, dataAsNestedObject?.kibana?.alert] - ); - if (isPreview) return null; return ( - <> - - - - - {dataAsNestedObject && ( - - )} - - - - - - {openAddExceptionFlyout && - alertSummaryData.ruleId != null && - alertSummaryData.ruleRuleId != null && - alertSummaryData.eventId != null && ( - - )} - - {isAddEventFilterModalOpen && dataAsNestedObject != null && ( - - )} - - {isOsqueryFlyoutOpenWithAgentId && dataAsNestedObject != null && ( - - )} - + + + + + + + + + ); }; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/test_ids.ts index 89a71e5fd17ba..ebfb197b319a3 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/test_ids.ts @@ -9,8 +9,6 @@ import { PREFIX } from '../../shared/test_ids'; export const FLYOUT_BODY_TEST_ID = `${PREFIX}Body` as const; export const FLYOUT_FOOTER_TEST_ID = `${PREFIX}Footer` as const; -export const FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID = - `${FLYOUT_FOOTER_TEST_ID}DropdownButton` as const; export const OVERVIEW_TAB_TEST_ID = `${PREFIX}OverviewTab` as const; export const TABLE_TAB_TEST_ID = `${PREFIX}TableTab` as const; export const JSON_TAB_TEST_ID = `${PREFIX}JsonTab` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/take_action_button.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/take_action_button.test.tsx new file mode 100644 index 0000000000000..4326a60c4a0cf --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/take_action_button.test.tsx @@ -0,0 +1,74 @@ +/* + * 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 type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { TakeActionButton } from './take_action_button'; +import { TestProviders } from '../../../../common/mock'; +import { mockContextValue } from '../mocks/mock_context'; +import { DocumentDetailsContext } from '../context'; +import { FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID } from './test_ids'; +import { useKibana } from '../../../../common/lib/kibana'; +import { useAlertExceptionActions } from '../../../../detections/components/alerts_table/timeline_actions/use_add_exception_actions'; +import { useInvestigateInTimeline } from '../../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline'; +import { useAddToCaseActions } from '../../../../detections/components/alerts_table/timeline_actions/use_add_to_case_actions'; + +jest.mock('../../../../common/lib/kibana'); +jest.mock('react-router-dom', () => { + const original = jest.requireActual('react-router-dom'); + return { + ...original, + useLocation: jest.fn().mockReturnValue({ search: '' }), + }; +}); +jest.mock( + '../../../../detections/components/alerts_table/timeline_actions/use_add_exception_actions' +); +jest.mock( + '../../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline' +); +jest.mock( + '../../../../detections/components/alerts_table/timeline_actions/use_add_to_case_actions' +); + +describe('TakeActionButton', () => { + it('should render the take action button', () => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + osquery: { isOsqueryAvailable: jest.fn() }, + cases: { hooks: { useIsAddToCaseOpen: jest.fn().mockReturnValue(false) } }, + }, + }); + (useAlertExceptionActions as jest.Mock).mockReturnValue({ exceptionActionItems: [] }); + (useInvestigateInTimeline as jest.Mock).mockReturnValue({ + investigateInTimelineActionItems: [{ name: 'test', onClick: jest.fn() }], + }); + (useAddToCaseActions as jest.Mock).mockReturnValue({ addToCaseActionItems: [] }); + + const { getByTestId } = render( + + + + + + ); + expect(getByTestId(FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID)).toBeInTheDocument(); + }); + + it('should not render the take action button if dataAsNestedObject is null', () => { + const { queryByTestId } = render( + + + + + + ); + expect(queryByTestId(FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID)).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/take_action_button.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/take_action_button.tsx new file mode 100644 index 0000000000000..10595f732fcda --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/take_action_button.tsx @@ -0,0 +1,220 @@ +/* + * 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 { FC } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; +import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { useEuiTheme } from '@elastic/eui'; +import { find } from 'lodash/fp'; +import type { Status } from '../../../../../common/api/detection_engine'; +import { getAlertDetailsFieldValue } from '../../../../common/lib/endpoint/utils/get_event_details_field_values'; +import { TakeActionDropdown } from './take_action_dropdown'; +import { AddExceptionFlyoutWrapper } from '../../../../detections/components/alerts_table/timeline_actions/alert_context_menu'; +import { EventFiltersFlyout } from '../../../../management/pages/event_filters/view/components/event_filters_flyout'; +import { OsqueryFlyout } from '../../../../detections/components/osquery/osquery_flyout'; +import { useDocumentDetailsContext } from '../context'; +import { useHostIsolation } from '../hooks/use_host_isolation'; +import { DocumentDetailsIsolateHostPanelKey } from '../constants/panel_keys'; +import { useRefetchByScope } from '../../right/hooks/use_refetch_by_scope'; +import { useExceptionFlyout } from '../../../../detections/components/alerts_table/timeline_actions/use_add_exception_flyout'; +import { isActiveTimeline } from '../../../../helpers'; +import { useEventFilterModal } from '../../../../detections/components/alerts_table/timeline_actions/use_event_filter_modal'; + +interface AlertSummaryData { + /** + * Status of the alert (open, closed...) + */ + alertStatus: Status; + /** + * Id of the document + */ + eventId: string; + /** + * Id of the rule + */ + ruleId: string; + /** + * Property ruleId on the rule + */ + ruleRuleId: string; + /** + * Name of the rule + */ + ruleName: string; +} + +/** + * Take action button in the panel footer + */ +export const TakeActionButton: FC = () => { + const { euiTheme } = useEuiTheme(); + // we need this flyout to be above the timeline flyout (which has a z-index of 1002) + const flyoutZIndex = useMemo( + () => ({ style: `z-index: ${(euiTheme.levels.flyout as number) + 3}` }), + [euiTheme] + ); + + const { closeFlyout, openRightPanel } = useExpandableFlyoutApi(); + const { + eventId, + indexName, + dataFormattedForFieldBrowser, + dataAsNestedObject, + refetchFlyoutData, + scopeId, + } = useDocumentDetailsContext(); + + // host isolation interaction + const { isHostIsolationPanelOpen, showHostIsolationPanel } = useHostIsolation(); + const showHostIsolationPanelCallback = useCallback( + (action: 'isolateHost' | 'unisolateHost' | undefined) => { + showHostIsolationPanel(action); + openRightPanel({ + id: DocumentDetailsIsolateHostPanelKey, + params: { + id: eventId, + indexName, + scopeId, + isolateAction: action, + }, + }); + }, + [eventId, indexName, openRightPanel, scopeId, showHostIsolationPanel] + ); + + const { refetch: refetchAll } = useRefetchByScope({ scopeId }); + + // exception interaction + const ruleIndexRaw = useMemo( + () => + find({ category: 'signal', field: 'signal.rule.index' }, dataFormattedForFieldBrowser) + ?.values ?? + find( + { category: 'kibana', field: 'kibana.alert.rule.parameters.index' }, + dataFormattedForFieldBrowser + )?.values, + [dataFormattedForFieldBrowser] + ); + const ruleIndex = useMemo( + (): string[] | undefined => (Array.isArray(ruleIndexRaw) ? ruleIndexRaw : undefined), + [ruleIndexRaw] + ); + const ruleDataViewIdRaw = useMemo( + () => + find({ category: 'signal', field: 'signal.rule.data_view_id' }, dataFormattedForFieldBrowser) + ?.values ?? + find( + { category: 'kibana', field: 'kibana.alert.rule.parameters.data_view_id' }, + dataFormattedForFieldBrowser + )?.values, + [dataFormattedForFieldBrowser] + ); + const ruleDataViewId = useMemo( + (): string | undefined => (Array.isArray(ruleDataViewIdRaw) ? ruleDataViewIdRaw[0] : undefined), + [ruleDataViewIdRaw] + ); + const alertSummaryData = useMemo( + () => + [ + { category: 'signal', field: 'signal.rule.id', name: 'ruleId' }, + { category: 'signal', field: 'signal.rule.rule_id', name: 'ruleRuleId' }, + { category: 'signal', field: 'signal.rule.name', name: 'ruleName' }, + { category: 'signal', field: 'kibana.alert.workflow_status', name: 'alertStatus' }, + { category: '_id', field: '_id', name: 'eventId' }, + ].reduce( + (acc, curr) => ({ + ...acc, + [curr.name]: getAlertDetailsFieldValue( + { category: curr.category, field: curr.field }, + dataFormattedForFieldBrowser + ), + }), + {} as AlertSummaryData + ), + [dataFormattedForFieldBrowser] + ); + const { + exceptionFlyoutType, + openAddExceptionFlyout, + onAddExceptionTypeClick, + onAddExceptionCancel, + onAddExceptionConfirm, + } = useExceptionFlyout({ + refetch: refetchAll, + isActiveTimelines: isActiveTimeline(scopeId), + }); + + // event filter interaction + const { closeAddEventFilterModal, isAddEventFilterModalOpen, onAddEventFilterClick } = + useEventFilterModal(); + + // osquery interaction + const [isOsqueryFlyoutOpenWithAgentId, setOsqueryFlyoutOpenWithAgentId] = useState( + null + ); + const closeOsqueryFlyout = useCallback(() => { + setOsqueryFlyoutOpenWithAgentId(null); + }, [setOsqueryFlyoutOpenWithAgentId]); + const alertId = useMemo( + () => (dataAsNestedObject?.kibana?.alert ? dataAsNestedObject?._id : null), + [dataAsNestedObject?._id, dataAsNestedObject?.kibana?.alert] + ); + + return ( + <> + {dataAsNestedObject && ( + + )} + + {openAddExceptionFlyout && + alertSummaryData.ruleId != null && + alertSummaryData.ruleRuleId != null && + alertSummaryData.eventId != null && ( + + )} + + {isAddEventFilterModalOpen && dataAsNestedObject != null && ( + + )} + + {isOsqueryFlyoutOpenWithAgentId && dataAsNestedObject != null && ( + + )} + + ); +}; + +TakeActionButton.displayName = 'TakeActionButton'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/take_action_dropdown.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/take_action_dropdown.test.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/flyout/document_details/right/components/take_action_dropdown.test.tsx rename to x-pack/plugins/security_solution/public/flyout/document_details/shared/components/take_action_dropdown.test.tsx index 6189f1b353ec8..ffed3e064d7f5 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/take_action_dropdown.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/take_action_dropdown.test.tsx @@ -27,7 +27,7 @@ import { ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE, ALERT_TAGS_CONTEXT_MENU_ITEM_TITLE, } from '../../../../common/components/toolbar/bulk_actions/translations'; -import { FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID } from '../test_ids'; +import { FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID } from './test_ids'; jest.mock('../../../../common/components/endpoint/host_isolation'); jest.mock('../../../../common/components/endpoint/responder'); @@ -128,7 +128,7 @@ describe('take action dropdown', () => { ); expect( - wrapper.find(`[data-test-subj="${FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID}"]`).exists() + wrapper.find(`[data-test-subj="${FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID}"]`).exists() ).toBeTruthy(); }); @@ -139,7 +139,7 @@ describe('take action dropdown', () => { ); expect( - wrapper.find(`[data-test-subj="${FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID}"]`).first().text() + wrapper.find(`[data-test-subj="${FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID}"]`).first().text() ).toEqual('Take action'); }); @@ -153,7 +153,7 @@ describe('take action dropdown', () => { ); wrapper - .find(`button[data-test-subj="${FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID}"]`) + .find(`button[data-test-subj="${FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID}"]`) .simulate('click'); }); test('should render "Add to existing case"', async () => { @@ -325,7 +325,7 @@ describe('take action dropdown', () => { ); wrapper - .find(`button[data-test-subj="${FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID}"]`) + .find(`button[data-test-subj="${FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID}"]`) .simulate('click'); return wrapper; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/take_action_dropdown.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/take_action_dropdown.tsx similarity index 99% rename from x-pack/plugins/security_solution/public/flyout/document_details/right/components/take_action_dropdown.tsx rename to x-pack/plugins/security_solution/public/flyout/document_details/shared/components/take_action_dropdown.tsx index dbc21e82220de..da94ec6e02e99 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/take_action_dropdown.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/take_action_dropdown.tsx @@ -12,7 +12,7 @@ import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { TableId } from '@kbn/securitysolution-data-table'; import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; import { i18n } from '@kbn/i18n'; -import { FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID } from '../test_ids'; +import { FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID } from './test_ids'; import { getAlertDetailsFieldValue } from '../../../../common/lib/endpoint/utils/get_event_details_field_values'; import { GuidedOnboardingTourStep } from '../../../../common/components/guided_onboarding_tour/tour_step'; import { @@ -362,7 +362,7 @@ export const TakeActionDropdown = memo( tourId={SecurityStepId.alertsCases} >