[Security Solution] Fix networkTopNFlow search strategy response (#80362)

This commit is contained in:
Patryk Kopyciński 2020-10-14 21:50:49 +02:00 committed by GitHub
parent c39581d89e
commit 4debc51576
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 323 additions and 256 deletions

View file

@ -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[];
}

View file

@ -8,14 +8,9 @@ import { GeoEcs } from '../geo';
export interface SourceEcs {
bytes?: number[];
ip?: string[];
port?: number[];
domain?: string[];
geo?: GeoEcs;
packets?: number[];
}

View file

@ -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 {

View file

@ -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={{

View file

@ -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}
/>
</>
);

View file

@ -32,4 +32,5 @@ export type AnomaliesQueryTabBodyProps = QueryTabBodyProps & {
updateDateRange?: UpdateDateRange;
hideHistogramIfEmpty?: boolean;
ip?: string;
hostName?: string;
};

View file

@ -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} />,

View file

@ -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 '
);
});
});

View file

@ -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>

View file

@ -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>
`;

View file

@ -2,7 +2,6 @@
exports[`PointToolTipContent renders correctly against snapshot 1`] = `
<PointToolTipContentComponent
closeTooltip={[MockFunction]}
contextId="contextId"
featureProps={
Array [

View file

@ -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>
);
};

View file

@ -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();

View file

@ -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} />;
};

View file

@ -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;

View file

@ -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);

View file

@ -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,

View file

@ -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);

View file

@ -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

View file

@ -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;
};

View file

@ -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}`];

View file

@ -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;
},

View file

@ -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;
}

View file

@ -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;
}, {});

View file

@ -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: {

View file

@ -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 = {

View file

@ -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]

View file

@ -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),

View file

@ -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]);
});
});