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