[8.10] [Security Solution] expandable flyout - add isolate host panel (#165933) (#165984)

# Backport

This will backport the following commits from `main` to `8.10`:
- [[Security Solution] expandable flyout - add isolate host panel
(#165933)](https://github.com/elastic/kibana/pull/165933)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Philippe
Oberti","email":"philippe.oberti@elastic.co"},"sourceCommit":{"committedDate":"2023-09-07T14:49:10Z","message":"[Security
Solution] expandable flyout - add isolate host panel (#165933)\n\n##
Summary\r\n\r\nThis new expandable flyout is going GA in `8.10`. One
feature isn't\r\nworking: the `isolate host` from the `take action`
button in the right\r\nsection footer. The code was added in
this\r\n[PR](https://github.com/elastic/kibana/pull/153903) but isolate
host\r\ntesting must have been overlooked.\r\n\r\nThis PR adds the
functionality to the new expandable flyout, by creating\r\na new panel,
displayed similarly to the right panel is
today.\r\n\r\n\r\n\r\nabd99323-616b-4474-a21c-29ce3c56dd1a\r\n\r\nhttps://github.com/elastic/kibana/pull/165933\r\n\r\n###
TODO\r\n\r\n- [ ] verify logic\r\n- [ ] add unit tests\r\n- [ ] add
Cypress tests\r\n\r\n### Checklist\r\n\r\nDelete any items that are not
applicable to this PR.\r\n\r\n- [ ] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[ ] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Ashokaditya
<ashokaditya@elastic.co>","sha":"ed48990395c639a49370a829345d22d89f24522f","branchLabelMapping":{"^v8.11.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Team:Threat
Hunting:Investigations","v8.10.0","v8.11.0"],"number":165933,"url":"https://github.com/elastic/kibana/pull/165933","mergeCommit":{"message":"[Security
Solution] expandable flyout - add isolate host panel (#165933)\n\n##
Summary\r\n\r\nThis new expandable flyout is going GA in `8.10`. One
feature isn't\r\nworking: the `isolate host` from the `take action`
button in the right\r\nsection footer. The code was added in
this\r\n[PR](https://github.com/elastic/kibana/pull/153903) but isolate
host\r\ntesting must have been overlooked.\r\n\r\nThis PR adds the
functionality to the new expandable flyout, by creating\r\na new panel,
displayed similarly to the right panel is
today.\r\n\r\n\r\n\r\nabd99323-616b-4474-a21c-29ce3c56dd1a\r\n\r\nhttps://github.com/elastic/kibana/pull/165933\r\n\r\n###
TODO\r\n\r\n- [ ] verify logic\r\n- [ ] add unit tests\r\n- [ ] add
Cypress tests\r\n\r\n### Checklist\r\n\r\nDelete any items that are not
applicable to this PR.\r\n\r\n- [ ] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[ ] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Ashokaditya
<ashokaditya@elastic.co>","sha":"ed48990395c639a49370a829345d22d89f24522f"}},"sourceBranch":"main","suggestedTargetBranches":["8.10"],"targetPullRequestStates":[{"branch":"8.10","label":"v8.10.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.11.0","labelRegex":"^v8.11.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/165933","number":165933,"mergeCommit":{"message":"[Security
Solution] expandable flyout - add isolate host panel (#165933)\n\n##
Summary\r\n\r\nThis new expandable flyout is going GA in `8.10`. One
feature isn't\r\nworking: the `isolate host` from the `take action`
button in the right\r\nsection footer. The code was added in
this\r\n[PR](https://github.com/elastic/kibana/pull/153903) but isolate
host\r\ntesting must have been overlooked.\r\n\r\nThis PR adds the
functionality to the new expandable flyout, by creating\r\na new panel,
displayed similarly to the right panel is
today.\r\n\r\n\r\n\r\nabd99323-616b-4474-a21c-29ce3c56dd1a\r\n\r\nhttps://github.com/elastic/kibana/pull/165933\r\n\r\n###
TODO\r\n\r\n- [ ] verify logic\r\n- [ ] add unit tests\r\n- [ ] add
Cypress tests\r\n\r\n### Checklist\r\n\r\nDelete any items that are not
applicable to this PR.\r\n\r\n- [ ] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[ ] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Ashokaditya
<ashokaditya@elastic.co>","sha":"ed48990395c639a49370a829345d22d89f24522f"}}]}]
BACKPORT-->

Co-authored-by: Philippe Oberti <philippe.oberti@elastic.co>
This commit is contained in:
Kibana Machine 2023-09-07 13:20:16 -04:00 committed by GitHub
parent e186d070f3
commit 824bcbfd99
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 322 additions and 9 deletions

View file

@ -11,6 +11,9 @@ import {
type ExpandableFlyoutProps,
ExpandableFlyoutProvider,
} from '@kbn/expandable-flyout';
import type { IsolateHostPanelProps } from './isolate_host';
import { IsolateHostPanel, IsolateHostPanelKey } from './isolate_host';
import { IsolateHostPanelProvider } from './isolate_host/context';
import type { RightPanelProps } from './right';
import { RightPanel, RightPanelKey } from './right';
import { RightPanelProvider } from './right/context';
@ -54,6 +57,14 @@ const expandableFlyoutDocumentsPanels: ExpandableFlyoutProps['registeredPanels']
</PreviewPanelProvider>
),
},
{
key: IsolateHostPanelKey,
component: (props) => (
<IsolateHostPanelProvider {...(props as IsolateHostPanelProps).params}>
<IsolateHostPanel path={props.path as IsolateHostPanelProps['path']} />
</IsolateHostPanelProvider>
),
},
];
const OuterProviders: FC = ({ children }) => {

View file

@ -0,0 +1,62 @@
/*
* 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 } from 'react';
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
import { EuiPanel } from '@elastic/eui';
import { RightPanelKey } from '../right';
import { useBasicDataFromDetailsData } from '../../timelines/components/side_panel/event_details/helpers';
import { EndpointIsolateSuccess } from '../../common/components/endpoint/host_isolation';
import { useHostIsolationTools } from '../../timelines/components/side_panel/event_details/use_host_isolation_tools';
import { useIsolateHostPanelContext } from './context';
import { HostIsolationPanel } from '../../detections/components/host_isolation';
/**
* Document details expandable flyout section content for the isolate host component, displaying the form or the success banner
*/
export const PanelContent: FC = () => {
const { openRightPanel } = useExpandableFlyoutContext();
const { dataFormattedForFieldBrowser, eventId, scopeId, indexName, isolateAction } =
useIsolateHostPanelContext();
const { isIsolateActionSuccessBannerVisible, handleIsolationActionSuccess } =
useHostIsolationTools();
const { alertId, hostName } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser);
const showAlertDetails = useCallback(
() =>
openRightPanel({
id: RightPanelKey,
params: {
id: eventId,
indexName,
scopeId,
},
}),
[eventId, indexName, scopeId, openRightPanel]
);
return (
<EuiPanel hasShadow={false} hasBorder={false}>
{isIsolateActionSuccessBannerVisible && (
<EndpointIsolateSuccess
hostName={hostName}
alertId={alertId}
isolateAction={isolateAction}
/>
)}
<HostIsolationPanel
details={dataFormattedForFieldBrowser}
cancelCallback={showAlertDetails}
successCallback={handleIsolationActionSuccess}
isolateAction={isolateAction}
/>
</EuiPanel>
);
};

View file

@ -0,0 +1,122 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
import { 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 type { IsolateHostPanelProps } from '.';
export interface IsolateHostPanelContext {
/**
* Id of the document
*/
eventId: string;
/**
* Name of the index used in the parent's page
*/
indexName: string;
/**
* Maintain backwards compatibility // TODO remove when possible
*/
scopeId: string;
/**
* An array of field objects with category and value
*/
dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null;
/**
* Isolate action, either 'isolateHost' or 'unisolateHost'
*/
isolateAction: 'isolateHost' | 'unisolateHost';
}
export const IsolateHostPanelContext = createContext<IsolateHostPanelContext | undefined>(
undefined
);
export type IsolateHostPanelProviderProps = {
/**
* React components to render
*/
children: React.ReactNode;
} & Partial<IsolateHostPanelProps['params']>;
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 contextValue = useMemo(
() =>
id && indexName && scopeId && isolateAction
? {
eventId: id,
indexName,
scopeId,
dataFormattedForFieldBrowser,
isolateAction,
}
: undefined,
[id, indexName, scopeId, dataFormattedForFieldBrowser, isolateAction]
);
if (loading) {
return (
<EuiFlexItem
css={css`
align-items: center;
justify-content: center;
`}
>
<EuiLoadingSpinner size="xxl" />
</EuiFlexItem>
);
}
return (
<IsolateHostPanelContext.Provider value={contextValue}>
{children}
</IsolateHostPanelContext.Provider>
);
}
);
IsolateHostPanelProvider.displayName = 'IsolateHostPanelProvider';
export const useIsolateHostPanelContext = (): IsolateHostPanelContext => {
const contextValue = useContext(IsolateHostPanelContext);
if (!contextValue) {
throw new Error(
'IsolateHostPanelContext can only be used within IsolateHostPanelContext provider'
);
}
return contextValue;
};

View file

@ -0,0 +1,31 @@
/*
* 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 { EuiFlyoutHeader, EuiTitle } from '@elastic/eui';
import type { FC } from 'react';
import React from 'react';
import { useIsolateHostPanelContext } from './context';
import { FLYOUT_HEADER_TITLE_TEST_ID } from './test_ids';
import { PANEL_HEADER_ISOLATE_TITLE, PANEL_HEADER_RELEASE_TITLE } from './translations';
/**
* Document details expandable right section header for the isolate host panel
*/
export const PanelHeader: FC = () => {
const { isolateAction } = useIsolateHostPanelContext();
const title =
isolateAction === 'isolateHost' ? PANEL_HEADER_ISOLATE_TITLE : PANEL_HEADER_RELEASE_TITLE;
return (
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s">
<h4 data-test-subj={FLYOUT_HEADER_TITLE_TEST_ID}>{title}</h4>
</EuiTitle>
</EuiFlyoutHeader>
);
};

View file

@ -0,0 +1,37 @@
/*
* 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 from 'react';
import type { FlyoutPanelProps } from '@kbn/expandable-flyout';
import { PanelContent } from './content';
import { PanelHeader } from './header';
export const IsolateHostPanelKey: IsolateHostPanelProps['key'] = 'document-details-isolate-host';
export interface IsolateHostPanelProps extends FlyoutPanelProps {
key: 'document-details-isolate-host';
params?: {
id: string;
indexName: string;
scopeId: string;
isolateAction: 'isolateHost' | 'unisolateHost' | undefined;
};
}
/**
* Panel to be displayed right section in the document details expandable flyout when isolate host is clicked in the
* take action button
*/
export const IsolateHostPanel: FC<Partial<IsolateHostPanelProps>> = () => {
return (
<>
<PanelHeader />
<PanelContent />
</>
);
};

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const FLYOUT_HEADER_TITLE_TEST_ID = 'securitySolutionDocumentDetailsFlyoutHeaderTitle';

View file

@ -0,0 +1,22 @@
/*
* 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 { i18n } from '@kbn/i18n';
export const PANEL_HEADER_ISOLATE_TITLE = i18n.translate(
'xpack.securitySolution.flyout.documentDetails.isolateHostPanelHeaderIsolateTitle',
{
defaultMessage: `Isolate host`,
}
);
export const PANEL_HEADER_RELEASE_TITLE = i18n.translate(
'xpack.securitySolution.flyout.documentDetails.isolateHostPanelHeaderReleaseTitle',
{
defaultMessage: `Release host`,
}
);

View file

@ -6,7 +6,7 @@
*/
import type { FC } from 'react';
import React, { memo } from 'react';
import React, { useCallback } from 'react';
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
import { FlyoutFooter } from '../../timelines/components/side_panel/event_details/flyout';
import { useRightPanelContext } from './context';
@ -15,13 +15,35 @@ import { useHostIsolationTools } from '../../timelines/components/side_panel/eve
/**
*
*/
export const PanelFooter: FC = memo(() => {
const { closeFlyout } = useExpandableFlyoutContext();
const { dataFormattedForFieldBrowser, dataAsNestedObject, refetchFlyoutData, scopeId } =
useRightPanelContext();
export const PanelFooter: FC = () => {
const { closeFlyout, openRightPanel } = useExpandableFlyoutContext();
const {
eventId,
indexName,
dataFormattedForFieldBrowser,
dataAsNestedObject,
refetchFlyoutData,
scopeId,
} = useRightPanelContext();
const { isHostIsolationPanelOpen, showHostIsolationPanel } = useHostIsolationTools();
const showHostIsolationPanelCallback = useCallback(
(action: 'isolateHost' | 'unisolateHost' | undefined) => {
showHostIsolationPanel(action);
openRightPanel({
id: 'document-details-isolate-host',
params: {
id: eventId,
indexName,
scopeId,
isolateAction: action,
},
});
},
[eventId, indexName, openRightPanel, scopeId, showHostIsolationPanel]
);
if (!dataFormattedForFieldBrowser || !dataAsNestedObject) {
return null;
}
@ -34,11 +56,9 @@ export const PanelFooter: FC = memo(() => {
isHostIsolationPanelOpen={isHostIsolationPanelOpen}
isReadOnly={false}
loadingEventDetails={false}
onAddIsolationStatusClick={showHostIsolationPanel}
onAddIsolationStatusClick={showHostIsolationPanelCallback}
scopeId={scopeId}
refetchFlyoutData={refetchFlyoutData}
/>
);
});
PanelFooter.displayName = 'PanelFooter';
};