Skip to content

Commit

Permalink
[8.18] [SecuritySolution] Network page crashes after interacting with…
Browse files Browse the repository at this point in the history
… map (#206773) (#211078)

# Backport

This will backport the following commits from `main` to `8.18`:
- [[SecuritySolution] Network page crashes after interacting with map
(#206773)](#206773)

<!--- Backport version: 9.4.3 -->

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

<!--BACKPORT [{"author":{"name":"Angela
Chuang","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-02-13T18:57:07Z","message":"[SecuritySolution]
Network page crashes after interacting with map (#206773)\n\n##
Summary\r\n\r\nIssues and steps to
reproduce:\r\nhttps://github.com//issues/206761\r\n\r\n\r\nhttps://github.com/user-attachments/assets/a6e2753e-2728-478d-b46b-bcd19ff83c9e\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nCheck the PR satisfies following conditions.
\r\n\r\nReviewers should verify this PR satisfies this list as
well.\r\n\r\n\r\n- [x] [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","sha":"ca81958c29d7ce92496a27a57b54d06056aa1a60","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","v9.0.0","Team:Threat
Hunting:Explore","backport:prev-minor","v8.18.0","v9.1.0"],"title":"[SecuritySolution]
Network page crashes after interacting with
map","number":206773,"url":"https://github.com/elastic/kibana/pull/206773","mergeCommit":{"message":"[SecuritySolution]
Network page crashes after interacting with map (#206773)\n\n##
Summary\r\n\r\nIssues and steps to
reproduce:\r\nhttps://github.com//issues/206761\r\n\r\n\r\nhttps://github.com/user-attachments/assets/a6e2753e-2728-478d-b46b-bcd19ff83c9e\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nCheck the PR satisfies following conditions.
\r\n\r\nReviewers should verify this PR satisfies this list as
well.\r\n\r\n\r\n- [x] [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","sha":"ca81958c29d7ce92496a27a57b54d06056aa1a60"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/206773","number":206773,"mergeCommit":{"message":"[SecuritySolution]
Network page crashes after interacting with map (#206773)\n\n##
Summary\r\n\r\nIssues and steps to
reproduce:\r\nhttps://github.com//issues/206761\r\n\r\n\r\nhttps://github.com/user-attachments/assets/a6e2753e-2728-478d-b46b-bcd19ff83c9e\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nCheck the PR satisfies following conditions.
\r\n\r\nReviewers should verify this PR satisfies this list as
well.\r\n\r\n\r\n- [x] [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","sha":"ca81958c29d7ce92496a27a57b54d06056aa1a60"}}]}]
BACKPORT-->

Co-authored-by: Angela Chuang <[email protected]>
  • Loading branch information
kibanamachine and angorayc authored Feb 13, 2025
1 parent 6953738 commit f34b73a
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 103 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,107 @@
* 2.0.
*/

import { shallow } from 'enzyme';
import React from 'react';
import { MapToolTipComponent } from './map_tool_tip';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import type { TooltipFeature } from '@kbn/maps-plugin/common';
import { MapToolTipComponent } from './map_tool_tip';
import * as i18n from '../translations';
import { TestProviders } from '../../../../../common/mock';

describe('MapToolTip', () => {
test('placeholder component renders correctly against snapshot', () => {
const wrapper = shallow(<MapToolTipComponent />);
expect(wrapper).toMatchSnapshot();
});
jest.mock('./line_tool_tip_content', () => ({
LineToolTipContent: jest.fn(() => <div data-test-subj="line-tool-tip-content" />),
}));

test('full component renders correctly against snapshot', () => {
const addFilters = jest.fn();
const closeTooltip = jest.fn();
const features: TooltipFeature[] = [
{
id: 1,
layerId: 'layerId',
mbProperties: {},
actions: [],
},
];
const getLayerName = jest.fn();
const loadFeatureProperties = jest.fn();
const loadFeatureGeometry = jest.fn();

const wrapper = shallow(
jest.mock('./point_tool_tip_content', () => ({
PointToolTipContent: jest.fn(() => <div data-test-subj="point-tool-tip-content" />),
}));

describe('MapToolTipComponent', () => {
const mockCloseTooltip = jest.fn();
const mockGetLayerName = jest.fn();
const mockLoadFeatureProperties = jest.fn();
const mockLoadFeatureGeometry = jest.fn();
const features = [
{ layerId: 'layer1', id: 'feature1', mbProperties: {} },
{ layerId: 'layer2', id: 'feature2', mbProperties: {} },
] as TooltipFeature[];

const renderComponent = (props = {}) => {
return render(
<MapToolTipComponent
addFilters={addFilters}
closeTooltip={closeTooltip}
closeTooltip={mockCloseTooltip}
features={features}
isLocked={false}
getLayerName={getLayerName}
loadFeatureProperties={loadFeatureProperties}
loadFeatureGeometry={loadFeatureGeometry}
/>
getLayerName={mockGetLayerName}
loadFeatureProperties={mockLoadFeatureProperties}
loadFeatureGeometry={mockLoadFeatureGeometry}
{...props}
/>,
{ wrapper: TestProviders }
);
expect(wrapper).toMatchSnapshot();
};

beforeEach(() => {
jest.clearAllMocks();
mockGetLayerName.mockResolvedValue('Layer Name');
mockLoadFeatureProperties.mockResolvedValue([{ name: 'property1', value: 'value1' }]);
mockLoadFeatureGeometry.mockResolvedValue({ type: 'Point' });
});

it('should not render tooltips when features is empty', () => {
renderComponent({ features: [] });
expect(screen.queryByTestId('point-tool-tip-content')).toBeNull();
});

it('shows a loading spinner initially', () => {
renderComponent();
expect(screen.getByRole('progressbar')).toBeInTheDocument();
});

it('displays an error message when isError is true', async () => {
mockLoadFeatureProperties.mockRejectedValue(new Error('Load error'));
renderComponent();

await waitFor(() => {
expect(screen.getByText(i18n.MAP_TOOL_TIP_ERROR)).toBeInTheDocument();
});
});

it('displays PointToolTipContent and ToolTipFooter when feature geometry is Point', async () => {
renderComponent();

await waitFor(() => {
expect(screen.getByTestId('point-tool-tip-content')).toBeInTheDocument();
expect(screen.getByTestId('previous-feature-button')).toBeInTheDocument();
expect(screen.getByTestId('next-feature-button')).toBeInTheDocument();
});
});

it('navigates to the next and previous features correctly', async () => {
renderComponent();

await waitFor(() => {
expect(screen.getByTestId('previous-feature-button')).toBeInTheDocument();
expect(screen.getByTestId('next-feature-button')).toBeInTheDocument();
});

const nextButton = screen.getByTestId('next-feature-button');
fireEvent.click(nextButton);

await waitFor(() => {
expect(mockLoadFeatureProperties).toHaveBeenCalledWith({
layerId: 'layer2',
properties: {},
});
});

const previousButton = screen.getByTestId('previous-feature-button');
fireEvent.click(previousButton);

await waitFor(() => {
expect(mockLoadFeatureProperties).toHaveBeenCalledWith({
layerId: 'layer1',
properties: {},
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@ import {
} from '@elastic/eui';
import type { Geometry } from 'geojson';
import type { ITooltipProperty } from '@kbn/maps-plugin/public/classes/tooltips/tooltip_property';
import type { TooltipFeature } from '@kbn/maps-plugin/common';
import type { MapToolTipProps } from '../types';
import { ToolTipFooter } from './tooltip_footer';
import { LineToolTipContent } from './line_tool_tip_content';
import { PointToolTipContent } from './point_tool_tip_content';
import { Loader } from '../../../../../common/components/loader';
import * as i18n from '../translations';

const DEFAULT_FEATURE: TooltipFeature[] = [];

export const MapToolTipComponent = ({
closeTooltip,
features = [],
features = DEFAULT_FEATURE,
getLayerName,
loadFeatureProperties,
loadFeatureGeometry,
Expand All @@ -35,7 +38,6 @@ export const MapToolTipComponent = ({
const [featureProps, setFeatureProps] = useState<ITooltipProperty[]>([]);
const [featureGeometry, setFeatureGeometry] = useState<Geometry | null>(null);
const [, setLayerName] = useState<string>('');

const handleCloseTooltip = useCallback(() => {
if (closeTooltip != null) {
closeTooltip();
Expand Down Expand Up @@ -74,24 +76,28 @@ export const MapToolTipComponent = ({

return (
<div>
{featureGeometry != null && featureGeometry.type === 'LineString' ? (
<LineToolTipContent
contextId={`${features[featureIndex].layerId}-${features[featureIndex].id}-${featureIndex}`}
featureProps={featureProps}
/>
) : (
<PointToolTipContent
contextId={`${features[featureIndex].layerId}-${features[featureIndex].id}-${featureIndex}`}
featureProps={featureProps}
/>
)}
{features.length > 1 && (
<ToolTipFooter
featureIndex={featureIndex}
totalFeatures={features.length}
previousFeature={handlePreviousFeature}
nextFeature={handleNextFeature}
/>
<>
{featureGeometry != null && featureGeometry.type === 'LineString' ? (
<LineToolTipContent
data-test-subj="line-tool-tip-content"
contextId={`${features[featureIndex].layerId}-${features[featureIndex].id}-${featureIndex}`}
featureProps={featureProps}
/>
) : (
<PointToolTipContent
data-test-subj="point-tool-tip-content"
contextId={`${features[featureIndex].layerId}-${features[featureIndex].id}-${featureIndex}`}
featureProps={featureProps}
/>
)}
<ToolTipFooter
featureIndex={featureIndex}
totalFeatures={features.length}
previousFeature={handlePreviousFeature}
nextFeature={handleNextFeature}
/>
</>
)}
{isLoadingNextFeature && <Loader data-test-subj="loading-panel" overlay size="m" />}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { DescriptionListStyled } from '../../../../../common/components/page';
import { HostDetailsLink, NetworkDetailsLink } from '../../../../../common/components/links';
import { DefaultFieldRenderer } from '../../../../../timelines/components/field_renderers/default_renderer';
import type { FlowTarget } from '../../../../../../common/search_strategy';
import { SourcererScopeName } from '../../../../../sourcerer/store/model';

interface PointToolTipContentProps {
contextId: string;
Expand All @@ -42,6 +43,7 @@ export const PointToolTipContentComponent = ({
attrName={key}
idPrefix={`map-point-tooltip-${contextId}-${key}-${value}`}
render={(item) => getRenderedFieldValue(key, item)}
scopeId={SourcererScopeName.default}
/>
) : (
getEmptyTagValue()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,9 @@
*/

import React from 'react';
import {
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiIcon,
EuiText,
} from '@elastic/eui';
import { euiLightVars as theme } from '@kbn/ui-theme';
import styled from '@emotion/styled';
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiText } from '@elastic/eui';
import * as i18n from '../translations';

export const Icon = styled(EuiIcon)`
margin-right: ${theme.euiSizeS};
`;

Icon.displayName = 'Icon';

interface MapToolTipFooterProps {
featureIndex: number;
totalFeatures: number;
Expand Down

0 comments on commit f34b73a

Please sign in to comment.