mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Adding Private ips to network page (#32527)
* add query for unique private ip display unique private ip update mock data update unit test rename file add unique private ip type add loading icon fix unit test rename variables update integration test build types * update loading icon * update snapshot * fix type * abstract card generator * card itme refactor * create managed kpiNetwork component
This commit is contained in:
parent
e390ed5ddc
commit
8fa9c32116
20 changed files with 464 additions and 131 deletions
|
@ -1,13 +1,29 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NetworkTopNFlow Table Component rendering it renders the default Authentication table 1`] = `
|
||||
exports[`NetworkTopNFlow Table Component rendering it renders loading icons 1`] = `
|
||||
<pure(Component)
|
||||
data={
|
||||
Object {
|
||||
"activeAgents": 60015,
|
||||
"networkEvents": 16,
|
||||
"uniqueFlowId": 10277307,
|
||||
"uniquePrivateIps": 383,
|
||||
}
|
||||
}
|
||||
loading={true}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`NetworkTopNFlow Table Component rendering it renders the default widget 1`] = `
|
||||
<pure(Component)
|
||||
data={
|
||||
Object {
|
||||
"activeAgents": 60015,
|
||||
"networkEvents": 16,
|
||||
"uniqueFlowId": 10277307,
|
||||
"uniquePrivateIps": 383,
|
||||
}
|
||||
}
|
||||
loading={false}
|
||||
/>
|
||||
`;
|
||||
|
|
|
@ -25,10 +25,20 @@ describe('NetworkTopNFlow Table Component', () => {
|
|||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
test('it renders the default Authentication table', () => {
|
||||
test('it renders loading icons', () => {
|
||||
const wrapper = shallow(
|
||||
<ReduxStoreProvider store={store}>
|
||||
<KpiNetworkComponent data={mockData.KpiNetwork} />
|
||||
<KpiNetworkComponent data={mockData.KpiNetwork} loading={true} />
|
||||
</ReduxStoreProvider>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it renders the default widget', () => {
|
||||
const wrapper = shallow(
|
||||
<ReduxStoreProvider store={store}>
|
||||
<KpiNetworkComponent data={mockData.KpiNetwork} loading={false} />
|
||||
</ReduxStoreProvider>
|
||||
);
|
||||
|
||||
|
|
|
@ -10,8 +10,9 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { has } from 'lodash/fp';
|
||||
import { get } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
|
@ -22,37 +23,47 @@ import * as i18n from './translations';
|
|||
|
||||
interface KpiNetworkProps {
|
||||
data: KpiNetworkData;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const kpiNetworkCards = (data: KpiNetworkData) => [
|
||||
{
|
||||
title:
|
||||
has('networkEvents', data) && data.networkEvents !== null
|
||||
? numeral(data.networkEvents).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
description: i18n.NETWORK_EVENTS,
|
||||
},
|
||||
{
|
||||
title:
|
||||
has('uniqueFlowId', data) && data.uniqueFlowId !== null
|
||||
? numeral(data.uniqueFlowId).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
description: i18n.UNIQUE_ID,
|
||||
},
|
||||
{
|
||||
title:
|
||||
has('activeAgents', data) && data.activeAgents !== null
|
||||
? numeral(data.activeAgents).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
description: i18n.ACTIVE_AGENTS,
|
||||
},
|
||||
];
|
||||
export const KpiNetworkComponent = pure<KpiNetworkProps>(({ data }) => (
|
||||
interface CardItemProps {
|
||||
isLoading: boolean;
|
||||
i18nKey: string;
|
||||
data: KpiNetworkData;
|
||||
property: string;
|
||||
}
|
||||
|
||||
const fieldTitleMapping = (isLoading: boolean, title: number | null | undefined) => {
|
||||
return isLoading ? (
|
||||
<EuiLoadingSpinner size="m" />
|
||||
) : title != null ? (
|
||||
numeral(title).format('0,0')
|
||||
) : (
|
||||
getEmptyTagValue()
|
||||
);
|
||||
};
|
||||
|
||||
const CardItem = pure<CardItemProps>(({ isLoading, i18nKey, data, property }) => {
|
||||
const matrixTitle: number | null | undefined = get(property, data);
|
||||
const matrixDescription: string = get(i18nKey, i18n);
|
||||
|
||||
return (
|
||||
<EuiFlexItem key={matrixDescription}>
|
||||
<EuiCard title={fieldTitleMapping(isLoading, matrixTitle)} description={matrixDescription} />
|
||||
</EuiFlexItem>
|
||||
);
|
||||
});
|
||||
|
||||
export const KpiNetworkComponent = pure<KpiNetworkProps>(({ data, loading }) => (
|
||||
<EuiFlexGroup>
|
||||
{kpiNetworkCards(data).map(item => (
|
||||
<EuiFlexItem key={item.description}>
|
||||
<EuiCard title={item.title} description={item.description} />
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
<CardItem isLoading={loading} i18nKey="NETWORK_EVENTS" data={data} property="networkEvents" />
|
||||
<CardItem isLoading={loading} i18nKey="UNIQUE_ID" data={data} property="uniqueFlowId" />
|
||||
<CardItem isLoading={loading} i18nKey="ACTIVE_AGENTS" data={data} property="activeAgents" />
|
||||
<CardItem
|
||||
isLoading={loading}
|
||||
i18nKey="UNIQUE_PRIVATE_IP"
|
||||
data={data}
|
||||
property="uniquePrivateIps"
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
));
|
||||
|
|
|
@ -11,5 +11,6 @@ export const mockData: { KpiNetwork: KpiNetworkData } = {
|
|||
networkEvents: 16,
|
||||
uniqueFlowId: 10277307,
|
||||
activeAgents: 60015,
|
||||
uniquePrivateIps: 383,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -17,3 +17,14 @@ export const UNIQUE_ID = i18n.translate('xpack.secops.kpiNetwork.source.uniquiId
|
|||
export const ACTIVE_AGENTS = i18n.translate('xpack.secops.kpiNetwork.source.activeAgentsTitle', {
|
||||
defaultMessage: 'Active Agents',
|
||||
});
|
||||
|
||||
export const UNIQUE_PRIVATE_IP = i18n.translate(
|
||||
'xpack.secops.kpiNetwork.source.uniquePrivateIpsTitle',
|
||||
{
|
||||
defaultMessage: 'Unique Private IP',
|
||||
}
|
||||
);
|
||||
|
||||
export const LOADING = i18n.translate('xpack.secops.kpiNetwork.source.loadingDescription', {
|
||||
defaultMessage: 'Loading',
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ export const kpiNetworkQuery = gql`
|
|||
networkEvents
|
||||
uniqueFlowId
|
||||
activeAgents
|
||||
uniquePrivateIps
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Query } from 'react-apollo';
|
|||
import { pure } from 'recompose';
|
||||
|
||||
import { GetKpiNetworkQuery, KpiNetworkData } from '../../graphql/types';
|
||||
import { inputsModel } from '../../store';
|
||||
import { createFilter } from '../helpers';
|
||||
import { QueryTemplateProps } from '../query_template';
|
||||
|
||||
|
@ -18,6 +19,8 @@ import { kpiNetworkQuery } from './index.gql_query';
|
|||
export interface KpiNetworkArgs {
|
||||
id: string;
|
||||
kpiNetwork: KpiNetworkData;
|
||||
loading: boolean;
|
||||
refetch: inputsModel.Refetch;
|
||||
}
|
||||
|
||||
export interface KpiNetworkProps extends QueryTemplateProps {
|
||||
|
@ -40,11 +43,13 @@ export const KpiNetworkQuery = pure<KpiNetworkProps>(
|
|||
filterQuery: createFilter(filterQuery),
|
||||
}}
|
||||
>
|
||||
{({ data }) => {
|
||||
{({ data, loading, refetch }) => {
|
||||
const kpiNetwork = getOr({}, `source.KpiNetwork`, data);
|
||||
return children({
|
||||
id,
|
||||
kpiNetwork,
|
||||
loading,
|
||||
refetch,
|
||||
});
|
||||
}}
|
||||
</Query>
|
||||
|
|
|
@ -3091,6 +3091,14 @@
|
|||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "uniquePrivateIps",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
|
@ -4211,11 +4219,7 @@
|
|||
"name": "networkEvents",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "Float", "ofType": null }
|
||||
},
|
||||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
|
@ -4223,11 +4227,7 @@
|
|||
"name": "uniqueFlowId",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "Float", "ofType": null }
|
||||
},
|
||||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
|
|
|
@ -715,11 +715,13 @@ export interface SayMyName {
|
|||
}
|
||||
|
||||
export interface KpiNetworkData {
|
||||
networkEvents: number;
|
||||
networkEvents?: number | null;
|
||||
|
||||
uniqueFlowId: number;
|
||||
uniqueFlowId?: number | null;
|
||||
|
||||
activeAgents?: number | null;
|
||||
|
||||
uniquePrivateIps?: number | null;
|
||||
}
|
||||
|
||||
// ====================================================
|
||||
|
@ -1449,11 +1451,13 @@ export namespace GetKpiNetworkQuery {
|
|||
export type KpiNetwork = {
|
||||
__typename?: 'KpiNetworkData';
|
||||
|
||||
networkEvents: number;
|
||||
networkEvents?: number | null;
|
||||
|
||||
uniqueFlowId: number;
|
||||
uniqueFlowId?: number | null;
|
||||
|
||||
activeAgents?: number | null;
|
||||
|
||||
uniquePrivateIps?: number | null;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -31,13 +31,12 @@ const basePath = chrome.getBasePath();
|
|||
|
||||
const NetworkTopNFlowTableManage = manageQuery(NetworkTopNFlowTable);
|
||||
const NetworkDnsTableManage = manageQuery(NetworkDnsTable);
|
||||
|
||||
const KpiNetworkComponentManage = manageQuery(KpiNetworkComponent);
|
||||
interface NetworkComponentReduxProps {
|
||||
filterQuery: string;
|
||||
}
|
||||
|
||||
type NetworkComponentProps = NetworkComponentReduxProps;
|
||||
|
||||
const NetworkComponent = pure<NetworkComponentProps>(({ filterQuery }) => (
|
||||
<WithSource sourceId="default" indexTypes={[IndexType.FILEBEAT, IndexType.PACKETBEAT]}>
|
||||
{({ filebeatIndicesExist, indexPattern }) =>
|
||||
|
@ -56,7 +55,15 @@ const NetworkComponent = pure<NetworkComponentProps>(({ filterQuery }) => (
|
|||
sourceId="default"
|
||||
startDate={from}
|
||||
>
|
||||
{({ kpiNetwork }) => <KpiNetworkComponent data={kpiNetwork} />}
|
||||
{({ kpiNetwork, loading, id, refetch }) => (
|
||||
<KpiNetworkComponentManage
|
||||
id={id}
|
||||
setQuery={setQuery}
|
||||
refetch={refetch}
|
||||
data={kpiNetwork}
|
||||
loading={loading}
|
||||
/>
|
||||
)}
|
||||
</KpiNetworkQuery>
|
||||
<EuiSpacer size="m" />
|
||||
<NetworkTopNFlowQuery
|
||||
|
|
|
@ -15,6 +15,7 @@ export const mockKpiNetworkData: { KpiNetwork: KpiNetworkData } = {
|
|||
networkEvents: 0,
|
||||
uniqueFlowId: 0,
|
||||
activeAgents: 0,
|
||||
uniquePrivateIps: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -8,9 +8,10 @@ import gql from 'graphql-tag';
|
|||
|
||||
export const kpiNetworkSchema = gql`
|
||||
type KpiNetworkData {
|
||||
networkEvents: Float!
|
||||
uniqueFlowId: Float!
|
||||
networkEvents: Float
|
||||
uniqueFlowId: Float
|
||||
activeAgents: Float
|
||||
uniquePrivateIps: Float
|
||||
}
|
||||
|
||||
extend type Source {
|
||||
|
|
|
@ -744,11 +744,13 @@ export interface SayMyName {
|
|||
}
|
||||
|
||||
export interface KpiNetworkData {
|
||||
networkEvents: number;
|
||||
networkEvents?: number | null;
|
||||
|
||||
uniqueFlowId: number;
|
||||
uniqueFlowId?: number | null;
|
||||
|
||||
activeAgents?: number | null;
|
||||
|
||||
uniquePrivateIps?: number | null;
|
||||
}
|
||||
|
||||
// ====================================================
|
||||
|
@ -3311,20 +3313,22 @@ export namespace SayMyNameResolvers {
|
|||
|
||||
export namespace KpiNetworkDataResolvers {
|
||||
export interface Resolvers<Context = SecOpsContext, TypeParent = KpiNetworkData> {
|
||||
networkEvents?: NetworkEventsResolver<number, TypeParent, Context>;
|
||||
networkEvents?: NetworkEventsResolver<number | null, TypeParent, Context>;
|
||||
|
||||
uniqueFlowId?: UniqueFlowIdResolver<number, TypeParent, Context>;
|
||||
uniqueFlowId?: UniqueFlowIdResolver<number | null, TypeParent, Context>;
|
||||
|
||||
activeAgents?: ActiveAgentsResolver<number | null, TypeParent, Context>;
|
||||
|
||||
uniquePrivateIps?: UniquePrivateIpsResolver<number | null, TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type NetworkEventsResolver<
|
||||
R = number,
|
||||
R = number | null,
|
||||
Parent = KpiNetworkData,
|
||||
Context = SecOpsContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type UniqueFlowIdResolver<
|
||||
R = number,
|
||||
R = number | null,
|
||||
Parent = KpiNetworkData,
|
||||
Context = SecOpsContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
|
@ -3333,4 +3337,9 @@ export namespace KpiNetworkDataResolvers {
|
|||
Parent = KpiNetworkData,
|
||||
Context = SecOpsContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type UniquePrivateIpsResolver<
|
||||
R = number | null,
|
||||
Parent = KpiNetworkData,
|
||||
Context = SecOpsContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
|
|
|
@ -4,67 +4,104 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { cloneDeep } from 'lodash/fp';
|
||||
|
||||
import { KpiNetworkData } from '../../graphql/types';
|
||||
import { FrameworkAdapter, FrameworkRequest } from '../framework';
|
||||
|
||||
import { ElasticsearchKpiNetworkAdapter } from './elasticsearch_adapter';
|
||||
import { mockOptions, mockRequest, mockResponse, mockResult } from './mock';
|
||||
import { mockMsearchOptions, mockOptions, mockRequest, mockResponse, mockResult } from './mock';
|
||||
import * as generalQueryDsl from './query_general.dsl';
|
||||
import * as uniquePrvateIpQueryDsl from './query_unique_private_ips.dsl';
|
||||
|
||||
describe('Network Kpi elasticsearch_adapter', () => {
|
||||
describe('Happy Path - get Data', () => {
|
||||
const mockCallWithRequest = jest.fn();
|
||||
mockCallWithRequest.mockResolvedValue(mockResponse);
|
||||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
};
|
||||
jest.mock('../framework', () => ({
|
||||
callWithRequest: mockCallWithRequest,
|
||||
}));
|
||||
const mockCallWithRequest = jest.fn();
|
||||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
};
|
||||
let mockBuildQuery: jest.SpyInstance;
|
||||
let mockBuildUniquePrvateIpsQuery: jest.SpyInstance;
|
||||
let EsKpiNetwork: ElasticsearchKpiNetworkAdapter;
|
||||
let data: KpiNetworkData;
|
||||
|
||||
test('getKpiNetwork', async () => {
|
||||
const EsKpiNetwork = new ElasticsearchKpiNetworkAdapter(mockFramework);
|
||||
const data: KpiNetworkData = await EsKpiNetwork.getKpiNetwork(
|
||||
mockRequest as FrameworkRequest,
|
||||
mockOptions
|
||||
);
|
||||
describe('getKpiNetwork - call stack', () => {
|
||||
beforeAll(async () => {
|
||||
mockCallWithRequest.mockResolvedValue(mockResponse);
|
||||
jest.mock('../framework', () => ({
|
||||
callWithRequest: mockCallWithRequest,
|
||||
}));
|
||||
mockBuildQuery = jest.spyOn(generalQueryDsl, 'buildGeneralQuery').mockReturnValue([]);
|
||||
mockBuildUniquePrvateIpsQuery = jest
|
||||
.spyOn(uniquePrvateIpQueryDsl, 'buildUniquePrvateIpQuery')
|
||||
.mockReturnValue([]);
|
||||
EsKpiNetwork = new ElasticsearchKpiNetworkAdapter(mockFramework);
|
||||
data = await EsKpiNetwork.getKpiNetwork(mockRequest as FrameworkRequest, mockOptions);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mockCallWithRequest.mockReset();
|
||||
mockBuildQuery.mockRestore();
|
||||
mockBuildUniquePrvateIpsQuery.mockRestore();
|
||||
});
|
||||
|
||||
test('should build general query with correct option', () => {
|
||||
expect(mockBuildQuery).toHaveBeenCalledWith(mockOptions);
|
||||
});
|
||||
|
||||
test('should build query for unique private ip (source) with correct option', () => {
|
||||
expect(mockBuildUniquePrvateIpsQuery).toHaveBeenCalledWith('source', mockOptions);
|
||||
});
|
||||
|
||||
test('should build query for unique private ip (destination) with correct option', () => {
|
||||
expect(mockBuildUniquePrvateIpsQuery).toHaveBeenCalledWith('destination', mockOptions);
|
||||
});
|
||||
|
||||
test('should send msearch request', () => {
|
||||
expect(mockCallWithRequest).toHaveBeenCalledWith(mockRequest, 'msearch', mockMsearchOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Happy Path - get Data', () => {
|
||||
beforeAll(async () => {
|
||||
mockCallWithRequest.mockResolvedValue(mockResponse);
|
||||
jest.mock('../framework', () => ({
|
||||
callWithRequest: mockCallWithRequest,
|
||||
}));
|
||||
EsKpiNetwork = new ElasticsearchKpiNetworkAdapter(mockFramework);
|
||||
data = await EsKpiNetwork.getKpiNetwork(mockRequest as FrameworkRequest, mockOptions);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mockCallWithRequest.mockReset();
|
||||
});
|
||||
|
||||
test('getKpiNetwork - response with data', () => {
|
||||
expect(data).toEqual(mockResult);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Unhappy Path - No data', () => {
|
||||
const mockNoDataResponse = cloneDeep(mockResponse);
|
||||
mockNoDataResponse.hits.total.value = 0;
|
||||
mockNoDataResponse.aggregations.unique_flow_id.value = 0;
|
||||
mockNoDataResponse.aggregations.active_agents.value = 0;
|
||||
const mockCallWithRequest = jest.fn();
|
||||
mockCallWithRequest.mockResolvedValue(mockNoDataResponse);
|
||||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
};
|
||||
jest.mock('../framework', () => ({
|
||||
callWithRequest: mockCallWithRequest,
|
||||
}));
|
||||
beforeAll(async () => {
|
||||
mockCallWithRequest.mockResolvedValue(null);
|
||||
jest.mock('../framework', () => ({
|
||||
callWithRequest: mockCallWithRequest,
|
||||
}));
|
||||
EsKpiNetwork = new ElasticsearchKpiNetworkAdapter(mockFramework);
|
||||
data = await EsKpiNetwork.getKpiNetwork(mockRequest as FrameworkRequest, mockOptions);
|
||||
});
|
||||
|
||||
test('getKpiNetwork', async () => {
|
||||
const EsKpiNetwork = new ElasticsearchKpiNetworkAdapter(mockFramework);
|
||||
const data: KpiNetworkData = await EsKpiNetwork.getKpiNetwork(
|
||||
mockRequest as FrameworkRequest,
|
||||
mockOptions
|
||||
);
|
||||
afterAll(() => {
|
||||
mockCallWithRequest.mockReset();
|
||||
});
|
||||
|
||||
test('getKpiNetwork - response without data', async () => {
|
||||
expect(data).toEqual({
|
||||
networkEvents: 0,
|
||||
uniqueFlowId: 0,
|
||||
activeAgents: 0,
|
||||
networkEvents: null,
|
||||
uniqueFlowId: null,
|
||||
activeAgents: null,
|
||||
uniquePrivateIps: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,14 +4,20 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { getOr } from 'lodash/fp';
|
||||
import { getOr, isNil } from 'lodash/fp';
|
||||
|
||||
import { KpiNetworkData } from '../../graphql/types';
|
||||
import { FrameworkAdapter, FrameworkRequest, RequestBasicOptions } from '../framework';
|
||||
import {
|
||||
DatabaseMultiResponse,
|
||||
FrameworkAdapter,
|
||||
FrameworkRequest,
|
||||
RequestBasicOptions,
|
||||
} from '../framework';
|
||||
import { TermAggregation } from '../types';
|
||||
|
||||
import { buildQuery } from './query.dsl';
|
||||
import { KpiNetworkAdapter, KpiNetworkHit } from './types';
|
||||
import { buildGeneralQuery } from './query_general.dsl';
|
||||
import { buildUniquePrvateIpQuery } from './query_unique_private_ips.dsl';
|
||||
import { KpiNetworkAdapter, KpiNetworkESMSearchBody, KpiNetworkHit } from './types';
|
||||
|
||||
export class ElasticsearchKpiNetworkAdapter implements KpiNetworkAdapter {
|
||||
constructor(private readonly framework: FrameworkAdapter) {}
|
||||
|
@ -20,16 +26,55 @@ export class ElasticsearchKpiNetworkAdapter implements KpiNetworkAdapter {
|
|||
request: FrameworkRequest,
|
||||
options: RequestBasicOptions
|
||||
): Promise<KpiNetworkData> {
|
||||
const generalQuery: KpiNetworkESMSearchBody[] = buildGeneralQuery(options);
|
||||
const uniqueSourcePrivateIpsQuery: KpiNetworkESMSearchBody[] = buildUniquePrvateIpQuery(
|
||||
'source',
|
||||
options
|
||||
);
|
||||
const uniqueDestinationPrivateIpsQuery: KpiNetworkESMSearchBody[] = buildUniquePrvateIpQuery(
|
||||
'destination',
|
||||
options
|
||||
);
|
||||
const response = await this.framework.callWithRequest<KpiNetworkHit, TermAggregation>(
|
||||
request,
|
||||
'search',
|
||||
buildQuery(options)
|
||||
'msearch',
|
||||
{
|
||||
body: [
|
||||
...generalQuery,
|
||||
...uniqueSourcePrivateIpsQuery,
|
||||
...uniqueDestinationPrivateIpsQuery,
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
networkEvents: getOr(null, 'hits.total.value', response),
|
||||
uniqueFlowId: getOr(null, 'aggregations.unique_flow_id.value', response),
|
||||
activeAgents: getOr(null, 'aggregations.active_agents.value', response),
|
||||
networkEvents: getOr(null, 'responses.0.hits.total.value', response),
|
||||
uniqueFlowId: getOr(null, 'responses.0.aggregations.unique_flow_id.value', response),
|
||||
activeAgents: getOr(null, 'responses.0.aggregations.active_agents.value', response),
|
||||
uniquePrivateIps: this.combineUniquePrivateIp(response),
|
||||
};
|
||||
}
|
||||
|
||||
private combineUniquePrivateIp(
|
||||
response: DatabaseMultiResponse<KpiNetworkHit, TermAggregation>
|
||||
): number | null {
|
||||
const uniqueSourcePrivateIp = getOr(
|
||||
null,
|
||||
'responses.1.aggregations.unique_private_ips.value',
|
||||
response
|
||||
);
|
||||
const uniqueDestinationPrivateIp = getOr(
|
||||
null,
|
||||
'responses.2.aggregations.unique_private_ips.value',
|
||||
response
|
||||
);
|
||||
if (!isNil(uniqueSourcePrivateIp) && !isNil(uniqueDestinationPrivateIp)) {
|
||||
return uniqueSourcePrivateIp + uniqueDestinationPrivateIp;
|
||||
} else if (isNil(uniqueSourcePrivateIp) && !isNil(uniqueDestinationPrivateIp)) {
|
||||
return uniqueDestinationPrivateIp;
|
||||
} else if (!isNil(uniqueSourcePrivateIp) && isNil(uniqueDestinationPrivateIp)) {
|
||||
return uniqueSourcePrivateIp;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,10 @@ export const mockOptions: RequestBasicOptions = {
|
|||
filterQuery: {},
|
||||
};
|
||||
|
||||
export const mockMsearchOptions = {
|
||||
body: [],
|
||||
};
|
||||
|
||||
export const mockRequest = {
|
||||
params: {},
|
||||
payload: {
|
||||
|
@ -34,21 +38,35 @@ export const mockRequest = {
|
|||
filterQuery: '',
|
||||
},
|
||||
query:
|
||||
'query GetKpiNetworkQuery($sourceId: ID!, $timerange: TimerangeInput!, $filterQuery: String) {\n source(id: $sourceId) {\n id\n KpiNetwork(timerange: $timerange, filterQuery: $filterQuery) {\n networkEvents\n uniqueFlowId\n activeAgents\n }\n }\n }',
|
||||
'query GetKpiNetworkQuery($sourceId: ID!, $timerange: TimerangeInput!, $filterQuery: String) {\n source(id: $sourceId) {\n id\n KpiNetwork(timerange: $timerange, filterQuery: $filterQuery) {\n networkEvents\n uniqueFlowId\n activeAgents\n uniquePrivateIps\n }\n }\n }',
|
||||
},
|
||||
query: {},
|
||||
};
|
||||
|
||||
export const mockResponse = {
|
||||
took: 89,
|
||||
timed_out: false,
|
||||
_shards: { total: 18, successful: 18, skipped: 0, failed: 0 },
|
||||
hits: { total: { value: 950867, relation: 'eq' }, max_score: null, hits: [] },
|
||||
aggregations: { unique_flow_id: { value: 50243 }, active_agents: { value: 15 } },
|
||||
responses: [
|
||||
{
|
||||
took: 258,
|
||||
timed_out: false,
|
||||
_shards: { total: 26, successful: 26, skipped: 0, failed: 0 },
|
||||
hits: { total: { value: 950867, relation: 'eq' }, max_score: null, hits: [] },
|
||||
aggregations: { unique_flow_id: { value: 50243 }, active_agents: { value: 15 } },
|
||||
status: 200,
|
||||
},
|
||||
{
|
||||
took: 323,
|
||||
timed_out: false,
|
||||
_shards: { total: 26, successful: 26, skipped: 0, failed: 0 },
|
||||
hits: { total: { value: 406839, relation: 'eq' }, max_score: null, hits: [] },
|
||||
aggregations: { unique_private_ips: { value: 383 } },
|
||||
status: 200,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mockResult = {
|
||||
networkEvents: 950867,
|
||||
uniqueFlowId: 50243,
|
||||
activeAgents: 15,
|
||||
uniquePrivateIps: 383,
|
||||
};
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
import { createQueryFilterClauses } from '../../utils/build_query';
|
||||
import { RequestBasicOptions } from '../framework';
|
||||
|
||||
const getKpiNetworkFilter = () => [
|
||||
import { KpiNetworkESMSearchBody } from './types';
|
||||
|
||||
const getGeneralQueryFilter = () => [
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -39,7 +41,7 @@ const getKpiNetworkFilter = () => [
|
|||
},
|
||||
];
|
||||
|
||||
export const buildQuery = ({
|
||||
export const buildGeneralQuery = ({
|
||||
filterQuery,
|
||||
timerange: { from, to },
|
||||
sourceConfiguration: {
|
||||
|
@ -47,10 +49,10 @@ export const buildQuery = ({
|
|||
logAlias,
|
||||
packetbeatAlias,
|
||||
},
|
||||
}: RequestBasicOptions) => {
|
||||
}: RequestBasicOptions): KpiNetworkESMSearchBody[] => {
|
||||
const filter = [
|
||||
...createQueryFilterClauses(filterQuery),
|
||||
...getKpiNetworkFilter(),
|
||||
...getGeneralQueryFilter(),
|
||||
{
|
||||
range: {
|
||||
[timestamp]: {
|
||||
|
@ -61,11 +63,13 @@ export const buildQuery = ({
|
|||
},
|
||||
];
|
||||
|
||||
const dslQuery = {
|
||||
allowNoIndices: true,
|
||||
index: [logAlias, packetbeatAlias],
|
||||
ignoreUnavailable: true,
|
||||
body: {
|
||||
const dslQuery = [
|
||||
{
|
||||
index: [logAlias, packetbeatAlias],
|
||||
allowNoIndices: true,
|
||||
ignoreUnavailable: true,
|
||||
},
|
||||
{
|
||||
aggregations: {
|
||||
unique_flow_id: {
|
||||
cardinality: {
|
||||
|
@ -86,7 +90,7 @@ export const buildQuery = ({
|
|||
size: 0,
|
||||
track_total_hits: true,
|
||||
},
|
||||
};
|
||||
];
|
||||
|
||||
return dslQuery;
|
||||
};
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* 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 { createQueryFilterClauses } from '../../utils/build_query';
|
||||
import { RequestBasicOptions } from '../framework';
|
||||
|
||||
import { KpiNetworkESMSearchBody, UniquePrivateAttributeQuery } from './types';
|
||||
|
||||
const getUniquePrivateIpsFilter = (attrQuery: UniquePrivateAttributeQuery) => [
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
match: {
|
||||
[`${attrQuery}.ip`]: '10.0.0.0/8',
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
match: {
|
||||
[`${attrQuery}.ip`]: '192.168.0.0/16',
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
match: {
|
||||
[`${attrQuery}.ip`]: '172.16.0.0/12',
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
[`${attrQuery}.ip`]: 'fd00::/8',
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const buildUniquePrvateIpQuery = (
|
||||
attrQuery: UniquePrivateAttributeQuery,
|
||||
{
|
||||
filterQuery,
|
||||
timerange: { from, to },
|
||||
sourceConfiguration: {
|
||||
fields: { timestamp },
|
||||
logAlias,
|
||||
packetbeatAlias,
|
||||
},
|
||||
}: RequestBasicOptions
|
||||
): KpiNetworkESMSearchBody[] => {
|
||||
const filter = [
|
||||
...createQueryFilterClauses(filterQuery),
|
||||
...getUniquePrivateIpsFilter(attrQuery),
|
||||
{
|
||||
range: {
|
||||
[timestamp]: {
|
||||
gte: from,
|
||||
lte: to,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const dslQuery = [
|
||||
{
|
||||
allowNoIndices: true,
|
||||
index: [logAlias, packetbeatAlias],
|
||||
ignoreUnavailable: true,
|
||||
},
|
||||
{
|
||||
aggregations: {
|
||||
unique_private_ips: {
|
||||
cardinality: {
|
||||
field: `${attrQuery}.ip`,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
filter,
|
||||
},
|
||||
},
|
||||
size: 0,
|
||||
track_total_hits: true,
|
||||
},
|
||||
];
|
||||
|
||||
return dslQuery;
|
||||
};
|
|
@ -21,3 +21,20 @@ export interface KpiNetworkHit extends SearchHit {
|
|||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface KpiNetworkHeader {
|
||||
index: string[] | string;
|
||||
allowNoIndices?: boolean;
|
||||
ignoreUnavailable?: boolean;
|
||||
}
|
||||
|
||||
export interface KpiNetworkBody {
|
||||
query?: object;
|
||||
aggregations?: object;
|
||||
size?: number;
|
||||
track_total_hits?: boolean;
|
||||
}
|
||||
|
||||
export type KpiNetworkESMSearchBody = KpiNetworkBody | KpiNetworkHeader;
|
||||
|
||||
export type UniquePrivateAttributeQuery = 'source' | 'destination';
|
||||
|
|
|
@ -38,6 +38,7 @@ const kpiNetworkTests: KbnTestProvider = ({ getService }) => {
|
|||
expect(kpiNetwork!.networkEvents).to.be(6157);
|
||||
expect(kpiNetwork!.uniqueFlowId).to.be(712);
|
||||
expect(kpiNetwork!.activeAgents).to.equal(1);
|
||||
expect(kpiNetwork!.uniquePrivateIps).to.equal(17);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -67,6 +68,7 @@ const kpiNetworkTests: KbnTestProvider = ({ getService }) => {
|
|||
expect(kpiNetwork!.networkEvents).to.be(6157);
|
||||
expect(kpiNetwork!.uniqueFlowId).to.be(712);
|
||||
expect(kpiNetwork!.activeAgents).to.equal(1);
|
||||
expect(kpiNetwork!.uniquePrivateIps).to.equal(17);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue