diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/content.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/content.test.tsx
new file mode 100644
index 0000000000000..ab535cf5338ad
--- /dev/null
+++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/content.test.tsx
@@ -0,0 +1,90 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import type { AppContextTestRender } from '../../../common/mock/endpoint';
+import { createAppRootMockRenderer, endpointAlertDataMock } from '../../../common/mock/endpoint';
+import { FLYOUT_HOST_ISOLATION_PANEL_TEST_ID } from './test_ids';
+import { IsolateHostPanelContent } from './content';
+
+describe('', () => {
+ let appContextMock: AppContextTestRender;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ appContextMock = createAppRootMockRenderer();
+
+ appContextMock.setExperimentalFlag({
+ responseActionsSentinelOneV1Enabled: true,
+ responseActionsCrowdstrikeManualHostIsolationEnabled: true,
+ });
+ });
+
+ it('should display content with success banner when action is isolateHost', () => {
+ const { getByTestId } = appContextMock.render(
+ {}}
+ handleIsolationActionSuccess={() => {}}
+ />
+ );
+ expect(getByTestId(FLYOUT_HOST_ISOLATION_PANEL_TEST_ID)).toBeInTheDocument();
+ expect(getByTestId('hostIsolateSuccessMessage')).toBeInTheDocument();
+ });
+
+ it('should display content without success banner when action is isolateHost and success banner is not visible', () => {
+ const { getByTestId, queryByTestId } = appContextMock.render(
+ {}}
+ handleIsolationActionSuccess={() => {}}
+ />
+ );
+ expect(getByTestId(FLYOUT_HOST_ISOLATION_PANEL_TEST_ID)).toBeInTheDocument();
+ expect(queryByTestId('hostIsolateSuccessMessage')).not.toBeInTheDocument();
+ });
+
+ it('should display content with success banner when action is unisolateHost', () => {
+ const { getByTestId } = appContextMock.render(
+ {}}
+ handleIsolationActionSuccess={() => {}}
+ />
+ );
+ expect(getByTestId(FLYOUT_HOST_ISOLATION_PANEL_TEST_ID)).toBeInTheDocument();
+ expect(getByTestId('hostUnisolateSuccessMessage')).toBeInTheDocument();
+ });
+
+ it('should display content without success banner when action is unisolateHost', () => {
+ const { getByTestId, queryByTestId } = appContextMock.render(
+ {}}
+ handleIsolationActionSuccess={() => {}}
+ />
+ );
+ expect(getByTestId(FLYOUT_HOST_ISOLATION_PANEL_TEST_ID)).toBeInTheDocument();
+ expect(queryByTestId('hostUnisolateSuccessMessage')).not.toBeInTheDocument();
+ });
+});
diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx
index 0c9f05391d82a..16a0a58cb0a28 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx
@@ -8,6 +8,7 @@
import type { FC } from 'react';
import React, { useCallback } from 'react';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
+import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
import { DocumentDetailsRightPanelKey } from '../shared/constants/panel_keys';
import { useBasicDataFromDetailsData } from '../shared/hooks/use_basic_data_from_details_data';
import {
@@ -17,6 +18,7 @@ import {
import { useHostIsolation } from '../shared/hooks/use_host_isolation';
import { useIsolateHostPanelContext } from './context';
import { FlyoutBody } from '../../shared/components/flyout_body';
+import { FLYOUT_HOST_ISOLATION_PANEL_TEST_ID } from './test_ids';
/**
* Document details expandable flyout section content for the isolate host component, displaying the form or the success banner
@@ -44,7 +46,36 @@ export const PanelContent: FC = () => {
);
return (
-
+
+ );
+};
+export const IsolateHostPanelContent: FC<{
+ isIsolateActionSuccessBannerVisible: boolean;
+ hostName: string;
+ alertId: string;
+ isolateAction: 'isolateHost' | 'unisolateHost';
+ dataFormattedForFieldBrowser: TimelineEventsDetailsItem[];
+ showAlertDetails: () => void;
+ handleIsolationActionSuccess: () => void;
+}> = ({
+ isIsolateActionSuccessBannerVisible,
+ hostName,
+ alertId,
+ isolateAction,
+ dataFormattedForFieldBrowser,
+ showAlertDetails,
+ handleIsolationActionSuccess,
+}) => {
+ return (
+
{isIsolateActionSuccessBannerVisible && (
{
- let render: () => ReturnType;
+ let renderComponent: () => ReturnType;
const setUseIsolateHostPanelContext = (data: Partial = {}) => {
const panelContextMock: IsolateHostPanelContext = {
- eventId: 'some-even-1',
+ eventId: 'some-event-1',
indexName: 'some-index-name',
scopeId: 'some-scope-id',
dataFormattedForFieldBrowser: endpointAlertDataMock.generateEndpointAlertDetailsItemData(),
@@ -41,7 +42,7 @@ describe('Isolation Flyout PanelHeader', () => {
responseActionsCrowdstrikeManualHostIsolationEnabled: true,
});
- render = () => appContextMock.render();
+ renderComponent = () => appContextMock.render();
setUseIsolateHostPanelContext({
isolateAction: 'isolateHost',
@@ -79,10 +80,23 @@ describe('Isolation Flyout PanelHeader', () => {
dataFormattedForFieldBrowser:
endpointAlertDataMock.generateAlertDetailsItemDataForAgentType(agentType),
});
- const { getByTestId } = render();
+ const { getByTestId } = renderComponent();
expect(getByTestId('flyoutHostIsolationHeaderTitle')).toHaveTextContent(title);
expect(getByTestId('flyoutHostIsolationHeaderIntegration'));
}
);
});
+
+describe('', () => {
+ it('should display correct flyout header title for isolateHost', () => {
+ const { getByTestId } = render(
+
+ );
+ expect(getByTestId('flyoutHostIsolationHeaderTitle')).toHaveTextContent(ISOLATE_HOST);
+ expect(getByTestId('flyoutHostIsolationHeaderIntegration')).toBeInTheDocument();
+ });
+});
diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/header.tsx
index c0f4174cff95a..135260cf9ac7c 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/header.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/header.tsx
@@ -8,6 +8,7 @@
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui';
import type { FC } from 'react';
import React from 'react';
+import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
import { AgentTypeIntegration } from '../../../common/components/endpoint/agents/agent_type_integration';
import { useAlertResponseActionsSupport } from '../../../common/hooks/endpoint/use_alert_response_actions_support';
import { useIsolateHostPanelContext } from './context';
@@ -20,6 +21,13 @@ import { ISOLATE_HOST, UNISOLATE_HOST } from '../../../common/components/endpoin
*/
export const PanelHeader: FC = () => {
const { isolateAction, dataFormattedForFieldBrowser: data } = useIsolateHostPanelContext();
+ return ;
+};
+
+export const IsolateHostPanelHeader: FC<{
+ isolateAction: string;
+ data: TimelineEventsDetailsItem[];
+}> = ({ isolateAction, data }) => {
const {
details: { agentType },
} = useAlertResponseActionsSupport(data);
diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/test_ids.ts b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/test_ids.ts
index b3b18c76b4333..5f5a5748ec038 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/test_ids.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/isolate_host/test_ids.ts
@@ -8,3 +8,4 @@
import { PREFIX } from '../../shared/test_ids';
export const FLYOUT_HEADER_TITLE_TEST_ID = `${PREFIX}HeaderTitle` as const;
+export const FLYOUT_HOST_ISOLATION_PANEL_TEST_ID = `${PREFIX}HostIsolationPanel` as const;
diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/take_action_button.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/take_action_button.tsx
index 10595f732fcda..914f98f8772ce 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/take_action_button.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/take_action_button.tsx
@@ -8,8 +8,9 @@
import type { FC } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
-import { useEuiTheme } from '@elastic/eui';
+import { useEuiTheme, EuiFlyout } from '@elastic/eui';
import { find } from 'lodash/fp';
+import { useBasicDataFromDetailsData } from '../hooks/use_basic_data_from_details_data';
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';
@@ -18,12 +19,12 @@ import { EventFiltersFlyout } from '../../../../management/pages/event_filters/v
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';
-
+import { IsolateHostPanelHeader } from '../../isolate_host/header';
+import { IsolateHostPanelContent } from '../../isolate_host/content';
interface AlertSummaryData {
/**
* Status of the alert (open, closed...)
@@ -58,33 +59,21 @@ export const TakeActionButton: FC = () => {
[euiTheme]
);
- const { closeFlyout, openRightPanel } = useExpandableFlyoutApi();
- const {
- eventId,
- indexName,
- dataFormattedForFieldBrowser,
- dataAsNestedObject,
- refetchFlyoutData,
- scopeId,
- } = useDocumentDetailsContext();
+ const { closeFlyout } = useExpandableFlyoutApi();
+ const { 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 {
+ isolateAction,
+ isHostIsolationPanelOpen,
+ showHostIsolationPanel,
+ isIsolateActionSuccessBannerVisible,
+ handleIsolationActionSuccess,
+ showAlertDetails,
+ } = useHostIsolation();
+
+ const { hostName } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser);
const { refetch: refetchAll } = useRefetchByScope({ scopeId });
@@ -174,7 +163,7 @@ export const TakeActionButton: FC = () => {
isHostIsolationPanelOpen={isHostIsolationPanelOpen}
onAddEventFilterClick={onAddEventFilterClick}
onAddExceptionTypeClick={onAddExceptionTypeClick}
- onAddIsolationStatusClick={showHostIsolationPanelCallback}
+ onAddIsolationStatusClick={showHostIsolationPanel}
refetchFlyoutData={refetchFlyoutData}
refetch={refetchAll}
scopeId={scopeId}
@@ -213,6 +202,25 @@ export const TakeActionButton: FC = () => {
ecsData={dataAsNestedObject}
/>
)}
+
+ {isHostIsolationPanelOpen && alertId && (
+ // EUI TODO: This z-index override of EuiOverlayMask is a workaround, and ideally should be resolved with a cleaner UI/UX flow long-term
+
+
+
+
+ )}
>
);
};
diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/hooks/use_host_isolation.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/hooks/use_host_isolation.tsx
index d4882572157b5..2b6aabe2ef5ec 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/hooks/use_host_isolation.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/hooks/use_host_isolation.tsx
@@ -9,11 +9,13 @@ import { useCallback, useMemo, useReducer } from 'react';
import { useWithCaseDetailsRefresh } from '../../../../common/components/endpoint';
interface State {
+ isolateAction: 'isolateHost' | 'unisolateHost';
isHostIsolationPanelOpen: boolean;
isIsolateActionSuccessBannerVisible: boolean;
}
const initialState: State = {
+ isolateAction: 'isolateHost',
isHostIsolationPanelOpen: false,
isIsolateActionSuccessBannerVisible: false,
};
@@ -23,6 +25,10 @@ type HostIsolationActions =
type: 'setIsHostIsolationPanel';
isHostIsolationPanelOpen: boolean;
}
+ | {
+ type: 'setIsolateAction';
+ isolateAction: 'isolateHost' | 'unisolateHost';
+ }
| {
type: 'setIsIsolateActionSuccessBannerVisible';
isIsolateActionSuccessBannerVisible: boolean;
@@ -30,6 +36,8 @@ type HostIsolationActions =
function reducer(state: State, action: HostIsolationActions) {
switch (action.type) {
+ case 'setIsolateAction':
+ return { ...state, isolateAction: action.isolateAction };
case 'setIsHostIsolationPanel':
return { ...state, isHostIsolationPanelOpen: action.isHostIsolationPanelOpen };
case 'setIsIsolateActionSuccessBannerVisible':
@@ -43,6 +51,10 @@ function reducer(state: State, action: HostIsolationActions) {
}
export interface UseHostIsolationResult {
+ /**
+ * The action to take on the host
+ */
+ isolateAction: 'isolateHost' | 'unisolateHost';
/**
* True if the host isolation panel is open in the flyout
*/
@@ -59,21 +71,34 @@ export interface UseHostIsolationResult {
* Callback to show the host isolation panel in the flyout
*/
showHostIsolationPanel: (action: 'isolateHost' | 'unisolateHost' | undefined) => void;
+ /**
+ * Callback to show the alert details in the flyout
+ */
+ showAlertDetails: () => void;
}
/**
* Hook that returns the information for a parent to render the host isolation panel in the flyout
*/
export const useHostIsolation = (): UseHostIsolationResult => {
- const [{ isHostIsolationPanelOpen, isIsolateActionSuccessBannerVisible }, dispatch] = useReducer(
- reducer,
- initialState
- );
+ const [
+ { isolateAction, isHostIsolationPanelOpen, isIsolateActionSuccessBannerVisible },
+ dispatch,
+ ] = useReducer(reducer, initialState);
+
+ const showAlertDetails = useCallback(() => {
+ dispatch({ type: 'setIsHostIsolationPanel', isHostIsolationPanelOpen: false });
+ dispatch({
+ type: 'setIsIsolateActionSuccessBannerVisible',
+ isIsolateActionSuccessBannerVisible: false,
+ });
+ }, []);
const showHostIsolationPanel = useCallback(
(action: 'isolateHost' | 'unisolateHost' | undefined) => {
if (action === 'isolateHost' || action === 'unisolateHost') {
dispatch({ type: 'setIsHostIsolationPanel', isHostIsolationPanelOpen: true });
+ dispatch({ type: 'setIsolateAction', isolateAction: action });
}
},
[]
@@ -95,15 +120,19 @@ export const useHostIsolation = (): UseHostIsolationResult => {
return useMemo(
() => ({
+ isolateAction,
isHostIsolationPanelOpen,
isIsolateActionSuccessBannerVisible,
handleIsolationActionSuccess,
+ showAlertDetails,
showHostIsolationPanel,
}),
[
+ isolateAction,
isHostIsolationPanelOpen,
isIsolateActionSuccessBannerVisible,
handleIsolationActionSuccess,
+ showAlertDetails,
showHostIsolationPanel,
]
);