diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx
index 965167f2c945e..0c077aaea81a8 100644
--- a/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx
@@ -46,6 +46,7 @@ jest.mock('../../lib/kibana', () => {
describe('Custom Links', () => {
const hostName = 'Host Name';
const ipv4 = '192.0.2.255';
+ const ipv4a = '192.0.2.266';
const ipv6 = '2001:db8:ffff:ffff:ffff:ffff:ffff:ffff';
const ipv6Encoded = encodeIpv6(ipv6);
@@ -64,6 +65,16 @@ describe('Custom Links', () => {
});
describe('NetworkDetailsLink', () => {
+ test('can handle array of ips', () => {
+ const wrapper = mount();
+ expect(wrapper.find('EuiLink').first().prop('href')).toEqual(
+ `/ip/${encodeURIComponent(ipv4)}/source`
+ );
+ expect(wrapper.text()).toEqual(`${ipv4}${ipv4a}`);
+ expect(wrapper.find('EuiLink').last().prop('href')).toEqual(
+ `/ip/${encodeURIComponent(ipv4a)}/source`
+ );
+ });
test('should render valid link to IP Details with ipv4 as the display text', () => {
const wrapper = mount();
expect(wrapper.find('EuiLink').prop('href')).toEqual(
diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.tsx
index c74791b8b3aa7..a488b019b123a 100644
--- a/x-pack/plugins/security_solution/public/common/components/links/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/links/index.tsx
@@ -14,7 +14,7 @@ import {
EuiToolTip,
} from '@elastic/eui';
import React, { useMemo, useCallback, SyntheticEvent } from 'react';
-import { isNil } from 'lodash/fp';
+import { isArray, isNil } from 'lodash/fp';
import { IP_REPUTATION_LINKS_SETTING, APP_ID } from '../../../../common/constants';
import {
@@ -172,7 +172,7 @@ const NetworkDetailsLinkComponent: React.FC<{
children?: React.ReactNode;
/** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */
Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon;
- ip: string;
+ ip: string | string[];
flowTarget?: FlowTarget | FlowTargetSourceDest;
isButton?: boolean;
onClick?: (e: SyntheticEvent) => void | undefined;
@@ -181,39 +181,46 @@ const NetworkDetailsLinkComponent: React.FC<{
const { formatUrl, search } = useFormatUrl(SecurityPageName.network);
const { navigateToApp } = useKibana().services.application;
const goToNetworkDetails = useCallback(
- (ev) => {
+ (ev, cIp: string) => {
ev.preventDefault();
navigateToApp(APP_ID, {
deepLinkId: SecurityPageName.network,
- path: getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(ip)), flowTarget, search),
+ path: getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(cIp)), flowTarget, search),
});
},
- [flowTarget, ip, navigateToApp, search]
+ [flowTarget, navigateToApp, search]
);
- const href = useMemo(
- () => formatUrl(getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(ip)))),
- [formatUrl, ip]
+ const getHref = useCallback(
+ (cIp: string) => formatUrl(getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(cIp)))),
+ [formatUrl]
);
- return isButton ? (
-
- {children}
-
- ) : (
-
- {children ? children : ip}
-
+ const getLink = useCallback(
+ (cIp: string, i: number) =>
+ isButton ? (
+ goToNetworkDetails(e, cIp))}
+ href={getHref(cIp)}
+ title={title ?? cIp}
+ >
+ {children}
+
+ ) : (
+ goToNetworkDetails(e, cIp))}
+ href={getHref(cIp)}
+ data-test-subj="network-details"
+ >
+ {children ? children : cIp}
+
+ ),
+ [Component, children, getHref, goToNetworkDetails, isButton, onClick, title]
);
+ return isArray(ip) ? <>{ip.map(getLink)}> : getLink(ip, 0);
};
export const NetworkDetailsLink = React.memo(NetworkDetailsLinkComponent);