diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts index 63a57c20a85932..a39638e48892dd 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts @@ -9,6 +9,7 @@ export * from './authentications'; export * from './common'; export * from './details'; export * from './first_last_seen'; +export * from './kpi'; export * from './overview'; export * from './uncommon_processes'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/authentications/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/authentications/index.ts new file mode 100644 index 00000000000000..cbf1f32c3b5fa7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/authentications/index.ts @@ -0,0 +1,24 @@ +/* + * 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 { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { Inspect, Maybe } from '../../../../common'; +import { RequestBasicOptions } from '../../..'; +import { HostsKpiHistogramData } from '../common'; + +export interface HostsKpiAuthenticationsHistogramCount { + doc_count: number; +} + +export type HostsKpiAuthenticationsRequestOptions = RequestBasicOptions; + +export interface HostsKpiAuthenticationsStrategyResponse extends IEsSearchResponse { + authenticationsSuccess: Maybe; + authenticationsSuccessHistogram: Maybe; + authenticationsFailure: Maybe; + authenticationsFailureHistogram: Maybe; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/common/index.ts new file mode 100644 index 00000000000000..52e65bb9957964 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/common/index.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +/* + * 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 { Maybe } from '../../../../common'; + +export interface HostsKpiHistogramData { + x?: Maybe; + y?: Maybe; +} + +export interface HostsKpiHistogram { + key_as_string: string; + key: number; + doc_count: number; + count: T; +} + +export interface HostsKpiGeneralHistogramCount { + value: number; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/hosts/index.ts new file mode 100644 index 00000000000000..8e8bd97c9b60b3 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/hosts/index.ts @@ -0,0 +1,18 @@ +/* + * 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 { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { Inspect, Maybe } from '../../../../common'; +import { RequestBasicOptions } from '../../..'; +import { HostsKpiHistogramData } from '../common'; + +export type HostsKpiHostsRequestOptions = RequestBasicOptions; + +export interface HostsKpiHostsStrategyResponse extends IEsSearchResponse { + hosts: Maybe; + hostsHistogram: Maybe; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts new file mode 100644 index 00000000000000..dc34f619e0362d --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +export * from './authentications'; +export * from './common'; +export * from './hosts'; +export * from './unique_ips'; + +import { HostsKpiAuthenticationsStrategyResponse } from './authentications'; +import { HostsKpiHostsStrategyResponse } from './hosts'; +import { HostsKpiUniqueIpsStrategyResponse } from './unique_ips'; + +export enum HostsKpiQueries { + kpiAuthentications = 'hostsKpiAuthentications', + kpiHosts = 'hostsKpiHosts', + kpiUniqueIps = 'hostsKpiUniqueIps', +} + +export type HostsKpiStrategyResponse = + | Omit + | Omit + | Omit; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/unique_ips/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/unique_ips/index.ts new file mode 100644 index 00000000000000..18a603725f401f --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/unique_ips/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { Inspect, Maybe } from '../../../../common'; +import { RequestBasicOptions } from '../../..'; +import { HostsKpiHistogramData } from '../common'; + +export type HostsKpiUniqueIpsRequestOptions = RequestBasicOptions; + +export interface HostsKpiUniqueIpsStrategyResponse extends IEsSearchResponse { + uniqueSourceIps: Maybe; + uniqueSourceIpsHistogram: Maybe; + uniqueDestinationIps: Maybe; + uniqueDestinationIpsHistogram: Maybe; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts index 95f3cd4fd7da7f..cfcf613b662bca 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -20,6 +20,13 @@ import { HostsStrategyResponse, HostUncommonProcessesStrategyResponse, HostUncommonProcessesRequestOptions, + HostsKpiQueries, + HostsKpiAuthenticationsStrategyResponse, + HostsKpiAuthenticationsRequestOptions, + HostsKpiHostsStrategyResponse, + HostsKpiHostsRequestOptions, + HostsKpiUniqueIpsStrategyResponse, + HostsKpiUniqueIpsRequestOptions, } from './hosts'; import { NetworkQueries, @@ -70,6 +77,7 @@ export * from './network'; export type FactoryQueryTypes = | HostsQueries + | HostsKpiQueries | NetworkQueries | NetworkKpiQueries | typeof MatrixHistogramQuery; @@ -106,6 +114,12 @@ export type StrategyResponseType = T extends HostsQ ? HostFirstLastSeenStrategyResponse : T extends HostsQueries.uncommonProcesses ? HostUncommonProcessesStrategyResponse + : T extends HostsKpiQueries.kpiAuthentications + ? HostsKpiAuthenticationsStrategyResponse + : T extends HostsKpiQueries.kpiHosts + ? HostsKpiHostsStrategyResponse + : T extends HostsKpiQueries.kpiUniqueIps + ? HostsKpiUniqueIpsStrategyResponse : T extends NetworkQueries.details ? NetworkDetailsStrategyResponse : T extends NetworkQueries.dns @@ -148,6 +162,12 @@ export type StrategyRequestType = T extends HostsQu ? HostFirstLastSeenRequestOptions : T extends HostsQueries.uncommonProcesses ? HostUncommonProcessesRequestOptions + : T extends HostsKpiQueries.kpiAuthentications + ? HostsKpiAuthenticationsRequestOptions + : T extends HostsKpiQueries.kpiHosts + ? HostsKpiHostsRequestOptions + : T extends HostsKpiQueries.kpiUniqueIps + ? HostsKpiUniqueIpsRequestOptions : T extends NetworkQueries.details ? NetworkDetailsRequestOptions : T extends NetworkQueries.dns diff --git a/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx index 664d8b2ff5598d..310d4c52ec5bce 100644 --- a/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx @@ -39,8 +39,10 @@ import { } from '../../mock'; import { State, createStore } from '../../store'; import { Provider as ReduxStoreProvider } from 'react-redux'; -import { KpiHostsData } from '../../../graphql/types'; -import { NetworkKpiStrategyResponse } from '../../../../common/search_strategy'; +import { + HostsKpiStrategyResponse, + NetworkKpiStrategyResponse, +} from '../../../../common/search_strategy'; const from = '2019-06-15T06:00:00.000Z'; const to = '2019-06-18T06:00:00.000Z'; @@ -242,7 +244,7 @@ describe('useKpiMatrixStatus', () => { data, }: { fieldsMapping: Readonly; - data: NetworkKpiStrategyResponse | KpiHostsData; + data: NetworkKpiStrategyResponse | HostsKpiStrategyResponse; }) => { const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( fieldsMapping, diff --git a/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx b/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx index 13a93a784a2c9d..34fb344eed3c4c 100644 --- a/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx @@ -18,8 +18,10 @@ import { get, getOr } from 'lodash/fp'; import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; -import { NetworkKpiStrategyResponse } from '../../../../common/search_strategy'; -import { KpiHostsData } from '../../../graphql/types'; +import { + HostsKpiStrategyResponse, + NetworkKpiStrategyResponse, +} from '../../../../common/search_strategy'; import { AreaChart } from '../charts/areachart'; import { BarChart } from '../charts/barchart'; import { ChartSeriesData, ChartData, ChartSeriesConfigs, UpdateDateRange } from '../charts/common'; @@ -113,12 +115,12 @@ export const barchartConfigs = (config?: { onElementClick?: ElementClickListener export const addValueToFields = ( fields: StatItem[], - data: KpiHostsData | NetworkKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse ): StatItem[] => fields.map((field) => ({ ...field, value: get(field.key, data) })); export const addValueToAreaChart = ( fields: StatItem[], - data: KpiHostsData | NetworkKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse ): ChartSeriesData[] => fields .filter((field) => get(`${field.key}Histogram`, data) != null) @@ -130,7 +132,7 @@ export const addValueToAreaChart = ( export const addValueToBarChart = ( fields: StatItem[], - data: KpiHostsData | NetworkKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse ): ChartSeriesData[] => { if (fields.length === 0) return []; return fields.reduce((acc: ChartSeriesData[], field: StatItem, idx: number) => { @@ -159,7 +161,7 @@ export const addValueToBarChart = ( export const useKpiMatrixStatus = ( mappings: Readonly, - data: KpiHostsData | NetworkKpiStrategyResponse, + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse, id: string, from: string, to: string, diff --git a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts index 0e918275e2b183..d437a6b73f71a4 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts +++ b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; export const AUTHENTICATIONS = i18n.translate( - 'xpack.securitySolution.authenticationsTable.authenticationFailures', + 'xpack.securitySolution.authenticationsTable.authentications', { defaultMessage: 'Authentications', } diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 2b2a35945bdf18..00000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,155 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`kpiHostsComponent render it should render KpiHostDetailsData 1`] = ` - - - - -`; - -exports[`kpiHostsComponent render it should render KpiHostsData 1`] = ` - - - - - -`; - -exports[`kpiHostsComponent render it should render spinner if it is loading 1`] = ` - - - - - -`; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx new file mode 100644 index 00000000000000..09496168274701 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx @@ -0,0 +1,73 @@ +/* + * 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 React from 'react'; + +import { StatItems } from '../../../../common/components/stat_items'; +import { useHostsKpiAuthentications } from '../../../containers/kpi_hosts/authentications'; +import { HostsKpiBaseComponentManage } from '../common'; +import { HostsKpiProps, HostsKpiChartColors } from '../types'; +import * as i18n from './translations'; + +export const fieldsMapping: Readonly = [ + { + key: 'authentication', + fields: [ + { + key: 'authenticationsSuccess', + name: i18n.SUCCESS_CHART_LABEL, + description: i18n.SUCCESS_UNIT_LABEL, + value: null, + color: HostsKpiChartColors.authenticationsSuccess, + icon: 'check', + }, + { + key: 'authenticationsFailure', + name: i18n.FAIL_CHART_LABEL, + description: i18n.FAIL_UNIT_LABEL, + value: null, + color: HostsKpiChartColors.authenticationsFailure, + icon: 'cross', + }, + ], + enableAreaChart: true, + enableBarChart: true, + description: i18n.USER_AUTHENTICATIONS, + }, +]; + +const HostsKpiAuthenticationsComponent: React.FC = ({ + filterQuery, + from, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const [loading, { refetch, id, inspect, ...data }] = useHostsKpiAuthentications({ + filterQuery, + endDate: to, + startDate: from, + skip, + }); + + return ( + + ); +}; + +export const HostsKpiAuthentications = React.memo(HostsKpiAuthenticationsComponent); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/translations.ts similarity index 55% rename from x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts rename to x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/translations.ts index 82543e6f106fa0..5175781159c911 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/translations.ts @@ -3,11 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; -export const HOSTS = i18n.translate('xpack.securitySolution.kpiHosts.hosts.title', { - defaultMessage: 'Hosts', -}); +import { i18n } from '@kbn/i18n'; export const USER_AUTHENTICATIONS = i18n.translate( 'xpack.securitySolution.kpiHosts.userAuthentications.title', @@ -43,35 +40,3 @@ export const FAIL_CHART_LABEL = i18n.translate( defaultMessage: 'Fail', } ); - -export const UNIQUE_IPS = i18n.translate('xpack.securitySolution.kpiHosts.uniqueIps.title', { - defaultMessage: 'Unique IPs', -}); - -export const SOURCE_UNIT_LABEL = i18n.translate( - 'xpack.securitySolution.kpiHosts.uniqueIps.sourceUnitLabel', - { - defaultMessage: 'source', - } -); - -export const DESTINATION_UNIT_LABEL = i18n.translate( - 'xpack.securitySolution.kpiHosts.uniqueIps.destinationUnitLabel', - { - defaultMessage: 'destination', - } -); - -export const SOURCE_CHART_LABEL = i18n.translate( - 'xpack.securitySolution.kpiHosts.uniqueIps.sourceChartLabel', - { - defaultMessage: 'Src.', - } -); - -export const DESTINATION_CHART_LABEL = i18n.translate( - 'xpack.securitySolution.kpiHosts.uniqueIps.destinationChartLabel', - { - defaultMessage: 'Dest.', - } -); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/common/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/common/index.tsx new file mode 100644 index 00000000000000..7c51a503092afa --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/common/index.tsx @@ -0,0 +1,69 @@ +/* + * 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 React from 'react'; + +import { EuiFlexItem, EuiLoadingSpinner, EuiFlexGroup } from '@elastic/eui'; +import styled from 'styled-components'; + +import { manageQuery } from '../../../../common/components/page/manage_query'; +import { HostsKpiStrategyResponse } from '../../../../../common/search_strategy'; +import { + StatItemsComponent, + StatItemsProps, + useKpiMatrixStatus, + StatItems, +} from '../../../../common/components/stat_items'; +import { UpdateDateRange } from '../../../../common/components/charts/common'; + +const kpiWidgetHeight = 247; + +export const FlexGroup = styled(EuiFlexGroup)` + min-height: ${kpiWidgetHeight}px; +`; + +FlexGroup.displayName = 'FlexGroup'; + +export const HostsKpiBaseComponent = React.memo<{ + fieldsMapping: Readonly; + data: HostsKpiStrategyResponse; + loading?: boolean; + id: string; + from: string; + to: string; + narrowDateRange: UpdateDateRange; +}>(({ fieldsMapping, data, id, loading = false, from, to, narrowDateRange }) => { + const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( + fieldsMapping, + data, + id, + from, + to, + narrowDateRange + ); + + if (loading) { + return ( + + + + + + ); + } + + return ( + + {statItemsProps.map((mappedStatItemProps) => ( + + ))} + + ); +}); + +HostsKpiBaseComponent.displayName = 'HostsKpiBaseComponent'; + +export const HostsKpiBaseComponentManage = manageQuery(HostsKpiBaseComponent); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx new file mode 100644 index 00000000000000..b1c4d6331e4504 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx @@ -0,0 +1,62 @@ +/* + * 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 React from 'react'; + +import { StatItems } from '../../../../common/components/stat_items'; +import { useHostsKpiHosts } from '../../../containers/kpi_hosts/hosts'; +import { HostsKpiBaseComponentManage } from '../common'; +import { HostsKpiProps, HostsKpiChartColors } from '../types'; +import * as i18n from './translations'; + +export const fieldsMapping: Readonly = [ + { + key: 'hosts', + fields: [ + { + key: 'hosts', + value: null, + color: HostsKpiChartColors.hosts, + icon: 'storage', + }, + ], + enableAreaChart: true, + description: i18n.HOSTS, + }, +]; + +const HostsKpiHostsComponent: React.FC = ({ + filterQuery, + from, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const [loading, { refetch, id, inspect, ...data }] = useHostsKpiHosts({ + filterQuery, + endDate: to, + startDate: from, + skip, + }); + + return ( + + ); +}; + +export const HostsKpiHosts = React.memo(HostsKpiHostsComponent); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/translations.ts new file mode 100644 index 00000000000000..7754591ab415b2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/translations.ts @@ -0,0 +1,11 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const HOSTS = i18n.translate('xpack.securitySolution.kpiHosts.hosts.title', { + defaultMessage: 'Hosts', +}); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.test.tsx deleted file mode 100644 index 7731881df6d2c7..00000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.test.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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 { mockKpiHostsData, mockKpiHostDetailsData } from './mock'; -import React from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; -import '../../../common/mock/match_media'; -import { KpiHostsComponentBase } from '.'; -import * as statItems from '../../../common/components/stat_items'; -import { kpiHostsMapping } from './kpi_hosts_mapping'; -import { kpiHostDetailsMapping } from './kpi_host_details_mapping'; - -describe('kpiHostsComponent', () => { - const ID = 'kpiHost'; - const from = '2019-06-15T06:00:00.000Z'; - const to = '2019-06-18T06:00:00.000Z'; - const narrowDateRange = () => {}; - describe('render', () => { - test('it should render spinner if it is loading', () => { - const wrapper: ShallowWrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - }); - - test('it should render KpiHostsData', () => { - const wrapper: ShallowWrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - }); - - test('it should render KpiHostDetailsData', () => { - const wrapper: ShallowWrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - }); - }); - - const table = [ - [mockKpiHostsData, kpiHostsMapping] as [typeof mockKpiHostsData, typeof kpiHostsMapping], - [mockKpiHostDetailsData, kpiHostDetailsMapping] as [ - typeof mockKpiHostDetailsData, - typeof kpiHostDetailsMapping - ], - ]; - - describe.each(table)( - 'it should handle KpiHostsProps and KpiHostDetailsProps', - (data, mapping) => { - let mockUseKpiMatrixStatus: jest.SpyInstance; - beforeAll(() => { - mockUseKpiMatrixStatus = jest.spyOn(statItems, 'useKpiMatrixStatus'); - }); - - beforeEach(() => { - shallow( - - ); - }); - - afterEach(() => { - mockUseKpiMatrixStatus.mockClear(); - }); - - afterAll(() => { - mockUseKpiMatrixStatus.mockRestore(); - }); - - test(`it should apply correct mapping by given data type`, () => { - expect(mockUseKpiMatrixStatus).toBeCalledWith(mapping, data, ID, from, to, narrowDateRange); - }); - } - ); -}); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx index c39e86591013f9..fff4c64900a8bb 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx @@ -4,81 +4,78 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import React from 'react'; -import styled from 'styled-components'; +import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; -import { KpiHostsData, KpiHostDetailsData } from '../../../graphql/types'; -import { - StatItemsComponent, - StatItemsProps, - useKpiMatrixStatus, -} from '../../../common/components/stat_items'; -import { kpiHostsMapping } from './kpi_hosts_mapping'; -import { kpiHostDetailsMapping } from './kpi_host_details_mapping'; -import { UpdateDateRange } from '../../../common/components/charts/common'; +import { HostsKpiAuthentications } from './authentications'; +import { HostsKpiHosts } from './hosts'; +import { HostsKpiUniqueIps } from './unique_ips'; +import { HostsKpiProps } from './types'; -const kpiWidgetHeight = 247; - -interface GenericKpiHostProps { - from: string; - id: string; - loading: boolean; - to: string; - narrowDateRange: UpdateDateRange; -} - -interface KpiHostsProps extends GenericKpiHostProps { - data: KpiHostsData; -} - -interface KpiHostDetailsProps extends GenericKpiHostProps { - data: KpiHostDetailsData; -} - -const FlexGroupSpinner = styled(EuiFlexGroup)` - { - min-height: ${kpiWidgetHeight}px; - } -`; - -FlexGroupSpinner.displayName = 'FlexGroupSpinner'; - -export const KpiHostsComponentBase = ({ - data, - from, - loading, - id, - to, - narrowDateRange, -}: KpiHostsProps | KpiHostDetailsProps) => { - const mappings = - (data as KpiHostsData).hosts !== undefined ? kpiHostsMapping : kpiHostDetailsMapping; - const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( - mappings, - data, - id, - from, - to, - narrowDateRange - ); - return loading ? ( - - - +export const HostsKpiComponent = React.memo( + ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => ( + + + + + + + + + - - ) : ( - - {statItemsProps.map((mappedStatItemProps, idx) => { - return ; - })} - ); -}; + ) +); -KpiHostsComponentBase.displayName = 'KpiHostsComponentBase'; +HostsKpiComponent.displayName = 'HostsKpiComponent'; -export const KpiHostsComponent = React.memo(KpiHostsComponentBase); +export const HostsDetailsKpiComponent = React.memo( + ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => ( + + + + + + + + + ) +); -KpiHostsComponent.displayName = 'KpiHostsComponent'; +HostsDetailsKpiComponent.displayName = 'HostsDetailsKpiComponent'; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_host_details_mapping.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_host_details_mapping.ts deleted file mode 100644 index b3e98b70c4cb0e..00000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_host_details_mapping.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 * as i18n from './translations'; -import { StatItems } from '../../../common/components/stat_items'; -import { KpiHostsChartColors } from './types'; - -export const kpiHostDetailsMapping: Readonly = [ - { - key: 'authentication', - index: 0, - fields: [ - { - key: 'authSuccess', - name: i18n.SUCCESS_CHART_LABEL, - description: i18n.SUCCESS_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.authSuccess, - icon: 'check', - }, - { - key: 'authFailure', - name: i18n.FAIL_CHART_LABEL, - description: i18n.FAIL_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.authFailure, - icon: 'cross', - }, - ], - enableAreaChart: true, - enableBarChart: true, - grow: 1, - description: i18n.USER_AUTHENTICATIONS, - }, - { - key: 'uniqueIps', - index: 1, - fields: [ - { - key: 'uniqueSourceIps', - name: i18n.SOURCE_CHART_LABEL, - description: i18n.SOURCE_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.uniqueSourceIps, - icon: 'visMapCoordinate', - }, - { - key: 'uniqueDestinationIps', - name: i18n.DESTINATION_CHART_LABEL, - description: i18n.DESTINATION_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.uniqueDestinationIps, - icon: 'visMapCoordinate', - }, - ], - enableAreaChart: true, - enableBarChart: true, - grow: 1, - description: i18n.UNIQUE_IPS, - }, -]; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_hosts_mapping.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_hosts_mapping.ts deleted file mode 100644 index 78a9fd5b84d1fa..00000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_hosts_mapping.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 * as i18n from './translations'; -import { KpiHostsChartColors } from './types'; -import { StatItems } from '../../../common/components/stat_items'; - -export const kpiHostsMapping: Readonly = [ - { - key: 'hosts', - index: 0, - fields: [ - { - key: 'hosts', - value: null, - color: KpiHostsChartColors.hosts, - icon: 'storage', - }, - ], - enableAreaChart: true, - grow: 2, - description: i18n.HOSTS, - }, - { - key: 'authentication', - index: 1, - fields: [ - { - key: 'authSuccess', - name: i18n.SUCCESS_CHART_LABEL, - description: i18n.SUCCESS_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.authSuccess, - icon: 'check', - }, - { - key: 'authFailure', - name: i18n.FAIL_CHART_LABEL, - description: i18n.FAIL_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.authFailure, - icon: 'cross', - }, - ], - enableAreaChart: true, - enableBarChart: true, - grow: 4, - description: i18n.USER_AUTHENTICATIONS, - }, - { - key: 'uniqueIps', - index: 2, - fields: [ - { - key: 'uniqueSourceIps', - name: i18n.SOURCE_CHART_LABEL, - description: i18n.SOURCE_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.uniqueSourceIps, - icon: 'visMapCoordinate', - }, - { - key: 'uniqueDestinationIps', - name: i18n.DESTINATION_CHART_LABEL, - description: i18n.DESTINATION_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.uniqueDestinationIps, - icon: 'visMapCoordinate', - }, - ], - enableAreaChart: true, - enableBarChart: true, - grow: 4, - description: i18n.UNIQUE_IPS, - }, -]; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/mock.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/mock.tsx deleted file mode 100644 index a1d081af204357..00000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/mock.tsx +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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. - */ - -export const mockKpiHostsData = { - hosts: 986, - hostsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 919, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 82, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 4, - }, - ], - authSuccess: 61, - authSuccessHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 8, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 52, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 1, - }, - ], - authFailure: 15722, - authFailureHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 11731, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 3979, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 12, - }, - ], - uniqueSourceIps: 1407, - uniqueSourceIpsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 1182, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 364, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 63, - }, - ], - uniqueDestinationIps: 1954, - uniqueDestinationIpsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 1809, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 407, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 64, - }, - ], -}; -export const mockKpiHostDetailsData = { - authSuccess: 61, - authSuccessHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 8, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 52, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 1, - }, - ], - authFailure: 15722, - authFailureHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 11731, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 3979, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 12, - }, - ], - uniqueSourceIps: 1407, - uniqueSourceIpsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 1182, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 364, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 63, - }, - ], - uniqueDestinationIps: 1954, - uniqueDestinationIpsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 1809, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 407, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 64, - }, - ], -}; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts index fd483681247952..7cdbb16ee348cd 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts @@ -4,9 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -export enum KpiHostsChartColors { - authSuccess = '#54B399', - authFailure = '#E7664C', +import { UpdateDateRange } from '../../../common/components/charts/common'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; + +export interface HostsKpiProps { + filterQuery: string; + from: string; + to: string; + narrowDateRange: UpdateDateRange; + setQuery: GlobalTimeArgs['setQuery']; + skip: boolean; +} + +export enum HostsKpiChartColors { + authenticationsSuccess = '#54B399', + authenticationsFailure = '#E7664C', uniqueSourceIps = '#D36086', uniqueDestinationIps = '#9170B8', hosts = '#6092C0', diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx new file mode 100644 index 00000000000000..c6f430faacccb9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx @@ -0,0 +1,73 @@ +/* + * 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 React from 'react'; + +import { StatItems } from '../../../../common/components/stat_items'; +import { useHostsKpiUniqueIps } from '../../../containers/kpi_hosts/unique_ips'; +import { HostsKpiBaseComponentManage } from '../common'; +import { HostsKpiProps, HostsKpiChartColors } from '../types'; +import * as i18n from './translations'; + +export const fieldsMapping: Readonly = [ + { + key: 'uniqueIps', + fields: [ + { + key: 'uniqueSourceIps', + name: i18n.SOURCE_CHART_LABEL, + description: i18n.SOURCE_UNIT_LABEL, + value: null, + color: HostsKpiChartColors.uniqueSourceIps, + icon: 'visMapCoordinate', + }, + { + key: 'uniqueDestinationIps', + name: i18n.DESTINATION_CHART_LABEL, + description: i18n.DESTINATION_UNIT_LABEL, + value: null, + color: HostsKpiChartColors.uniqueDestinationIps, + icon: 'visMapCoordinate', + }, + ], + enableAreaChart: true, + enableBarChart: true, + description: i18n.UNIQUE_IPS, + }, +]; + +const HostsKpiUniqueIpsComponent: React.FC = ({ + filterQuery, + from, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const [loading, { refetch, id, inspect, ...data }] = useHostsKpiUniqueIps({ + filterQuery, + endDate: to, + startDate: from, + skip, + }); + + return ( + + ); +}; + +export const HostsKpiUniqueIps = React.memo(HostsKpiUniqueIpsComponent); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/translations.ts new file mode 100644 index 00000000000000..6cc651880be7bf --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/translations.ts @@ -0,0 +1,39 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const UNIQUE_IPS = i18n.translate('xpack.securitySolution.kpiHosts.uniqueIps.title', { + defaultMessage: 'Unique IPs', +}); + +export const SOURCE_UNIT_LABEL = i18n.translate( + 'xpack.securitySolution.kpiHosts.uniqueIps.sourceUnitLabel', + { + defaultMessage: 'source', + } +); + +export const DESTINATION_UNIT_LABEL = i18n.translate( + 'xpack.securitySolution.kpiHosts.uniqueIps.destinationUnitLabel', + { + defaultMessage: 'destination', + } +); + +export const SOURCE_CHART_LABEL = i18n.translate( + 'xpack.securitySolution.kpiHosts.uniqueIps.sourceChartLabel', + { + defaultMessage: 'Src.', + } +); + +export const DESTINATION_CHART_LABEL = i18n.translate( + 'xpack.securitySolution.kpiHosts.uniqueIps.destinationChartLabel', + { + defaultMessage: 'Dest.', + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx new file mode 100644 index 00000000000000..0d90b73e0a5843 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx @@ -0,0 +1,170 @@ +/* + * 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 deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; +import { inputsModel } from '../../../../common/store'; +import { createFilter } from '../../../../common/containers/helpers'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + HostsKpiQueries, + HostsKpiAuthenticationsRequestOptions, + HostsKpiAuthenticationsStrategyResponse, +} from '../../../../../common/search_strategy'; +import { ESTermQuery } from '../../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../../helpers'; +import { InspectResponse } from '../../../../types'; + +const ID = 'hostsKpiAuthenticationsQuery'; + +export interface HostsKpiAuthenticationsArgs + extends Omit { + id: string; + inspect: InspectResponse; + isInspected: boolean; + refetch: inputsModel.Refetch; +} + +interface UseHostsKpiAuthentications { + filterQuery?: ESTermQuery | string; + endDate: string; + skip?: boolean; + startDate: string; +} + +export const useHostsKpiAuthentications = ({ + filterQuery, + endDate, + skip = false, + startDate, +}: UseHostsKpiAuthentications): [boolean, HostsKpiAuthenticationsArgs] => { + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + const [hostsKpiAuthenticationsRequest, setHostsKpiAuthenticationsRequest] = useState< + HostsKpiAuthenticationsRequestOptions + >({ + defaultIndex, + factoryQueryType: HostsKpiQueries.kpiAuthentications, + filterQuery: createFilter(filterQuery), + id: ID, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [hostsKpiAuthenticationsResponse, setHostsKpiAuthenticationsResponse] = useState< + HostsKpiAuthenticationsArgs + >({ + authenticationsSuccess: 0, + authenticationsSuccessHistogram: [], + authenticationsFailure: 0, + authenticationsFailureHistogram: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); + + const hostsKpiAuthenticationsSearch = useCallback( + (request: HostsKpiAuthenticationsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search( + request, + { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + } + ) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setHostsKpiAuthenticationsResponse((prevResponse) => ({ + ...prevResponse, + authenticationsSuccess: response.authenticationsSuccess, + authenticationsSuccessHistogram: response.authenticationsSuccessHistogram, + authenticationsFailure: response.authenticationsFailure, + authenticationsFailureHistogram: response.authenticationsFailureHistogram, + inspect: getInspectResponse(response, prevResponse.inspect), + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_AUTHENTICATIONS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_HOSTS_KPI_AUTHENTICATIONS, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setHostsKpiAuthenticationsRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [defaultIndex, endDate, filterQuery, skip, startDate]); + + useEffect(() => { + hostsKpiAuthenticationsSearch(hostsKpiAuthenticationsRequest); + }, [hostsKpiAuthenticationsRequest, hostsKpiAuthenticationsSearch]); + + return [loading, hostsKpiAuthenticationsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/translations.ts new file mode 100644 index 00000000000000..fb5af83d0acef0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/translations.ts @@ -0,0 +1,21 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const ERROR_HOSTS_KPI_AUTHENTICATIONS = i18n.translate( + 'xpack.securitySolution.hostsKpiAuthentications.errorSearchDescription', + { + defaultMessage: `An error has occurred on hosts kpi authentications search`, + } +); + +export const FAIL_HOSTS_KPI_AUTHENTICATIONS = i18n.translate( + 'xpack.securitySolution.hostsKpiAuthentications.failSearchDescription', + { + defaultMessage: `Failed to run search on hosts kpi authentications`, + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx new file mode 100644 index 00000000000000..190ce1aa7eae1c --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx @@ -0,0 +1,158 @@ +/* + * 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 deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; +import { inputsModel } from '../../../../common/store'; +import { createFilter } from '../../../../common/containers/helpers'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + HostsKpiQueries, + HostsKpiHostsRequestOptions, + HostsKpiHostsStrategyResponse, +} from '../../../../../common/search_strategy'; +import { ESTermQuery } from '../../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../../helpers'; +import { InspectResponse } from '../../../../types'; + +const ID = 'hostsKpiHostsQuery'; + +export interface HostsKpiHostsArgs extends Omit { + id: string; + inspect: InspectResponse; + isInspected: boolean; + refetch: inputsModel.Refetch; +} + +interface UseHostsKpiHosts { + filterQuery?: ESTermQuery | string; + endDate: string; + skip?: boolean; + startDate: string; +} + +export const useHostsKpiHosts = ({ + filterQuery, + endDate, + skip = false, + startDate, +}: UseHostsKpiHosts): [boolean, HostsKpiHostsArgs] => { + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + const [hostsKpiHostsRequest, setHostsKpiHostsRequest] = useState({ + defaultIndex, + factoryQueryType: HostsKpiQueries.kpiHosts, + filterQuery: createFilter(filterQuery), + id: ID, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [hostsKpiHostsResponse, setHostsKpiHostsResponse] = useState({ + hosts: 0, + hostsHistogram: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); + + const hostsKpiHostsSearch = useCallback( + (request: HostsKpiHostsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setHostsKpiHostsResponse((prevResponse) => ({ + ...prevResponse, + hosts: response.hosts, + hostsHistogram: response.hostsHistogram, + inspect: getInspectResponse(response, prevResponse.inspect), + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_HOSTS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_HOSTS_KPI_HOSTS, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setHostsKpiHostsRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [defaultIndex, endDate, filterQuery, skip, startDate]); + + useEffect(() => { + hostsKpiHostsSearch(hostsKpiHostsRequest); + }, [hostsKpiHostsRequest, hostsKpiHostsSearch]); + + return [loading, hostsKpiHostsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/translations.ts new file mode 100644 index 00000000000000..2a15563a4b1cd8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/translations.ts @@ -0,0 +1,21 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const ERROR_HOSTS_KPI_HOSTS = i18n.translate( + 'xpack.securitySolution.hostsKpiHosts.errorSearchDescription', + { + defaultMessage: `An error has occurred on hosts kpi hosts search`, + } +); + +export const FAIL_HOSTS_KPI_HOSTS = i18n.translate( + 'xpack.securitySolution.hostsKpiHosts.failSearchDescription', + { + defaultMessage: `Failed to run search on hosts kpi hosts`, + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx index 1a6df58f045976..c0ae767219aaea 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx @@ -4,82 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getOr } from 'lodash/fp'; -import React from 'react'; -import { Query } from 'react-apollo'; -import { connect, ConnectedProps } from 'react-redux'; - -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; -import { GetKpiHostsQuery, KpiHostsData } from '../../../graphql/types'; -import { inputsModel, inputsSelectors, State } from '../../../common/store'; -import { useUiSetting } from '../../../common/lib/kibana'; -import { createFilter, getDefaultFetchPolicy } from '../../../common/containers/helpers'; -import { QueryTemplateProps } from '../../../common/containers/query_template'; - -import { kpiHostsQuery } from './index.gql_query'; - -const ID = 'kpiHostsQuery'; - -export interface KpiHostsArgs { - id: string; - inspect: inputsModel.InspectQuery; - kpiHosts: KpiHostsData; - loading: boolean; - refetch: inputsModel.Refetch; -} - -export interface KpiHostsProps extends QueryTemplateProps { - children: (args: KpiHostsArgs) => React.ReactNode; -} - -const KpiHostsComponentQuery = React.memo( - ({ id = ID, children, endDate, filterQuery, isInspected, skip, sourceId, startDate }) => ( - - query={kpiHostsQuery} - fetchPolicy={getDefaultFetchPolicy()} - notifyOnNetworkStatusChange - skip={skip} - variables={{ - sourceId, - timerange: { - interval: '12h', - from: startDate!, - to: endDate!, - }, - filterQuery: createFilter(filterQuery), - defaultIndex: useUiSetting(DEFAULT_INDEX_KEY), - inspect: isInspected, - }} - > - {({ data, loading, refetch }) => { - const kpiHosts = getOr({}, `source.KpiHosts`, data); - return children({ - id, - inspect: getOr(null, 'source.KpiHosts.inspect', data), - kpiHosts, - loading, - refetch, - }); - }} - - ) -); - -KpiHostsComponentQuery.displayName = 'KpiHostsComponentQuery'; - -const makeMapStateToProps = () => { - const getQuery = inputsSelectors.globalQueryByIdSelector(); - const mapStateToProps = (state: State, { id = ID }: KpiHostsProps) => { - const { isInspected } = getQuery(state, id); - return { - isInspected, - }; - }; - return mapStateToProps; -}; - -const connector = connect(makeMapStateToProps); - -type PropsFromRedux = ConnectedProps; - -export const KpiHostsQuery = connector(KpiHostsComponentQuery); +export * from './authentications'; +export * from './hosts'; +export * from './unique_ips'; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx new file mode 100644 index 00000000000000..ac5cc12807f007 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx @@ -0,0 +1,167 @@ +/* + * 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 deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; +import { inputsModel } from '../../../../common/store'; +import { createFilter } from '../../../../common/containers/helpers'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + HostsKpiQueries, + HostsKpiUniqueIpsRequestOptions, + HostsKpiUniqueIpsStrategyResponse, +} from '../../../../../common/search_strategy'; +import { ESTermQuery } from '../../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../../helpers'; +import { InspectResponse } from '../../../../types'; + +const ID = 'hostsKpiUniqueIpsQuery'; + +export interface HostsKpiUniqueIpsArgs + extends Omit { + id: string; + inspect: InspectResponse; + isInspected: boolean; + refetch: inputsModel.Refetch; +} + +interface UseHostsKpiUniqueIps { + filterQuery?: ESTermQuery | string; + endDate: string; + skip?: boolean; + startDate: string; +} + +export const useHostsKpiUniqueIps = ({ + filterQuery, + endDate, + skip = false, + startDate, +}: UseHostsKpiUniqueIps): [boolean, HostsKpiUniqueIpsArgs] => { + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + const [hostsKpiUniqueIpsRequest, setHostsKpiUniqueIpsRequest] = useState< + HostsKpiUniqueIpsRequestOptions + >({ + defaultIndex, + factoryQueryType: HostsKpiQueries.kpiUniqueIps, + filterQuery: createFilter(filterQuery), + id: ID, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [hostsKpiUniqueIpsResponse, setHostsKpiUniqueIpsResponse] = useState( + { + uniqueSourceIps: 0, + uniqueSourceIpsHistogram: [], + uniqueDestinationIps: 0, + uniqueDestinationIpsHistogram: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + } + ); + + const hostsKpiUniqueIpsSearch = useCallback( + (request: HostsKpiUniqueIpsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setHostsKpiUniqueIpsResponse((prevResponse) => ({ + ...prevResponse, + uniqueSourceIps: response.uniqueSourceIps, + uniqueSourceIpsHistogram: response.uniqueSourceIpsHistogram, + uniqueDestinationIps: response.uniqueDestinationIps, + uniqueDestinationIpsHistogram: response.uniqueDestinationIpsHistogram, + inspect: getInspectResponse(response, prevResponse.inspect), + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_UNIQUE_IPS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_HOSTS_KPI_UNIQUE_IPS, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setHostsKpiUniqueIpsRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [defaultIndex, endDate, filterQuery, skip, startDate]); + + useEffect(() => { + hostsKpiUniqueIpsSearch(hostsKpiUniqueIpsRequest); + }, [hostsKpiUniqueIpsRequest, hostsKpiUniqueIpsSearch]); + + return [loading, hostsKpiUniqueIpsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/translations.ts new file mode 100644 index 00000000000000..2d1574b080ac1f --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/translations.ts @@ -0,0 +1,21 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const ERROR_HOSTS_KPI_UNIQUE_IPS = i18n.translate( + 'xpack.securitySolution.hostsKpiUniqueIps.errorSearchDescription', + { + defaultMessage: `An error has occurred on hosts kpi unique ips search`, + } +); + +export const FAIL_HOSTS_KPI_UNIQUE_IPS = i18n.translate( + 'xpack.securitySolution.hostsKpiUniqueIps.failSearchDescription', + { + defaultMessage: `Failed to run search on hosts kpi unique ips`, + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index d8cd59f119d525..57e1b128ce64de 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -21,13 +21,12 @@ import { hasMlUserPermissions } from '../../../../common/machine_learning/has_ml import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities'; import { scoreIntervalToDateTime } from '../../../common/components/ml/score/score_interval_to_datetime'; import { SiemNavigation } from '../../../common/components/navigation'; -import { KpiHostsComponent } from '../../components/kpi_hosts'; +import { HostsDetailsKpiComponent } from '../../components/kpi_hosts'; import { HostOverview } from '../../../overview/components/host_overview'; import { manageQuery } from '../../../common/components/page/manage_query'; import { SiemSearchBar } from '../../../common/components/search_bar'; import { WrapperPage } from '../../../common/components/wrapper_page'; import { HostOverviewByNameQuery } from '../../containers/hosts/details'; -import { KpiHostDetailsQuery } from '../../containers/kpi_host_details'; import { useGlobalTime } from '../../../common/containers/use_global_time'; import { useWithSource } from '../../../common/containers/source'; import { LastEventIndexKey } from '../../../graphql/types'; @@ -54,7 +53,6 @@ import { TimelineId } from '../../../../common/types/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; const HostOverviewManage = manageQuery(HostOverview); -const KpiHostDetailsManage = manageQuery(KpiHostsComponent); const HostDetailsComponent = React.memo( ({ @@ -160,27 +158,14 @@ const HostDetailsComponent = React.memo( - - {({ kpiHostDetails, id, inspect, loading, refetch }) => ( - - )} - + /> diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index ef88c255b1735a..4b8e3cc6987ac8 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -17,11 +17,9 @@ import { HeaderPage } from '../../common/components/header_page'; import { LastEventTime } from '../../common/components/last_event_time'; import { hasMlUserPermissions } from '../../../common/machine_learning/has_ml_user_permissions'; import { SiemNavigation } from '../../common/components/navigation'; -import { KpiHostsComponent } from '../components/kpi_hosts'; -import { manageQuery } from '../../common/components/page/manage_query'; +import { HostsKpiComponent } from '../components/kpi_hosts'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; -import { KpiHostsQuery } from '../containers/kpi_hosts'; import { useFullScreen } from '../../common/containers/use_full_screen'; import { useGlobalTime } from '../../common/containers/use_global_time'; import { useWithSource } from '../../common/containers/source'; @@ -49,8 +47,6 @@ import { timelineSelectors } from '../../timelines/store/timeline'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; import { TimelineModel } from '../../timelines/store/timeline/model'; -const KpiHostsComponentManage = manageQuery(KpiHostsComponent); - export const HostsComponent = React.memo( ({ filters, graphEventId, query, setAbsoluteRangeDatePicker, hostsPagePath }) => { const { to, from, deleteQuery, setQuery, isInitializing } = useGlobalTime(); @@ -109,27 +105,14 @@ export const HostsComponent = React.memo( title={i18n.PAGE_TITLE} /> - - {({ kpiHosts, loading, id, inspect, refetch }) => ( - - )} - + narrowDateRange={narrowDateRange} + /> diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx index 65ddb9305f607d..d3fc68874ce912 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx @@ -16,7 +16,7 @@ import { MatrixHistogramConfigs, } from '../../../common/components/matrix_histogram/types'; import { MatrixHistogram } from '../../../common/components/matrix_histogram'; -import { KpiHostsChartColors } from '../../components/kpi_hosts/types'; +import { HostsKpiChartColors } from '../../components/kpi_hosts/types'; import * as i18n from '../translations'; import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution'; @@ -24,7 +24,7 @@ const AuthenticationTableManage = manageQuery(AuthenticationTable); const ID = 'authenticationsHistogramQuery'; -const authStackByOptions: MatrixHistogramOption[] = [ +const authenticationsStackByOptions: MatrixHistogramOption[] = [ { text: 'event.outcome', value: 'event.outcome', @@ -32,31 +32,32 @@ const authStackByOptions: MatrixHistogramOption[] = [ ]; const DEFAULT_STACK_BY = 'event.outcome'; -enum AuthMatrixDataGroup { - authSuccess = 'success', - authFailure = 'failure', +enum AuthenticationsMatrixDataGroup { + authenticationsSuccess = 'success', + authenticationsFailure = 'failure', } -export const authMatrixDataMappingFields: MatrixHistogramMappingTypes = { - [AuthMatrixDataGroup.authSuccess]: { - key: AuthMatrixDataGroup.authSuccess, +export const authenticationsMatrixDataMappingFields: MatrixHistogramMappingTypes = { + [AuthenticationsMatrixDataGroup.authenticationsSuccess]: { + key: AuthenticationsMatrixDataGroup.authenticationsSuccess, value: null, - color: KpiHostsChartColors.authSuccess, + color: HostsKpiChartColors.authenticationsSuccess, }, - [AuthMatrixDataGroup.authFailure]: { - key: AuthMatrixDataGroup.authFailure, + [AuthenticationsMatrixDataGroup.authenticationsFailure]: { + key: AuthenticationsMatrixDataGroup.authenticationsFailure, value: null, - color: KpiHostsChartColors.authFailure, + color: HostsKpiChartColors.authenticationsFailure, }, }; const histogramConfigs: MatrixHistogramConfigs = { defaultStackByOption: - authStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? authStackByOptions[0], + authenticationsStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? + authenticationsStackByOptions[0], errorMessage: i18n.ERROR_FETCHING_AUTHENTICATIONS_DATA, histogramType: MatrixHistogramType.authentications, - mapping: authMatrixDataMappingFields, - stackByOptions: authStackByOptions, + mapping: authenticationsMatrixDataMappingFields, + stackByOptions: authenticationsStackByOptions, title: i18n.NAVIGATION_AUTHENTICATIONS_TITLE, }; diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap index 49562162e94a8c..a03d7c2317517d 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`KpiNetwork Component rendering it renders the default widget 1`] = ` -; data: NetworkKpiStrategyResponse; loading?: boolean; @@ -64,6 +64,6 @@ export const KpiNetworkBaseComponent = React.memo<{ ); }); -KpiNetworkBaseComponent.displayName = 'KpiNetworkBaseComponent'; +NetworkKpiBaseComponent.displayName = 'NetworkKpiBaseComponent'; -export const KpiNetworkBaseComponentManage = manageQuery(KpiNetworkBaseComponent); +export const NetworkKpiBaseComponentManage = manageQuery(NetworkKpiBaseComponent); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx index 889f3dacc2d98a..0f13b0e8f874e7 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { StatItems } from '../../../../common/components/stat_items'; import { useNetworkKpiDns } from '../../../containers/kpi_network/dns'; -import { KpiNetworkBaseComponentManage } from '../common'; +import { NetworkKpiBaseComponentManage } from '../common'; import { NetworkKpiProps } from '../types'; import * as i18n from './translations'; @@ -41,7 +41,7 @@ const NetworkKpiDnsComponent: React.FC = ({ }); return ( - { +describe('NetworkKpiComponent', () => { const state: State = mockGlobalState; const props = { from: '2019-06-15T06:00:00.000Z', @@ -53,11 +53,11 @@ describe('KpiNetwork Component', () => { test('it renders the default widget', () => { const wrapper = shallow( - + ); - expect(wrapper.find('KpiNetworkComponent')).toMatchSnapshot(); + expect(wrapper.find('NetworkKpiComponent')).toMatchSnapshot(); }); }); }); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx index 674e592940fa61..95534e1a61988b 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx @@ -14,7 +14,7 @@ import { NetworkKpiUniqueFlows } from './unique_flows'; import { NetworkKpiUniquePrivateIps } from './unique_private_ips'; import { NetworkKpiProps } from './types'; -export const KpiNetworkComponent = React.memo( +export const NetworkKpiComponent = React.memo( ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => ( @@ -78,4 +78,4 @@ export const KpiNetworkComponent = React.memo( ) ); -KpiNetworkComponent.displayName = 'KpiNetworkComponent'; +NetworkKpiComponent.displayName = 'NetworkKpiComponent'; diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx index 3ee2acf1a115c0..18217e41f2a27e 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx @@ -9,7 +9,7 @@ import { euiPaletteColorBlind } from '@elastic/eui'; import { StatItems } from '../../../../common/components/stat_items'; import { useNetworkKpiNetworkEvents } from '../../../containers/kpi_network/network_events'; -import { KpiNetworkBaseComponentManage } from '../common'; +import { NetworkKpiBaseComponentManage } from '../common'; import { NetworkKpiProps } from '../types'; import * as i18n from './translations'; @@ -46,7 +46,7 @@ const NetworkKpiNetworkEventsComponent: React.FC = ({ }); return ( - = ({ }); return ( - = ({ }); return ( - = ({ }); return ( - ( - > = { @@ -32,7 +32,7 @@ export const buildQuery = ({ defaultIndex, docValueFields, }: HostAuthenticationsRequestOptions) => { - const esFields = reduceFields(authenticationFields, { ...hostFieldsMap, ...sourceFieldsMap }); + const esFields = reduceFields(authenticationsFields, { ...hostFieldsMap, ...sourceFieldsMap }); const filter = [ ...createQueryFilterClauses(filterQuery), diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts index d61914fda7d06f..ce8900a5781026 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts @@ -15,7 +15,7 @@ import { StrategyResponseType, } from '../../../../../../common/search_strategy/security_solution'; -export const authenticationFields = [ +export const authenticationsFields = [ '_id', 'failures', 'successes', @@ -31,7 +31,7 @@ export const authenticationFields = [ ]; export const formatAuthenticationData = ( - fields: readonly string[] = authenticationFields, + fields: readonly string[] = authenticationsFields, hit: AuthenticationHit, fieldMap: Readonly> ): AuthenticationsEdges => diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx index a43f53880587ad..e09d8de7ba9457 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx @@ -20,7 +20,7 @@ import { import { inspectStringifyObject } from '../../../../../utils/build_query'; import { SecuritySolutionFactory } from '../../types'; import { auditdFieldsMap, buildQuery as buildAuthenticationQuery } from './dsl/query.dsl'; -import { authenticationFields, formatAuthenticationData, getHits } from './helpers'; +import { authenticationsFields, formatAuthenticationData, getHits } from './helpers'; export const authentications: SecuritySolutionFactory = { buildDsl: (options: HostAuthenticationsRequestOptions) => { @@ -40,7 +40,7 @@ export const authentications: SecuritySolutionFactory - formatAuthenticationData(authenticationFields, hit, auditdFieldsMap) + formatAuthenticationData(authenticationsFields, hit, auditdFieldsMap) ); const edges = authenticationEdges.splice(cursorStart, querySize - cursorStart); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts index edcba88a0cd89a..44c55ab6e7c9d3 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts @@ -5,13 +5,16 @@ */ import { hostsFactory } from '.'; -import { HostsQueries } from '../../../../../common/search_strategy'; +import { HostsQueries, HostsKpiQueries } from '../../../../../common/search_strategy'; import { allHosts } from './all'; import { hostDetails } from './details'; import { hostOverview } from './overview'; import { firstLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; import { authentications } from './authentications'; +import { hostsKpiAuthentications } from './kpi/authentications'; +import { hostsKpiHosts } from './kpi/hosts'; +import { hostsKpiUniqueIps } from './kpi/unique_ips'; jest.mock('./all'); jest.mock('./details'); @@ -19,6 +22,9 @@ jest.mock('./overview'); jest.mock('./last_first_seen'); jest.mock('./uncommon_processes'); jest.mock('./authentications'); +jest.mock('./kpi/authentications'); +jest.mock('./kpi/hosts'); +jest.mock('./kpi/unique_ips'); describe('hostsFactory', () => { test('should include correct apis', () => { @@ -29,6 +35,9 @@ describe('hostsFactory', () => { [HostsQueries.firstLastSeen]: firstLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, [HostsQueries.authentications]: authentications, + [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications, + [HostsKpiQueries.kpiHosts]: hostsKpiHosts, + [HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps, }; expect(hostsFactory).toEqual(expectedHostsFactory); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts index 85619cfec62ce7..ad6a6182d331b0 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts @@ -7,6 +7,7 @@ import { FactoryQueryTypes, HostsQueries, + HostsKpiQueries, } from '../../../../../common/search_strategy/security_solution'; import { SecuritySolutionFactory } from '../types'; @@ -16,12 +17,21 @@ import { hostOverview } from './overview'; import { firstLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; import { authentications } from './authentications'; +import { hostsKpiAuthentications } from './kpi/authentications'; +import { hostsKpiHosts } from './kpi/hosts'; +import { hostsKpiUniqueIps } from './kpi/unique_ips'; -export const hostsFactory: Record> = { +export const hostsFactory: Record< + HostsQueries | HostsKpiQueries, + SecuritySolutionFactory +> = { [HostsQueries.details]: hostDetails, [HostsQueries.hosts]: allHosts, [HostsQueries.overview]: hostOverview, [HostsQueries.firstLastSeen]: firstLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, [HostsQueries.authentications]: authentications, + [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications, + [HostsKpiQueries.kpiHosts]: hostsKpiHosts, + [HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts new file mode 100644 index 00000000000000..513e361b5be05c --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts @@ -0,0 +1,21 @@ +/* + * 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 { + HostsKpiHistogram, + HostsKpiAuthenticationsHistogramCount, + HostsKpiHistogramData, +} from '../../../../../../../common/search_strategy'; + +export const formatAuthenticationsHistogramData = ( + data: Array> +): HostsKpiHistogramData[] | null => + data && data.length > 0 + ? data.map(({ key, count }) => ({ + x: key, + y: count.doc_count, + })) + : null; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts new file mode 100644 index 00000000000000..bafc9a3accc6ee --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts @@ -0,0 +1,63 @@ +/* + * 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 { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + HostsKpiQueries, + HostsKpiAuthenticationsStrategyResponse, + HostsKpiAuthenticationsRequestOptions, +} from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../../types'; +import { buildHostsKpiAuthenticationsQuery } from './query.hosts_kpi_authentications.dsl'; +import { formatAuthenticationsHistogramData } from './helpers'; + +export const hostsKpiAuthentications: SecuritySolutionFactory = { + buildDsl: (options: HostsKpiAuthenticationsRequestOptions) => + buildHostsKpiAuthenticationsQuery(options), + parse: async ( + options: HostsKpiAuthenticationsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildHostsKpiAuthenticationsQuery(options))], + }; + + const authenticationsSuccessHistogram = getOr( + null, + 'aggregations.authentication_success_histogram.buckets', + response.rawResponse + ); + const authenticationsFailureHistogram = getOr( + null, + 'aggregations.authentication_failure_histogram.buckets', + response.rawResponse + ); + + return { + ...response, + inspect, + authenticationsSuccess: getOr( + null, + 'aggregations.authentication_success.doc_count', + response.rawResponse + ), + authenticationsSuccessHistogram: formatAuthenticationsHistogramData( + authenticationsSuccessHistogram + ), + authenticationsFailure: getOr( + null, + 'aggregations.authentication_failure.doc_count', + response.rawResponse + ), + authenticationsFailureHistogram: formatAuthenticationsHistogramData( + authenticationsFailureHistogram + ), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts new file mode 100644 index 00000000000000..8da5f7f95c5d11 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts @@ -0,0 +1,101 @@ +/* + * 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 { HostsKpiAuthenticationsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; + +export const buildHostsKpiAuthenticationsQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: HostsKpiAuthenticationsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + bool: { + filter: [ + { + term: { + 'event.category': 'authentication', + }, + }, + ], + }, + }, + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + aggs: { + authentication_success: { + filter: { + term: { + 'event.outcome': 'success', + }, + }, + }, + authentication_success_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + filter: { + term: { + 'event.outcome': 'success', + }, + }, + }, + }, + }, + authentication_failure: { + filter: { + term: { + 'event.outcome': 'failure', + }, + }, + }, + authentication_failure_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + filter: { + term: { + 'event.outcome': 'failure', + }, + }, + }, + }, + }, + }, + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/common/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/common/index.ts new file mode 100644 index 00000000000000..080ef05c991365 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/common/index.ts @@ -0,0 +1,21 @@ +/* + * 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 { + HostsKpiHistogram, + HostsKpiGeneralHistogramCount, + HostsKpiHistogramData, +} from '../../../../../../../common/search_strategy'; + +export const formatGeneralHistogramData = ( + data: Array> +): HostsKpiHistogramData[] | null => + data && data.length > 0 + ? data.map(({ key, count }) => ({ + x: key, + y: count.value, + })) + : null; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts new file mode 100644 index 00000000000000..6d91ebf09895ea --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts @@ -0,0 +1,42 @@ +/* + * 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 { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + HostsKpiQueries, + HostsKpiHostsStrategyResponse, + HostsKpiHostsRequestOptions, +} from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../../types'; +import { buildHostsKpiHostsQuery } from './query.hosts_kpi_hosts.dsl'; +import { formatGeneralHistogramData } from '../common'; + +export const hostsKpiHosts: SecuritySolutionFactory = { + buildDsl: (options: HostsKpiHostsRequestOptions) => buildHostsKpiHostsQuery(options), + parse: async ( + options: HostsKpiHostsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildHostsKpiHostsQuery(options))], + }; + + const hostsHistogram = getOr( + null, + 'aggregations.hosts_histogram.buckets', + response.rawResponse + ); + return { + ...response, + inspect, + hosts: getOr(null, 'aggregations.hosts.value', response.rawResponse), + hostsHistogram: formatGeneralHistogramData(hostsHistogram), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts new file mode 100644 index 00000000000000..704743cc434eda --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts @@ -0,0 +1,64 @@ +/* + * 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 { HostsKpiHostsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; + +export const buildHostsKpiHostsQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: HostsKpiHostsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + aggregations: { + hosts: { + cardinality: { + field: 'host.name', + }, + }, + hosts_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + cardinality: { + field: 'host.name', + }, + }, + }, + }, + }, + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts new file mode 100644 index 00000000000000..f4793ecd53f8f3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export * from './authentications'; +export * from './common'; +export * from './hosts'; +export * from './unique_ips'; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts new file mode 100644 index 00000000000000..2f890e6fdacca5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts @@ -0,0 +1,55 @@ +/* + * 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 { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + HostsKpiQueries, + HostsKpiUniqueIpsStrategyResponse, + HostsKpiUniqueIpsRequestOptions, +} from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../../types'; +import { buildHostsKpiUniqueIpsQuery } from './query.hosts_kpi_unique_ips.dsl'; +import { formatGeneralHistogramData } from '../common'; + +export const hostsKpiUniqueIps: SecuritySolutionFactory = { + buildDsl: (options: HostsKpiUniqueIpsRequestOptions) => buildHostsKpiUniqueIpsQuery(options), + parse: async ( + options: HostsKpiUniqueIpsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildHostsKpiUniqueIpsQuery(options))], + }; + + const uniqueSourceIpsHistogram = getOr( + null, + 'aggregations.unique_source_ips_histogram.buckets', + response.rawResponse + ); + + const uniqueDestinationIpsHistogram = getOr( + null, + 'aggregations.unique_destination_ips_histogram.buckets', + response.rawResponse + ); + + return { + ...response, + inspect, + uniqueSourceIps: getOr(null, 'aggregations.unique_source_ips.value', response.rawResponse), + uniqueSourceIpsHistogram: formatGeneralHistogramData(uniqueSourceIpsHistogram), + uniqueDestinationIps: getOr( + null, + 'aggregations.unique_destination_ips.value', + response.rawResponse + ), + uniqueDestinationIpsHistogram: formatGeneralHistogramData(uniqueDestinationIpsHistogram), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts new file mode 100644 index 00000000000000..618c6cb51f6661 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts @@ -0,0 +1,82 @@ +/* + * 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 { HostsKpiUniqueIpsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; + +export const buildHostsKpiUniqueIpsQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: HostsKpiUniqueIpsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + aggregations: { + unique_source_ips: { + cardinality: { + field: 'source.ip', + }, + }, + unique_source_ips_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + cardinality: { + field: 'source.ip', + }, + }, + }, + }, + unique_destination_ips: { + cardinality: { + field: 'destination.ip', + }, + }, + unique_destination_ips_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + cardinality: { + field: 'destination.ip', + }, + }, + }, + }, + }, + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 790d1152f0846a..b86d59762c8b87 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -14890,7 +14890,7 @@ "xpack.securitySolution.auditd.violatedSeLinuxPolicyDescription": "selinuxポリシーに違反しました", "xpack.securitySolution.auditd.wasAuthorizedToUseDescription": "が以下の使用を承認されました。", "xpack.securitySolution.auditd.withResultDescription": "結果付き", - "xpack.securitySolution.authenticationsTable.authenticationFailures": "認証", + "xpack.securitySolution.authenticationsTable.authentications": "認証", "xpack.securitySolution.authenticationsTable.failures": "失敗", "xpack.securitySolution.authenticationsTable.lastFailedDestination": "前回失敗したデスティネーション", "xpack.securitySolution.authenticationsTable.lastFailedSource": "前回失敗したソース", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d93c8054268e7b..28d9cfa4aaf0d4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -14899,7 +14899,7 @@ "xpack.securitySolution.auditd.violatedSeLinuxPolicyDescription": "已违反 selinux 策略", "xpack.securitySolution.auditd.wasAuthorizedToUseDescription": "有权使用", "xpack.securitySolution.auditd.withResultDescription": ",结果为", - "xpack.securitySolution.authenticationsTable.authenticationFailures": "身份验证", + "xpack.securitySolution.authenticationsTable.authentications": "身份验证", "xpack.securitySolution.authenticationsTable.failures": "错误", "xpack.securitySolution.authenticationsTable.lastFailedDestination": "上一失败目标", "xpack.securitySolution.authenticationsTable.lastFailedSource": "上一失败源",