mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Security Solution] Fix networkTopNFlow search strategy response (#80362)
This commit is contained in:
parent
c39581d89e
commit
4debc51576
29 changed files with 323 additions and 256 deletions
|
@ -6,22 +6,15 @@
|
|||
|
||||
export interface GeoEcs {
|
||||
city_name?: string[];
|
||||
|
||||
continent_name?: string[];
|
||||
|
||||
country_iso_code?: string[];
|
||||
|
||||
country_name?: string[];
|
||||
|
||||
location?: Location;
|
||||
|
||||
region_iso_code?: string[];
|
||||
|
||||
region_name?: string[];
|
||||
}
|
||||
|
||||
export interface Location {
|
||||
lon?: number[];
|
||||
|
||||
lat?: number[];
|
||||
}
|
||||
|
|
|
@ -8,14 +8,9 @@ import { GeoEcs } from '../geo';
|
|||
|
||||
export interface SourceEcs {
|
||||
bytes?: number[];
|
||||
|
||||
ip?: string[];
|
||||
|
||||
port?: number[];
|
||||
|
||||
domain?: string[];
|
||||
|
||||
geo?: GeoEcs;
|
||||
|
||||
packets?: number[];
|
||||
}
|
||||
|
|
|
@ -47,8 +47,6 @@ exports[`Table Helpers #getRowItemDraggables it returns correctly against snapsh
|
|||
key="idPrefix-attrName-item1-0"
|
||||
render={[Function]}
|
||||
/>
|
||||
,
|
||||
<Spacer />
|
||||
<DraggableWrapper
|
||||
dataProvider={
|
||||
Object {
|
||||
|
@ -69,8 +67,6 @@ exports[`Table Helpers #getRowItemDraggables it returns correctly against snapsh
|
|||
key="idPrefix-attrName-item2-1"
|
||||
render={[Function]}
|
||||
/>
|
||||
,
|
||||
<Spacer />
|
||||
<DraggableWrapper
|
||||
dataProvider={
|
||||
Object {
|
||||
|
|
|
@ -12,7 +12,7 @@ import styled from 'styled-components';
|
|||
import { DragEffects, DraggableWrapper } from '../drag_and_drop/draggable_wrapper';
|
||||
import { escapeDataProviderId } from '../drag_and_drop/helpers';
|
||||
import { defaultToEmptyTag, getEmptyTagValue } from '../empty_value';
|
||||
import { MoreRowItems, Spacer } from '../page';
|
||||
import { MoreRowItems } from '../page';
|
||||
import { IS_OPERATOR } from '../../../timelines/components/timeline/data_providers/data_provider';
|
||||
import { Provider } from '../../../timelines/components/timeline/data_providers/provider';
|
||||
|
||||
|
@ -92,12 +92,6 @@ export const getRowItemDraggables = ({
|
|||
const id = escapeDataProviderId(`${idPrefix}-${attrName}-${rowItem}-${index}`);
|
||||
return (
|
||||
<React.Fragment key={id}>
|
||||
{index !== 0 && (
|
||||
<>
|
||||
{','}
|
||||
<Spacer />
|
||||
</>
|
||||
)}
|
||||
<DraggableWrapper
|
||||
key={id}
|
||||
dataProvider={{
|
||||
|
|
|
@ -29,6 +29,7 @@ const AnomaliesQueryTabBodyComponent: React.FC<AnomaliesQueryTabBodyProps> = ({
|
|||
AnomaliesTableComponent,
|
||||
flowTarget,
|
||||
ip,
|
||||
hostName,
|
||||
indexNames,
|
||||
}) => {
|
||||
const { jobs } = useInstalledSecurityJobs();
|
||||
|
@ -71,6 +72,7 @@ const AnomaliesQueryTabBodyComponent: React.FC<AnomaliesQueryTabBodyProps> = ({
|
|||
narrowDateRange={narrowDateRange}
|
||||
flowTarget={flowTarget}
|
||||
ip={ip}
|
||||
hostName={hostName}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -32,4 +32,5 @@ export type AnomaliesQueryTabBodyProps = QueryTabBodyProps & {
|
|||
updateDateRange?: UpdateDateRange;
|
||||
hideHistogramIfEmpty?: boolean;
|
||||
ip?: string;
|
||||
hostName?: string;
|
||||
};
|
||||
|
|
|
@ -256,12 +256,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
|
|||
hideForMobile: false,
|
||||
render: ({ node }) =>
|
||||
getRowItemDraggables({
|
||||
rowItems:
|
||||
node.lastSuccess != null &&
|
||||
node.lastSuccess.source != null &&
|
||||
node.lastSuccess.source.ip != null
|
||||
? node.lastSuccess.source.ip
|
||||
: null,
|
||||
rowItems: node.lastSuccess?.source?.ip || null,
|
||||
attrName: 'source.ip',
|
||||
idPrefix: `authentications-table-${node._id}-lastSuccessSource`,
|
||||
render: (item) => <NetworkDetailsLink ip={item} />,
|
||||
|
@ -273,12 +268,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
|
|||
hideForMobile: false,
|
||||
render: ({ node }) =>
|
||||
getRowItemDraggables({
|
||||
rowItems:
|
||||
node.lastSuccess != null &&
|
||||
node.lastSuccess.host != null &&
|
||||
node.lastSuccess.host.name != null
|
||||
? node.lastSuccess.host.name
|
||||
: null,
|
||||
rowItems: node.lastSuccess?.host?.name ?? null,
|
||||
attrName: 'host.name',
|
||||
idPrefix: `authentications-table-${node._id}-lastSuccessfulDestination`,
|
||||
render: (item) => <HostDetailsLink hostName={item} />,
|
||||
|
@ -301,12 +291,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
|
|||
hideForMobile: false,
|
||||
render: ({ node }) =>
|
||||
getRowItemDraggables({
|
||||
rowItems:
|
||||
node.lastFailure != null &&
|
||||
node.lastFailure.source != null &&
|
||||
node.lastFailure.source.ip != null
|
||||
? node.lastFailure.source.ip
|
||||
: null,
|
||||
rowItems: node.lastFailure?.source?.ip || null,
|
||||
attrName: 'source.ip',
|
||||
idPrefix: `authentications-table-${node._id}-lastFailureSource`,
|
||||
render: (item) => <NetworkDetailsLink ip={item} />,
|
||||
|
@ -318,12 +303,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
|
|||
hideForMobile: false,
|
||||
render: ({ node }) =>
|
||||
getRowItemDraggables({
|
||||
rowItems:
|
||||
node.lastFailure != null &&
|
||||
node.lastFailure.host != null &&
|
||||
node.lastFailure.host.name != null
|
||||
? node.lastFailure.host.name
|
||||
: null,
|
||||
rowItems: node.lastFailure?.host?.name || null,
|
||||
attrName: 'host.name',
|
||||
idPrefix: `authentications-table-${node._id}-lastFailureDestination`,
|
||||
render: (item) => <HostDetailsLink hostName={item} />,
|
||||
|
|
|
@ -129,7 +129,7 @@ describe('Uncommon Process Table Component', () => {
|
|||
);
|
||||
|
||||
expect(wrapper.find('.euiTableRow').at(2).find('.euiTableRowCell').at(3).text()).toBe(
|
||||
'Host nameshello-world,hello-world-2 '
|
||||
'Host nameshello-worldhello-world-2 '
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -214,7 +214,7 @@ describe('Uncommon Process Table Component', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find('.euiTableRow').at(4).find('.euiTableRowCell').at(3).text()).toBe(
|
||||
'Host nameshello-world,hello-world-2 '
|
||||
'Host nameshello-worldhello-world-2 '
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -198,15 +198,13 @@ export const EmbeddedMapComponent = ({
|
|||
if (embeddable != null) {
|
||||
embeddable.updateInput({ query });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [query]);
|
||||
}, [embeddable, query]);
|
||||
|
||||
useEffect(() => {
|
||||
if (embeddable != null) {
|
||||
embeddable.updateInput({ filters });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [filters]);
|
||||
}, [embeddable, filters]);
|
||||
|
||||
// DateRange updated useEffect
|
||||
useEffect(() => {
|
||||
|
@ -217,8 +215,7 @@ export const EmbeddedMapComponent = ({
|
|||
};
|
||||
embeddable.updateInput({ timeRange });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [startDate, endDate]);
|
||||
}, [embeddable, startDate, endDate]);
|
||||
|
||||
return isError ? null : (
|
||||
<Embeddable>
|
||||
|
|
|
@ -1,29 +1,37 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MapToolTip full component renders correctly against snapshot 1`] = `
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceAround"
|
||||
<EuiOutsideClickDetector
|
||||
onOutsideClick={[Function]}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceAround"
|
||||
>
|
||||
<EuiLoadingSpinner
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiLoadingSpinner
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiOutsideClickDetector>
|
||||
`;
|
||||
|
||||
exports[`MapToolTip placeholder component renders correctly against snapshot 1`] = `
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceAround"
|
||||
<EuiOutsideClickDetector
|
||||
onOutsideClick={[Function]}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceAround"
|
||||
>
|
||||
<EuiLoadingSpinner
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiLoadingSpinner
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiOutsideClickDetector>
|
||||
`;
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
exports[`PointToolTipContent renders correctly against snapshot 1`] = `
|
||||
<PointToolTipContentComponent
|
||||
closeTooltip={[MockFunction]}
|
||||
contextId="contextId"
|
||||
featureProps={
|
||||
Array [
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
|
@ -35,6 +35,78 @@ export const MapToolTipComponent = ({
|
|||
const [featureGeometry, setFeatureGeometry] = useState<FeatureGeometry | null>(null);
|
||||
const [, setLayerName] = useState<string>('');
|
||||
|
||||
const handleCloseTooltip = useCallback(() => {
|
||||
if (closeTooltip != null) {
|
||||
closeTooltip();
|
||||
setFeatureIndex(0);
|
||||
}
|
||||
}, [closeTooltip]);
|
||||
|
||||
const handlePreviousFeature = useCallback(() => {
|
||||
setFeatureIndex((prevFeatureIndex) => prevFeatureIndex - 1);
|
||||
setIsLoadingNextFeature(true);
|
||||
}, []);
|
||||
|
||||
const handleNextFeature = useCallback(() => {
|
||||
setFeatureIndex((prevFeatureIndex) => prevFeatureIndex + 1);
|
||||
setIsLoadingNextFeature(true);
|
||||
}, []);
|
||||
|
||||
const content = useMemo(() => {
|
||||
if (isError) {
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="spaceAround">
|
||||
<EuiFlexItem grow={false}>{i18n.MAP_TOOL_TIP_ERROR}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading && !isLoadingNextFeature) {
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="spaceAround">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="m" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
{isLoadingNextFeature && <Loader data-test-subj="loading-panel" overlay size="m" />}
|
||||
</div>
|
||||
);
|
||||
}, [
|
||||
featureGeometry,
|
||||
featureIndex,
|
||||
featureProps,
|
||||
features,
|
||||
handleNextFeature,
|
||||
handlePreviousFeature,
|
||||
isError,
|
||||
isLoading,
|
||||
isLoadingNextFeature,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
// Early return if component doesn't yet have props -- result of mounting in portal before actual rendering
|
||||
if (
|
||||
|
@ -77,69 +149,17 @@ export const MapToolTipComponent = ({
|
|||
};
|
||||
|
||||
fetchFeatureProps();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
featureIndex,
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
features
|
||||
.map((f) => `${f.id}-${f.layerId}`)
|
||||
.sort()
|
||||
.join(),
|
||||
features,
|
||||
getLayerName,
|
||||
isLoadingNextFeature,
|
||||
loadFeatureGeometry,
|
||||
loadFeatureProperties,
|
||||
]);
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="spaceAround">
|
||||
<EuiFlexItem grow={false}>{i18n.MAP_TOOL_TIP_ERROR}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return isLoading && !isLoadingNextFeature ? (
|
||||
<EuiFlexGroup justifyContent="spaceAround">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="m" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<EuiOutsideClickDetector
|
||||
onOutsideClick={() => {
|
||||
if (closeTooltip != null) {
|
||||
closeTooltip();
|
||||
setFeatureIndex(0);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<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}
|
||||
closeTooltip={closeTooltip}
|
||||
/>
|
||||
)}
|
||||
{features.length > 1 && (
|
||||
<ToolTipFooter
|
||||
featureIndex={featureIndex}
|
||||
totalFeatures={features.length}
|
||||
previousFeature={() => {
|
||||
setFeatureIndex(featureIndex - 1);
|
||||
setIsLoadingNextFeature(true);
|
||||
}}
|
||||
nextFeature={() => {
|
||||
setFeatureIndex(featureIndex + 1);
|
||||
setIsLoadingNextFeature(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isLoadingNextFeature && <Loader data-test-subj="loading-panel" overlay size="m" />}
|
||||
</div>
|
||||
</EuiOutsideClickDetector>
|
||||
return (
|
||||
<EuiOutsideClickDetector onOutsideClick={handleCloseTooltip}>{content}</EuiOutsideClickDetector>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -24,15 +24,9 @@ describe('PointToolTipContent', () => {
|
|||
];
|
||||
|
||||
test('renders correctly against snapshot', () => {
|
||||
const closeTooltip = jest.fn();
|
||||
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<PointToolTipContentComponent
|
||||
contextId={'contextId'}
|
||||
featureProps={mockFeatureProps}
|
||||
closeTooltip={closeTooltip}
|
||||
/>
|
||||
<PointToolTipContentComponent contextId={'contextId'} featureProps={mockFeatureProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find('PointToolTipContentComponent')).toMatchSnapshot();
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { sourceDestinationFieldMappings } from '../map_config';
|
||||
import {
|
||||
getEmptyTagValue,
|
||||
|
@ -20,36 +20,38 @@ import { ITooltipProperty } from '../../../../../../maps/public/classes/tooltips
|
|||
interface PointToolTipContentProps {
|
||||
contextId: string;
|
||||
featureProps: ITooltipProperty[];
|
||||
closeTooltip?(): void;
|
||||
}
|
||||
|
||||
export const PointToolTipContentComponent = ({
|
||||
contextId,
|
||||
featureProps,
|
||||
closeTooltip,
|
||||
}: PointToolTipContentProps) => {
|
||||
const featureDescriptionListItems = featureProps.map((featureProp) => {
|
||||
const key = featureProp.getPropertyKey();
|
||||
const value = featureProp.getRawValue() ?? [];
|
||||
const featureDescriptionListItems = useMemo(
|
||||
() =>
|
||||
featureProps.map((featureProp) => {
|
||||
const key = featureProp.getPropertyKey();
|
||||
const value = featureProp.getRawValue() ?? [];
|
||||
|
||||
return {
|
||||
title: sourceDestinationFieldMappings[key],
|
||||
description: (
|
||||
<>
|
||||
{value != null ? (
|
||||
<DefaultFieldRenderer
|
||||
rowItems={Array.isArray(value) ? value : [value]}
|
||||
attrName={key}
|
||||
idPrefix={`map-point-tooltip-${contextId}-${key}-${value}`}
|
||||
render={(item) => getRenderedFieldValue(key, item)}
|
||||
/>
|
||||
) : (
|
||||
getEmptyTagValue()
|
||||
)}
|
||||
</>
|
||||
),
|
||||
};
|
||||
});
|
||||
return {
|
||||
title: sourceDestinationFieldMappings[key],
|
||||
description: (
|
||||
<>
|
||||
{value != null ? (
|
||||
<DefaultFieldRenderer
|
||||
rowItems={Array.isArray(value) ? value : [value]}
|
||||
attrName={key}
|
||||
idPrefix={`map-point-tooltip-${contextId}-${key}-${value}`}
|
||||
render={(item) => getRenderedFieldValue(key, item)}
|
||||
/>
|
||||
) : (
|
||||
getEmptyTagValue()
|
||||
)}
|
||||
</>
|
||||
),
|
||||
};
|
||||
}),
|
||||
[contextId, featureProps]
|
||||
);
|
||||
|
||||
return <DescriptionListStyled listItems={featureDescriptionListItems} />;
|
||||
};
|
||||
|
|
|
@ -9,6 +9,13 @@ import { isEmpty } from 'lodash/fp';
|
|||
import { EuiToolTip } from '@elastic/eui';
|
||||
import countries from 'i18n-iso-countries';
|
||||
import countryJson from 'i18n-iso-countries/langs/en.json';
|
||||
import styled from 'styled-components';
|
||||
|
||||
// Fixes vertical alignment of the flag
|
||||
const FlagWrapper = styled.span`
|
||||
position: relative;
|
||||
top: 1px;
|
||||
`;
|
||||
|
||||
/**
|
||||
* Returns the flag for the specified country code, or null if the specified
|
||||
|
@ -38,10 +45,10 @@ export const CountryFlag = memo<{
|
|||
if (flag !== null) {
|
||||
return displayCountryNameOnHover ? (
|
||||
<EuiToolTip position="top" content={countries.getName(countryCode, 'en')}>
|
||||
<span data-test-subj="country-flag">{flag}</span>
|
||||
<FlagWrapper data-test-subj="country-flag">{flag}</FlagWrapper>
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
<span data-test-subj="country-flag">{flag}</span>
|
||||
<FlagWrapper data-test-subj="country-flag">{flag}</FlagWrapper>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
@ -49,7 +56,7 @@ export const CountryFlag = memo<{
|
|||
|
||||
CountryFlag.displayName = 'CountryFlag';
|
||||
|
||||
/** Renders an emjoi flag with country name for the specified country code */
|
||||
/** Renders an emoji flag with country name for the specified country code */
|
||||
export const CountryFlagAndName = memo<{
|
||||
countryCode: string;
|
||||
displayCountryNameOnHover?: boolean;
|
||||
|
@ -67,10 +74,13 @@ export const CountryFlagAndName = memo<{
|
|||
if (flag !== null && localesLoaded) {
|
||||
return displayCountryNameOnHover ? (
|
||||
<EuiToolTip position="top" content={countries.getName(countryCode, 'en')}>
|
||||
<span data-test-subj="country-flag">{flag}</span>
|
||||
<FlagWrapper data-test-subj="country-flag">{flag}</FlagWrapper>
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
<span data-test-subj="country-flag">{`${flag} ${countries.getName(countryCode, 'en')}`}</span>
|
||||
<>
|
||||
<FlagWrapper data-test-subj="country-flag">{flag}</FlagWrapper>
|
||||
{` ${countries.getName(countryCode, 'en')}`}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -198,6 +198,7 @@ export const useNetworkHttp = ({
|
|||
factoryQueryType: NetworkQueries.http,
|
||||
filterQuery: createFilter(filterQuery),
|
||||
id: ID,
|
||||
ip,
|
||||
pagination: generateTablePaginationOptions(activePage, limit),
|
||||
sort: sort as SortField,
|
||||
timerange: {
|
||||
|
@ -211,7 +212,7 @@ export const useNetworkHttp = ({
|
|||
}
|
||||
return prevRequest;
|
||||
});
|
||||
}, [activePage, indexNames, endDate, filterQuery, limit, startDate, sort, skip]);
|
||||
}, [activePage, indexNames, endDate, filterQuery, ip, limit, startDate, sort, skip]);
|
||||
|
||||
useEffect(() => {
|
||||
networkHttpSearch(networkHttpRequest);
|
||||
|
|
|
@ -61,6 +61,7 @@ export const useNetworkTopCountries = ({
|
|||
filterQuery,
|
||||
flowTarget,
|
||||
indexNames,
|
||||
ip,
|
||||
skip,
|
||||
startDate,
|
||||
type,
|
||||
|
@ -86,6 +87,7 @@ export const useNetworkTopCountries = ({
|
|||
filterQuery: createFilter(filterQuery),
|
||||
flowTarget,
|
||||
id: queryId,
|
||||
ip,
|
||||
pagination: generateTablePaginationOptions(activePage, limit),
|
||||
sort,
|
||||
timerange: {
|
||||
|
@ -203,6 +205,7 @@ export const useNetworkTopCountries = ({
|
|||
filterQuery: createFilter(filterQuery),
|
||||
flowTarget,
|
||||
id: queryId,
|
||||
ip,
|
||||
pagination: generateTablePaginationOptions(activePage, limit),
|
||||
sort,
|
||||
timerange: {
|
||||
|
@ -221,6 +224,7 @@ export const useNetworkTopCountries = ({
|
|||
indexNames,
|
||||
endDate,
|
||||
filterQuery,
|
||||
ip,
|
||||
limit,
|
||||
startDate,
|
||||
sort,
|
||||
|
|
|
@ -61,6 +61,7 @@ export const useNetworkTopNFlow = ({
|
|||
filterQuery,
|
||||
flowTarget,
|
||||
indexNames,
|
||||
ip,
|
||||
skip,
|
||||
startDate,
|
||||
type,
|
||||
|
@ -84,7 +85,8 @@ export const useNetworkTopNFlow = ({
|
|||
factoryQueryType: NetworkQueries.topNFlow,
|
||||
filterQuery: createFilter(filterQuery),
|
||||
flowTarget,
|
||||
id: ID,
|
||||
id: `${ID}-${flowTarget}`,
|
||||
ip,
|
||||
pagination: generateTablePaginationOptions(activePage, limit),
|
||||
sort,
|
||||
timerange: {
|
||||
|
@ -199,7 +201,8 @@ export const useNetworkTopNFlow = ({
|
|||
factoryQueryType: NetworkQueries.topNFlow,
|
||||
filterQuery: createFilter(filterQuery),
|
||||
flowTarget,
|
||||
id: ID,
|
||||
id: `${ID}-${flowTarget}`,
|
||||
ip,
|
||||
pagination: generateTablePaginationOptions(activePage, limit),
|
||||
timerange: {
|
||||
interval: '12h',
|
||||
|
@ -213,7 +216,7 @@ export const useNetworkTopNFlow = ({
|
|||
}
|
||||
return prevRequest;
|
||||
});
|
||||
}, [activePage, indexNames, endDate, filterQuery, limit, startDate, sort, skip, flowTarget]);
|
||||
}, [activePage, endDate, filterQuery, indexNames, ip, limit, startDate, sort, skip, flowTarget]);
|
||||
|
||||
useEffect(() => {
|
||||
networkTopNFlowSearch(networkTopNFlowRequest);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPopover, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { getOr } from 'lodash/fp';
|
||||
import React, { Fragment, useState } from 'react';
|
||||
import React, { useCallback, Fragment, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { HostEcs } from '../../../../common/ecs/host';
|
||||
|
@ -260,25 +260,31 @@ MoreContainer.displayName = 'MoreContainer';
|
|||
export const DefaultFieldRendererOverflow = React.memo<DefaultFieldRendererOverflowProps>(
|
||||
({ idPrefix, moreMaxHeight, overflowIndexStart = 5, render, rowItems }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const handleClose = useCallback(() => setIsOpen(false), []);
|
||||
const button = useMemo(
|
||||
() => (
|
||||
<>
|
||||
{' ,'}
|
||||
<EuiButtonEmpty size="xs" onClick={handleClose}>
|
||||
{`+${rowItems.length - overflowIndexStart} `}
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.fieldRenderers.moreLabel"
|
||||
defaultMessage="More"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</>
|
||||
),
|
||||
[handleClose, overflowIndexStart, rowItems.length]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
{rowItems.length > overflowIndexStart && (
|
||||
<EuiPopover
|
||||
id="popover"
|
||||
button={
|
||||
<>
|
||||
{' ,'}
|
||||
<EuiButtonEmpty size="xs" onClick={() => setIsOpen(!isOpen)}>
|
||||
{`+${rowItems.length - overflowIndexStart} `}
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.fieldRenderers.moreLabel"
|
||||
defaultMessage="More"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</>
|
||||
}
|
||||
button={button}
|
||||
isOpen={isOpen}
|
||||
closePopover={() => setIsOpen(!isOpen)}
|
||||
closePopover={handleClose}
|
||||
repositionOnScroll
|
||||
>
|
||||
<MoreContainer
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { mapValues, isObject, isArray } from 'lodash/fp';
|
||||
|
||||
import { toArray } from './to_array';
|
||||
|
||||
export const mapObjectValuesToStringArray = (object: object): object =>
|
||||
mapValues((o) => {
|
||||
if (isObject(o) && !isArray(o)) {
|
||||
return mapObjectValuesToStringArray(o);
|
||||
}
|
||||
|
||||
return toArray(o);
|
||||
}, object);
|
||||
|
||||
export const formatResponseObjectValues = <T>(object: T | T[] | null) => {
|
||||
if (object && typeof object === 'object') {
|
||||
return mapObjectValuesToStringArray(object as object);
|
||||
}
|
||||
|
||||
return object;
|
||||
};
|
|
@ -4,5 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const toArray = <T = string>(value: T | T[] | null) =>
|
||||
export const toArray = <T = string>(value: T | T[] | null): T[] =>
|
||||
Array.isArray(value) ? value : value == null ? [] : [value];
|
||||
|
||||
export const toStringArray = <T = string>(value: T | T[] | null): T[] | string[] =>
|
||||
Array.isArray(value) ? value : value == null ? [] : [`${value}`];
|
||||
|
|
|
@ -9,7 +9,7 @@ import { hostFieldsMap } from '../../../../../../common/ecs/ecs_fields';
|
|||
import { HostsEdges } from '../../../../../../common/search_strategy/security_solution/hosts';
|
||||
|
||||
import { HostAggEsItem, HostBuckets, HostValue } from '../../../../../lib/hosts/types';
|
||||
import { toArray } from '../../../../helpers/to_array';
|
||||
import { toStringArray } from '../../../../helpers/to_array';
|
||||
|
||||
export const HOSTS_FIELDS: readonly string[] = [
|
||||
'_id',
|
||||
|
@ -31,7 +31,7 @@ export const formatHostEdgesData = (
|
|||
flattenedFields.cursor.value = hostId || '';
|
||||
const fieldValue = getHostFieldValue(fieldName, bucket);
|
||||
if (fieldValue != null) {
|
||||
return set(`node.${fieldName}`, toArray(fieldValue), flattenedFields);
|
||||
return set(`node.${fieldName}`, toStringArray(fieldValue), flattenedFields);
|
||||
}
|
||||
return flattenedFields;
|
||||
},
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { get, getOr, isEmpty } from 'lodash/fp';
|
||||
import { set } from '@elastic/safer-lodash-set/fp';
|
||||
import { mergeFieldsWithHit } from '../../../../../utils/build_query';
|
||||
import { toArray } from '../../../../helpers/to_array';
|
||||
import { toStringArray } from '../../../../helpers/to_array';
|
||||
import {
|
||||
AuthenticationsEdges,
|
||||
AuthenticationHit,
|
||||
|
@ -53,7 +53,7 @@ export const formatAuthenticationData = (
|
|||
const fieldPath = `node.${fieldName}`;
|
||||
const fieldValue = get(fieldPath, mergedResult);
|
||||
if (!isEmpty(fieldValue)) {
|
||||
return set(fieldPath, toArray(fieldValue), mergedResult);
|
||||
return set(fieldPath, toStringArray(fieldValue), mergedResult);
|
||||
} else {
|
||||
return mergedResult;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { set } from '@elastic/safer-lodash-set/fp';
|
|||
import { get, has, head } from 'lodash/fp';
|
||||
import { hostFieldsMap } from '../../../../../../common/ecs/ecs_fields';
|
||||
import { HostItem } from '../../../../../../common/search_strategy/security_solution/hosts';
|
||||
import { toArray } from '../../../../helpers/to_array';
|
||||
import { toStringArray } from '../../../../helpers/to_array';
|
||||
|
||||
import { HostAggEsItem, HostBuckets, HostValue } from '../../../../../lib/hosts/types';
|
||||
|
||||
|
@ -40,7 +40,7 @@ export const formatHostItem = (bucket: HostAggEsItem): HostItem =>
|
|||
if (fieldName === '_id') {
|
||||
return set('_id', fieldValue, flattenedFields);
|
||||
}
|
||||
return set(fieldName, toArray(fieldValue), flattenedFields);
|
||||
return set(fieldName, toStringArray(fieldValue), flattenedFields);
|
||||
}
|
||||
return flattenedFields;
|
||||
}, {});
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
HostsUncommonProcessesEdges,
|
||||
HostsUncommonProcessHit,
|
||||
} from '../../../../../../common/search_strategy/security_solution/hosts/uncommon_processes';
|
||||
import { toArray } from '../../../../helpers/to_array';
|
||||
import { toStringArray } from '../../../../helpers/to_array';
|
||||
import { HostHits } from '../../../../../../common/search_strategy';
|
||||
|
||||
export const uncommonProcessesFields = [
|
||||
|
@ -79,7 +79,7 @@ export const formatUncommonProcessesData = (
|
|||
fieldPath = `node.hosts.0.name`;
|
||||
fieldValue = get(fieldPath, mergedResult);
|
||||
}
|
||||
return set(fieldPath, toArray(fieldValue), mergedResult);
|
||||
return set(fieldPath, toStringArray(fieldValue), mergedResult);
|
||||
},
|
||||
{
|
||||
node: {
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
FlowTargetSourceDest,
|
||||
NetworkQueries,
|
||||
NetworkTopNFlowRequestOptions,
|
||||
NetworkTopNFlowStrategyResponse,
|
||||
NetworkTopTablesFields,
|
||||
} from '../../../../../../../common/search_strategy';
|
||||
|
||||
|
@ -554,7 +555,7 @@ export const mockSearchStrategyResponse: IEsSearchResponse<unknown> = {
|
|||
loaded: 21,
|
||||
};
|
||||
|
||||
export const formattedSearchStrategyResponse = {
|
||||
export const formattedSearchStrategyResponse: NetworkTopNFlowStrategyResponse = {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
|
@ -579,13 +580,16 @@ export const formattedSearchStrategyResponse = {
|
|||
ip: '35.232.239.42',
|
||||
location: {
|
||||
geo: {
|
||||
continent_name: 'North America',
|
||||
region_iso_code: 'US-VA',
|
||||
country_iso_code: 'US',
|
||||
region_name: 'Virginia',
|
||||
location: { lon: -77.2481, lat: 38.6583 },
|
||||
continent_name: ['North America'],
|
||||
region_iso_code: ['US-VA'],
|
||||
country_iso_code: ['US'],
|
||||
region_name: ['Virginia'],
|
||||
location: {
|
||||
lon: [-77.2481],
|
||||
lat: [38.6583],
|
||||
},
|
||||
},
|
||||
flowTarget: 'source',
|
||||
flowTarget: FlowTargetSourceDest.source,
|
||||
},
|
||||
autonomous_system: { number: 15169, name: 'Google LLC' },
|
||||
flows: 922,
|
||||
|
@ -603,14 +607,17 @@ export const formattedSearchStrategyResponse = {
|
|||
ip: '151.101.200.204',
|
||||
location: {
|
||||
geo: {
|
||||
continent_name: 'North America',
|
||||
region_iso_code: 'US-VA',
|
||||
city_name: 'Ashburn',
|
||||
country_iso_code: 'US',
|
||||
region_name: 'Virginia',
|
||||
location: { lon: -77.4728, lat: 39.0481 },
|
||||
continent_name: ['North America'],
|
||||
region_iso_code: ['US-VA'],
|
||||
city_name: ['Ashburn'],
|
||||
country_iso_code: ['US'],
|
||||
region_name: ['Virginia'],
|
||||
location: {
|
||||
lon: [-77.4728],
|
||||
lat: [39.0481],
|
||||
},
|
||||
},
|
||||
flowTarget: 'source',
|
||||
flowTarget: FlowTargetSourceDest.source,
|
||||
},
|
||||
autonomous_system: { number: 54113, name: 'Fastly' },
|
||||
flows: 2,
|
||||
|
@ -628,14 +635,17 @@ export const formattedSearchStrategyResponse = {
|
|||
ip: '91.189.92.39',
|
||||
location: {
|
||||
geo: {
|
||||
continent_name: 'Europe',
|
||||
region_iso_code: 'GB-ENG',
|
||||
city_name: 'London',
|
||||
country_iso_code: 'GB',
|
||||
region_name: 'England',
|
||||
location: { lon: -0.0961, lat: 51.5132 },
|
||||
continent_name: ['Europe'],
|
||||
region_iso_code: ['GB-ENG'],
|
||||
city_name: ['London'],
|
||||
country_iso_code: ['GB'],
|
||||
region_name: ['England'],
|
||||
location: {
|
||||
lon: [-0.0961],
|
||||
lat: [51.5132],
|
||||
},
|
||||
},
|
||||
flowTarget: 'source',
|
||||
flowTarget: FlowTargetSourceDest.source,
|
||||
},
|
||||
autonomous_system: { number: 41231, name: 'Canonical Group Limited' },
|
||||
flows: 1,
|
||||
|
@ -668,14 +678,17 @@ export const formattedSearchStrategyResponse = {
|
|||
ip: '151.101.248.204',
|
||||
location: {
|
||||
geo: {
|
||||
continent_name: 'North America',
|
||||
region_iso_code: 'US-VA',
|
||||
city_name: 'Ashburn',
|
||||
country_iso_code: 'US',
|
||||
region_name: 'Virginia',
|
||||
location: { lon: -77.539, lat: 39.018 },
|
||||
continent_name: ['North America'],
|
||||
region_iso_code: ['US-VA'],
|
||||
city_name: ['Ashburn'],
|
||||
country_iso_code: ['US'],
|
||||
region_name: ['Virginia'],
|
||||
location: {
|
||||
lon: [-77.539],
|
||||
lat: [39.018],
|
||||
},
|
||||
},
|
||||
flowTarget: 'source',
|
||||
flowTarget: FlowTargetSourceDest.source,
|
||||
},
|
||||
autonomous_system: { number: 54113, name: 'Fastly' },
|
||||
flows: 6,
|
||||
|
@ -693,13 +706,16 @@ export const formattedSearchStrategyResponse = {
|
|||
ip: '35.196.129.83',
|
||||
location: {
|
||||
geo: {
|
||||
continent_name: 'North America',
|
||||
region_iso_code: 'US-VA',
|
||||
country_iso_code: 'US',
|
||||
region_name: 'Virginia',
|
||||
location: { lon: -77.2481, lat: 38.6583 },
|
||||
continent_name: ['North America'],
|
||||
region_iso_code: ['US-VA'],
|
||||
country_iso_code: ['US'],
|
||||
region_name: ['Virginia'],
|
||||
location: {
|
||||
lon: [-77.2481],
|
||||
lat: [38.6583],
|
||||
},
|
||||
},
|
||||
flowTarget: 'source',
|
||||
flowTarget: FlowTargetSourceDest.source,
|
||||
},
|
||||
autonomous_system: { number: 15169, name: 'Google LLC' },
|
||||
flows: 1,
|
||||
|
@ -717,11 +733,14 @@ export const formattedSearchStrategyResponse = {
|
|||
ip: '151.101.2.217',
|
||||
location: {
|
||||
geo: {
|
||||
continent_name: 'North America',
|
||||
country_iso_code: 'US',
|
||||
location: { lon: -97.822, lat: 37.751 },
|
||||
continent_name: ['North America'],
|
||||
country_iso_code: ['US'],
|
||||
location: {
|
||||
lon: [-97.822],
|
||||
lat: [37.751],
|
||||
},
|
||||
},
|
||||
flowTarget: 'source',
|
||||
flowTarget: FlowTargetSourceDest.source,
|
||||
},
|
||||
autonomous_system: { number: 54113, name: 'Fastly' },
|
||||
flows: 24,
|
||||
|
@ -739,14 +758,17 @@ export const formattedSearchStrategyResponse = {
|
|||
ip: '91.189.91.38',
|
||||
location: {
|
||||
geo: {
|
||||
continent_name: 'North America',
|
||||
region_iso_code: 'US-MA',
|
||||
city_name: 'Boston',
|
||||
country_iso_code: 'US',
|
||||
region_name: 'Massachusetts',
|
||||
location: { lon: -71.0631, lat: 42.3562 },
|
||||
continent_name: ['North America'],
|
||||
region_iso_code: ['US-MA'],
|
||||
city_name: ['Boston'],
|
||||
country_iso_code: ['US'],
|
||||
region_name: ['Massachusetts'],
|
||||
location: {
|
||||
lon: [-71.0631],
|
||||
lat: [42.3562],
|
||||
},
|
||||
},
|
||||
flowTarget: 'source',
|
||||
flowTarget: FlowTargetSourceDest.source,
|
||||
},
|
||||
autonomous_system: { number: 41231, name: 'Canonical Group Limited' },
|
||||
flows: 1,
|
||||
|
@ -764,11 +786,14 @@ export const formattedSearchStrategyResponse = {
|
|||
ip: '193.228.91.123',
|
||||
location: {
|
||||
geo: {
|
||||
continent_name: 'North America',
|
||||
country_iso_code: 'US',
|
||||
location: { lon: -97.822, lat: 37.751 },
|
||||
continent_name: ['North America'],
|
||||
country_iso_code: ['US'],
|
||||
location: {
|
||||
lon: [-97.822],
|
||||
lat: [37.751],
|
||||
},
|
||||
},
|
||||
flowTarget: 'source',
|
||||
flowTarget: FlowTargetSourceDest.source,
|
||||
},
|
||||
autonomous_system: { number: 133766, name: 'YHSRV.LLC' },
|
||||
flows: 33,
|
||||
|
@ -846,6 +871,7 @@ export const formattedSearchStrategyResponse = {
|
|||
},
|
||||
pageInfo: { activePage: 0, fakeTotalCount: 50, showMorePagesIndicator: true },
|
||||
totalCount: 738,
|
||||
rawResponse: {} as NetworkTopNFlowStrategyResponse['rawResponse'],
|
||||
};
|
||||
|
||||
export const expectedDsl = {
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
AutonomousSystemItem,
|
||||
} from '../../../../../../common/search_strategy';
|
||||
import { getOppositeField } from '../helpers';
|
||||
import { formatResponseObjectValues } from '../../../../helpers/format_response_object_values';
|
||||
|
||||
export const getTopNFlowEdges = (
|
||||
response: IEsSearchResponse<unknown>,
|
||||
|
@ -66,12 +67,14 @@ const getFlowTargetFromString = (flowAsString: string) =>
|
|||
const getGeoItem = (result: NetworkTopNFlowBuckets): GeoItem | null =>
|
||||
result.location.top_geo.hits.hits.length > 0 && result.location.top_geo.hits.hits[0]._source
|
||||
? {
|
||||
geo: getOr(
|
||||
'',
|
||||
`location.top_geo.hits.hits[0]._source.${
|
||||
Object.keys(result.location.top_geo.hits.hits[0]._source)[0]
|
||||
}.geo`,
|
||||
result
|
||||
geo: formatResponseObjectValues(
|
||||
getOr(
|
||||
'',
|
||||
`location.top_geo.hits.hits[0]._source.${
|
||||
Object.keys(result.location.top_geo.hits.hits[0]._source)[0]
|
||||
}.geo`,
|
||||
result
|
||||
)
|
||||
),
|
||||
flowTarget: getFlowTargetFromString(
|
||||
Object.keys(result.location.top_geo.hits.hits[0]._source)[0]
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { get, has, merge, uniq } from 'lodash/fp';
|
||||
import { EventHit, TimelineEdges } from '../../../../../../common/search_strategy';
|
||||
import { toArray } from '../../../../helpers/to_array';
|
||||
import { toStringArray } from '../../../../helpers/to_array';
|
||||
|
||||
export const formatTimelineData = (
|
||||
dataFields: readonly string[],
|
||||
|
@ -56,8 +56,8 @@ const mergeTimelineFieldsWithHit = <T>(
|
|||
{
|
||||
field: fieldName,
|
||||
value: specialFields.includes(esField)
|
||||
? toArray(get(esField, hit))
|
||||
: toArray(get(esField, hit._source)),
|
||||
? toStringArray(get(esField, hit))
|
||||
: toStringArray(get(esField, hit._source)),
|
||||
},
|
||||
]
|
||||
: get('node.data', flattenedFields),
|
||||
|
@ -68,7 +68,7 @@ const mergeTimelineFieldsWithHit = <T>(
|
|||
...fieldName.split('.').reduceRight(
|
||||
// @ts-expect-error
|
||||
(obj, next) => ({ [next]: obj }),
|
||||
toArray<string>(get(esField, hit._source))
|
||||
toStringArray<string>(get(esField, hit._source))
|
||||
),
|
||||
}
|
||||
: get('node.ecs', flattenedFields),
|
||||
|
|
|
@ -14,6 +14,7 @@ const TO = '3000-01-01T00:00:00.000Z';
|
|||
|
||||
// typical values that have to change after an update from "scripts/es_archiver"
|
||||
const HOST_NAME = 'zeek-newyork-sha-aa8df15';
|
||||
const LAST_SUCCESS_SOURCE_IP = '8.42.77.171';
|
||||
const TOTAL_COUNT = 3;
|
||||
const EDGE_LENGTH = 1;
|
||||
|
||||
|
@ -78,6 +79,9 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
expect(authentications.edges.length).to.be(EDGE_LENGTH);
|
||||
expect(authentications.totalCount).to.be(TOTAL_COUNT);
|
||||
expect(authentications.edges[0]!.node.lastSuccess!.source!.ip).to.eql([
|
||||
LAST_SUCCESS_SOURCE_IP,
|
||||
]);
|
||||
expect(authentications.edges[0]!.node.lastSuccess!.host!.name).to.eql([HOST_NAME]);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue