[Security Solution][Network details] add ability to show full network flyout from preview (#211065)

## Summary

This PR is a follow up of [this
PR](https://github.com/elastic/kibana/pull/187870) that replaced the old
network/ip flyout with a new panel for the expandable flyout. Since then
we improved the UI or preview and added the ability to jump to a full
flyout from its preview.

This PR fixes the issues where users could not navigate to the full
details network/ip flyout from a preview. This functionality already
exists for the alert, event, host and user flyouts. The PR adds a new
footer to the network/ip flyout - only shown in preview mode - that
allows users to navigate to the full detail network/ip flyout.

| Old behavior  | New behavior |
| ------------- | ------------- |
| ![Screenshot 2025-02-13 at 11 53
22 AM](https://github.com/user-attachments/assets/8ecc5ad4-1038-4fd4-9f56-3e7d0e497b06)
| ![Screenshot 2025-02-13 at 11 34
19 AM](https://github.com/user-attachments/assets/bfb909f0-5be8-4f97-af66-4ed3292e6bc3)
|

The user has the ability to navigate to the full detail flyout:


https://github.com/user-attachments/assets/4c809e5d-0b59-4498-9966-0133d139233b

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Philippe Oberti 2025-02-13 21:54:34 +01:00 committed by GitHub
parent 1aac3188a1
commit 96b4f8442e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 189 additions and 0 deletions

View file

@ -0,0 +1,46 @@
/*
* 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 { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { PreviewPanelFooter } from './footer';
import { PREVIEW_FOOTER_LINK_TEST_ID, PREVIEW_FOOTER_TEST_ID } from './test_ids';
import { NetworkPanelKey } from '.';
import { FlowTargetSourceDest } from '../../../common/search_strategy';
import { mockFlyoutApi } from '../document_details/shared/mocks/mock_flyout_context';
jest.mock('@kbn/expandable-flyout');
const ip = 'ip';
const flowTarget = FlowTargetSourceDest.destination;
const scopeId = 'scopeId';
describe('<PreviewPanelFooter />', () => {
beforeEach(() => {
jest.mocked(useExpandableFlyoutApi).mockReturnValue(mockFlyoutApi);
});
it('should open network details flyout when clicked', () => {
const { getByTestId } = render(
<PreviewPanelFooter ip={ip} flowTarget={flowTarget} scopeId={scopeId} />
);
expect(getByTestId(PREVIEW_FOOTER_TEST_ID)).toBeInTheDocument();
getByTestId(PREVIEW_FOOTER_LINK_TEST_ID).click();
expect(mockFlyoutApi.openFlyout).toHaveBeenCalledWith({
right: {
id: NetworkPanelKey,
params: {
ip,
flowTarget,
scopeId,
},
},
});
});
});

View file

@ -0,0 +1,77 @@
/*
* 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 } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiFlyoutFooter, EuiLink, EuiPanel } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { PREVIEW_FOOTER_LINK_TEST_ID, PREVIEW_FOOTER_TEST_ID } from './test_ids';
import type { FlowTargetSourceDest } from '../../../common/search_strategy';
import { NetworkPanelKey } from '.';
export interface PreviewPanelFooterProps {
/**
* IP value
*/
ip: string;
/**
* Destination or source information
*/
flowTarget: FlowTargetSourceDest;
/**
* Scope ID
*/
scopeId: string;
}
/**
* Footer at the bottom of preview panel with a link to open network details flyout
*/
export const PreviewPanelFooter: FC<PreviewPanelFooterProps> = ({ ip, flowTarget, scopeId }) => {
const { openFlyout } = useExpandableFlyoutApi();
const openNetworkFlyout = useCallback(() => {
openFlyout({
right: {
id: NetworkPanelKey,
params: {
ip,
flowTarget,
scopeId,
},
},
});
}, [openFlyout, flowTarget, ip, scopeId]);
const fullDetailsLink = useMemo(
() => (
<EuiLink
onClick={openNetworkFlyout}
target="_blank"
data-test-subj={PREVIEW_FOOTER_LINK_TEST_ID}
>
<>
{i18n.translate('xpack.securitySolution.flyout.network.preview.openFlyoutLabel', {
defaultMessage: 'Show full network details',
})}
</>
</EuiLink>
),
[openNetworkFlyout]
);
return (
<EuiFlyoutFooter data-test-subj={PREVIEW_FOOTER_TEST_ID}>
<EuiPanel color="transparent">
<EuiFlexGroup justifyContent="center">
<EuiFlexItem grow={false}>{fullDetailsLink}</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlyoutFooter>
);
};

View file

@ -0,0 +1,53 @@
/*
* 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 {
useExpandableFlyoutApi,
useExpandableFlyoutHistory,
useExpandableFlyoutState,
} from '@kbn/expandable-flyout';
import { PREVIEW_FOOTER_TEST_ID } from './test_ids';
import { NetworkPanel } from '.';
import { FlowTargetSourceDest } from '../../../common/search_strategy';
import { mockFlyoutApi } from '../document_details/shared/mocks/mock_flyout_context';
import { TestProviders } from '../../common/mock';
jest.mock('@kbn/expandable-flyout');
jest.mock('../../common/hooks/use_experimental_features');
const ip = 'ip';
const flowTarget = FlowTargetSourceDest.destination;
const scopeId = 'scopeId';
describe('<NetworkPanel />', () => {
beforeEach(() => {
jest.mocked(useExpandableFlyoutApi).mockReturnValue(mockFlyoutApi);
jest.mocked(useExpandableFlyoutHistory).mockReturnValue([]);
(useExpandableFlyoutState as jest.Mock).mockReturnValue({});
});
it('should not show footer if non-preview mode', () => {
const { queryByTestId } = render(
<TestProviders>
<NetworkPanel ip={ip} flowTarget={flowTarget} scopeId={scopeId} isPreviewMode={false} />
</TestProviders>
);
expect(queryByTestId(PREVIEW_FOOTER_TEST_ID)).not.toBeInTheDocument();
});
it('should show footer if preview mode', () => {
const { getByTestId } = render(
<TestProviders>
<NetworkPanel ip={ip} flowTarget={flowTarget} scopeId={scopeId} isPreviewMode={true} />
</TestProviders>
);
expect(getByTestId(PREVIEW_FOOTER_TEST_ID)).toBeInTheDocument();
});
});

View file

@ -10,6 +10,7 @@ import React, { memo } from 'react';
import type { FlyoutPanelProps } from '@kbn/expandable-flyout';
import { i18n } from '@kbn/i18n';
import { TableId } from '@kbn/securitysolution-data-table';
import { PreviewPanelFooter } from './footer';
import type { FlowTargetSourceDest } from '../../../common/search_strategy';
import { PanelHeader } from './header';
import { PanelContent } from './content';
@ -64,6 +65,7 @@ export const NetworkPanel: FC<NetworkPanelProps> = memo(
/>
<PanelHeader ip={ip} flowTarget={flowTarget} />
<PanelContent ip={ip} flowTarget={flowTarget} />
{isPreviewMode && <PreviewPanelFooter ip={ip} flowTarget={flowTarget} scopeId={scopeId} />}
</>
);
}

View file

@ -0,0 +1,11 @@
/*
* 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 { PREFIX } from '../shared/test_ids';
export const PREVIEW_FOOTER_TEST_ID = `${PREFIX}NetworkPreviewFooter` as const;
export const PREVIEW_FOOTER_LINK_TEST_ID = `${PREVIEW_FOOTER_TEST_ID}Link` as const;