diff --git a/x-pack/legacy/plugins/apm/common/projections/typings.ts b/x-pack/legacy/plugins/apm/common/projections/typings.ts index 0c56fd2f75bde..2b55395b70c6b 100644 --- a/x-pack/legacy/plugins/apm/common/projections/typings.ts +++ b/x-pack/legacy/plugins/apm/common/projections/typings.ts @@ -4,15 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams } from 'elasticsearch'; +import { ESSearchRequest, ESSearchBody } from '../../typings/elasticsearch'; +import { + AggregationOptionsByType, + AggregationInputMap +} from '../../typings/elasticsearch/aggregations'; -export type Projection = Omit & { - body: { - query: any; - } & { +export type Projection = Omit & { + body: Omit & { aggs?: { [key: string]: { - terms: any; + terms: AggregationOptionsByType['terms']; + aggs?: AggregationInputMap; }; }; }; diff --git a/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.test.ts b/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.test.ts index ae1b7c552ab4e..aa72b5fd71365 100644 --- a/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.test.ts +++ b/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.test.ts @@ -29,12 +29,15 @@ describe('mergeProjection', () => { }); it('merges plain objects', () => { + const termsAgg = { terms: { field: 'bar' } }; expect( mergeProjection( - { body: { query: {}, aggs: { foo: { terms: { field: 'bar' } } } } }, + { body: { query: {}, aggs: { foo: termsAgg } } }, { body: { - aggs: { foo: { aggs: { bar: { terms: { field: 'baz' } } } } } + aggs: { + foo: { ...termsAgg, aggs: { bar: { terms: { field: 'baz' } } } } + } } } ) diff --git a/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts b/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts index 5b6b5b0b7f058..9a8f11c6493c5 100644 --- a/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts +++ b/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts @@ -4,10 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ import { merge, isPlainObject } from 'lodash'; +import { DeepPartial } from 'utility-types'; +import { AggregationInputMap } from '../../../../typings/elasticsearch/aggregations'; +import { + ESSearchRequest, + ESSearchBody +} from '../../../../typings/elasticsearch'; import { Projection } from '../../typings'; type PlainObject = Record; +type SourceProjection = Omit, 'body'> & { + body: Omit, 'aggs'> & { + aggs?: AggregationInputMap; + }; +}; + type DeepMerge = U extends PlainObject ? (T extends PlainObject ? (Omit & @@ -19,10 +31,10 @@ type DeepMerge = U extends PlainObject : U) : U; -export function mergeProjection( - target: T, - source: U -): DeepMerge { +export function mergeProjection< + T extends Projection, + U extends SourceProjection +>(target: T, source: U): DeepMerge { return merge({}, target, source, (a, b) => { if (isPlainObject(a) && isPlainObject(b)) { return undefined; diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx index 92ae1737a408f..fe1a80d702dba 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx @@ -49,6 +49,8 @@ const ErrorGroupOverview: React.SFC = () => { const { data: errorGroupListData } = useFetcher( callApmApi => { + const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc'; + if (serviceName && start && end) { return callApmApi({ pathname: '/api/apm/services/{serviceName}/errors', @@ -60,7 +62,7 @@ const ErrorGroupOverview: React.SFC = () => { start, end, sortField, - sortDirection, + sortDirection: normalizedSortDirection, uiFilters: JSON.stringify(uiFilters) } } diff --git a/x-pack/legacy/plugins/apm/public/services/rest/ml.ts b/x-pack/legacy/plugins/apm/public/services/rest/ml.ts index 968450e6aa796..9150c63127317 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/ml.ts +++ b/x-pack/legacy/plugins/apm/public/services/rest/ml.ts @@ -5,7 +5,6 @@ */ import { npStart } from 'ui/new_platform'; -import { ESFilter } from 'elasticsearch'; import { HttpServiceBase } from 'kibana/public'; import { PROCESSOR_EVENT, @@ -14,6 +13,7 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { getMlJobId, getMlPrefix } from '../../../common/ml_job_constants'; import { callApi } from './callApi'; +import { ESFilter } from '../../../typings/elasticsearch'; interface MlResponseItem { id: string; diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index 73b81bfed4794..2e3c8940680fe 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -15,9 +15,9 @@ import { Moment } from 'moment-timezone'; import React from 'react'; import { render, waitForElement } from 'react-testing-library'; import { MemoryRouter } from 'react-router-dom'; -import { ESFilter } from 'elasticsearch'; import { LocationProvider } from '../context/LocationContext'; import { PromiseReturnType } from '../../typings/common'; +import { ESFilter } from '../../typings/elasticsearch'; export function toJson(wrapper: ReactWrapper) { return enzymeToJson(wrapper, { diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts index 9c49a7f613aaa..6059f2b0051a7 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESFilter } from 'elasticsearch'; import { idx } from '@kbn/elastic-idx'; +import { ESFilter } from '../../../../typings/elasticsearch'; import { ERROR_GROUP_ID, PROCESSOR_EVENT, diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts index f692f94e255fc..193ee4c2bbd1c 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts @@ -17,6 +17,7 @@ import { APMError } from '../../../typings/es_schemas/ui/APMError'; import { Setup } from '../helpers/setup_request'; import { getErrorGroupsProjection } from '../../../common/projections/errors'; import { mergeProjection } from '../../../common/projections/util/merge_projection'; +import { SortOptions } from '../../../typings/elasticsearch/aggregations'; export type ErrorGroupListAPIResponse = PromiseReturnType< typeof getErrorGroups @@ -30,7 +31,7 @@ export async function getErrorGroups({ }: { serviceName: string; sortField?: string; - sortDirection?: string; + sortDirection?: 'asc' | 'desc'; setup: Setup; }) { const { client } = setup; @@ -40,18 +41,21 @@ export async function getErrorGroups({ const projection = getErrorGroupsProjection({ setup, serviceName }); + const order: SortOptions = sortByLatestOccurrence + ? { + max_timestamp: sortDirection + } + : { _count: sortDirection }; + const params = mergeProjection(projection, { body: { size: 0, aggs: { error_groups: { terms: { + ...projection.body.aggs.error_groups.terms, size: 500, - order: sortByLatestOccurrence - ? { - max_timestamp: sortDirection - } - : { _count: sortDirection } + order }, aggs: { sample: { @@ -64,7 +68,7 @@ export async function getErrorGroups({ ERROR_GROUP_ID, '@timestamp' ], - sort: [{ '@timestamp': 'desc' }], + sort: [{ '@timestamp': 'desc' as const }], size: 1 } }, @@ -98,13 +102,13 @@ export async function getErrorGroups({ }; } - const resp = await client.search(params); + const resp = await client.search(params); // aggregations can be undefined when no matching indices are found. // this is an exception rather than the rule so the ES type does not account for this. const hits = (idx(resp, _ => _.aggregations.error_groups.buckets) || []).map( bucket => { - const source = bucket.sample.hits.hits[0]._source as SampleError; + const source = bucket.sample.hits.hits[0]._source; const message = idx(source, _ => _.error.log.message) || idx(source, _ => _.error.exception[0].message); diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts index df471af4f5ee0..0f0a11a868d6d 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESFilter } from 'elasticsearch'; import { getEnvironmentUiFilterES } from '../get_environment_ui_filter_es'; import { ENVIRONMENT_NOT_DEFINED } from '../../../../../common/environment_filter_values'; import { SERVICE_ENVIRONMENT } from '../../../../../common/elasticsearch_fieldnames'; +import { ESFilter } from '../../../../../typings/elasticsearch'; describe('getEnvironmentUiFilterES', () => { it('should return undefined, when environment is undefined', () => { diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts index 8a94e42a40b21..57feaf6a6fd0e 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESFilter } from 'elasticsearch'; +import { ESFilter } from '../../../../typings/elasticsearch'; import { ENVIRONMENT_NOT_DEFINED } from '../../../../common/environment_filter_values'; import { SERVICE_ENVIRONMENT } from '../../../../common/elasticsearch_fieldnames'; diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_kuery_ui_filter_es.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_kuery_ui_filter_es.ts index 4ba4caa8c69d8..2543c2b9a8a61 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_kuery_ui_filter_es.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_kuery_ui_filter_es.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESFilter } from 'elasticsearch'; import { Server } from 'hapi'; import { idx } from '@kbn/elastic-idx'; import { toElasticsearchQuery, fromKueryExpression } from '@kbn/es-query'; +import { ESFilter } from '../../../../typings/elasticsearch'; import { ISavedObject } from '../../../../public/services/rest/savedObjects'; import { StaticIndexPattern } from '../../../../../../../../src/legacy/core_plugins/data/public'; import { getAPMIndexPattern } from '../../../lib/index_pattern'; diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts index 1658cb07e4d2f..bea7e510875db 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts @@ -5,7 +5,7 @@ */ import { Server } from 'hapi'; -import { ESFilter } from 'elasticsearch'; +import { ESFilter } from '../../../../typings/elasticsearch'; import { UIFilters } from '../../../../typings/ui-filters'; import { getEnvironmentUiFilterES } from './get_environment_ui_filter_es'; import { getKueryUiFilterES } from './get_kuery_ui_filter_es'; diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts index 79fcfe95c0552..9b2c97fab7aa3 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts @@ -9,13 +9,16 @@ import { SearchParams, IndexDocumentParams, IndicesDeleteParams, - IndicesCreateParams, - AggregationSearchResponseWithTotalHitsAsObject + IndicesCreateParams } from 'elasticsearch'; import { Legacy } from 'kibana'; import { cloneDeep, has, isString, set } from 'lodash'; import { OBSERVER_VERSION_MAJOR } from '../../../common/elasticsearch_fieldnames'; import { StringMap } from '../../../typings/common'; +import { + ESSearchResponse, + ESSearchRequest +} from '../../../typings/elasticsearch'; // `type` was deprecated in 7.0 export type APMIndexDocumentParams = Omit, 'type'>; @@ -92,10 +95,13 @@ export function getESClient(req: Legacy.Request) { const query = req.query as StringMap; return { - search: async ( - params: U, + search: async < + TDocument = unknown, + TSearchRequest extends ESSearchRequest = {} + >( + params: TSearchRequest, apmOptions?: APMOptions - ): Promise> => { + ): Promise> => { const nextParams = await getParamsForSearchRequest( req, params, @@ -117,9 +123,7 @@ export function getESClient(req: Legacy.Request) { req, 'search', nextParams - ) as unknown) as Promise< - AggregationSearchResponseWithTotalHitsAsObject - >; + ) as unknown) as Promise>; }, index: (params: APMIndexDocumentParams) => { return cluster.callWithRequest(req, 'index', params); diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts index cfc33b4fad7ea..e8cf56fcd0802 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts @@ -31,7 +31,7 @@ describe('setupRequest', () => { it('should call callWithRequest with default args', async () => { const { mockRequest, callWithRequestSpy } = getMockRequest(); const { client } = await setupRequest(mockRequest); - await client.search({ index: 'apm-*', body: { foo: 'bar' } }); + await client.search({ index: 'apm-*', body: { foo: 'bar' } } as any); expect(callWithRequestSpy).toHaveBeenCalledWith(mockRequest, 'search', { index: 'apm-*', body: { diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts index 7b8fc4ac44f64..f785e45062807 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts @@ -124,7 +124,7 @@ export async function fetchAndTransformGcMetrics({ } const series = aggregations.per_pool.buckets.map((poolBucket, i) => { - const label = poolBucket.key; + const label = poolBucket.key as string; const timeseriesData = poolBucket.over_time; const data = (idx(timeseriesData, _ => _.buckets) || []).map(bucket => { diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts index 9837fb21b6da0..3d425e50bc60a 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts @@ -4,20 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Unionize } from 'utility-types'; import { Setup } from '../helpers/setup_request'; import { getMetricsDateHistogramParams } from '../helpers/metrics'; import { ChartBase } from './types'; import { transformDataToMetricsChart } from './transform_metrics_chart'; import { getMetricsProjection } from '../../../common/projections/metrics'; import { mergeProjection } from '../../../common/projections/util/merge_projection'; +import { AggregationOptionsByType } from '../../../typings/elasticsearch/aggregations'; interface Aggs { - [key: string]: { - min?: any; - max?: any; - sum?: any; - avg?: any; - }; + [key: string]: Unionize<{ + min: AggregationOptionsByType['min']; + max: AggregationOptionsByType['max']; + sum: AggregationOptionsByType['sum']; + avg: AggregationOptionsByType['avg']; + }>; } interface Filter { diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts index 89bc3b4107a5f..594a0d35ed176 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts @@ -4,12 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { - AggregationSearchResponseWithTotalHitsAsObject, - AggregatedValue -} from 'elasticsearch'; import { idx } from '@kbn/elastic-idx'; +import { Unionize, Overwrite } from 'utility-types'; import { ChartBase } from './types'; +import { + ESSearchResponse, + ESSearchRequest +} from '../../../typings/elasticsearch'; +import { AggregationOptionsByType } from '../../../typings/elasticsearch/aggregations'; const colors = [ theme.euiColorVis0, @@ -25,33 +27,30 @@ export type GenericMetricsChart = ReturnType< typeof transformDataToMetricsChart >; -interface AggregatedParams { - body: { - aggs: { - timeseriesData: { - date_histogram: any; - aggs: { - min?: any; - max?: any; - sum?: any; - avg?: any; +interface MetricsAggregationMap { + min: AggregationOptionsByType['min']; + max: AggregationOptionsByType['max']; + sum: AggregationOptionsByType['sum']; + avg: AggregationOptionsByType['avg']; +} + +type GenericMetricsRequest = Overwrite< + ESSearchRequest, + { + body: { + aggs: { + timeseriesData: { + date_histogram: AggregationOptionsByType['date_histogram']; + aggs: Record>; }; - }; - } & { - [key: string]: { - min?: any; - max?: any; - sum?: any; - avg?: any; - }; + } & Record>; }; - }; -} + } +>; -export function transformDataToMetricsChart( - result: AggregationSearchResponseWithTotalHitsAsObject, - chartBase: ChartBase -) { +export function transformDataToMetricsChart< + TRequest extends GenericMetricsRequest +>(result: ESSearchResponse, chartBase: ChartBase) { const { aggregations, hits } = result; const timeseriesData = idx(aggregations, _ => _.timeseriesData); @@ -70,7 +69,7 @@ export function transformDataToMetricsChart( color: chartBase.series[seriesKey].color || colors[i], overallValue, data: (idx(timeseriesData, _ => _.buckets) || []).map(bucket => { - const { value } = bucket[seriesKey] as AggregatedValue; + const { value } = bucket[seriesKey] as { value: number | null }; const y = value === null || isNaN(value) ? null : value; return { x: bucket.key, diff --git a/x-pack/legacy/plugins/apm/server/lib/service_nodes/index.ts b/x-pack/legacy/plugins/apm/server/lib/service_nodes/index.ts index afdf4795c4d29..1e415252200ce 100644 --- a/x-pack/legacy/plugins/apm/server/lib/service_nodes/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/service_nodes/index.ts @@ -24,45 +24,45 @@ const getServiceNodes = async ({ }) => { const { client } = setup; - const projection = mergeProjection( - getServiceNodesProjection({ setup, serviceName }), - { - body: { - aggs: { - nodes: { - terms: { - size: 10000, - missing: SERVICE_NODE_NAME_MISSING + const projection = getServiceNodesProjection({ setup, serviceName }); + + const params = mergeProjection(projection, { + body: { + aggs: { + nodes: { + terms: { + ...projection.body.aggs.nodes.terms, + size: 10000, + missing: SERVICE_NODE_NAME_MISSING + }, + aggs: { + cpu: { + avg: { + field: METRIC_PROCESS_CPU_PERCENT + } + }, + heapMemory: { + avg: { + field: METRIC_JAVA_HEAP_MEMORY_USED + } }, - aggs: { - cpu: { - avg: { - field: METRIC_PROCESS_CPU_PERCENT - } - }, - heapMemory: { - avg: { - field: METRIC_JAVA_HEAP_MEMORY_USED - } - }, - nonHeapMemory: { - avg: { - field: METRIC_JAVA_NON_HEAP_MEMORY_USED - } - }, - threadCount: { - max: { - field: METRIC_JAVA_THREAD_COUNT - } + nonHeapMemory: { + avg: { + field: METRIC_JAVA_NON_HEAP_MEMORY_USED + } + }, + threadCount: { + max: { + field: METRIC_JAVA_THREAD_COUNT } } } } } } - ); + }); - const response = await client.search(projection); + const response = await client.search(params); if (!response.aggregations) { return []; @@ -70,7 +70,7 @@ const getServiceNodes = async ({ return response.aggregations.nodes.buckets.map(bucket => { return { - name: bucket.key, + name: bucket.key as string, cpu: bucket.cpu.value, heapMemory: bucket.heapMemory.value, nonHeapMemory: bucket.nonHeapMemory.value, diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts index 3c6f684389970..b39e35a305b19 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts @@ -44,6 +44,8 @@ export async function getServiceAgentName(serviceName: string, setup: Setup) { }; const { aggregations } = await client.search(params); - const agentName = idx(aggregations, _ => _.agents.buckets[0].key); + const agentName = idx(aggregations, _ => _.agents.buckets[0].key) as + | string + | undefined; return { agentName }; } diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts index a1f035da9dc1a..9d651247d5cee 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts @@ -41,6 +41,6 @@ export async function getServiceTransactionTypes( const { aggregations } = await client.search(params); const buckets = idx(aggregations, _ => _.types.buckets) || []; - const transactionTypes = buckets.map(bucket => bucket.key); + const transactionTypes = buckets.map(bucket => bucket.key as string); return { transactionTypes }; } diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_agent_status.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_agent_status.ts index d1654632dbb26..f9a7671abd994 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_agent_status.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_agent_status.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams } from 'elasticsearch'; import { PROCESSOR_EVENT } from '../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../helpers/setup_request'; @@ -12,13 +11,13 @@ import { Setup } from '../../helpers/setup_request'; export async function getAgentStatus(setup: Setup) { const { client, config } = setup; - const params: SearchParams = { + const params = { terminateAfter: 1, index: [ - config.get('apm_oss.errorIndices'), - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.sourcemapIndices'), - config.get('apm_oss.transactionIndices') + config.get('apm_oss.errorIndices'), + config.get('apm_oss.metricsIndices'), + config.get('apm_oss.sourcemapIndices'), + config.get('apm_oss.transactionIndices') ], body: { size: 0, diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts index c50506db1faec..60f3091567059 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts @@ -28,6 +28,7 @@ export async function getServicesItems(setup: Setup) { aggs: { services: { terms: { + ...projection.body.aggs.services.terms, size: 500 }, aggs: { @@ -69,12 +70,14 @@ export async function getServicesItems(setup: Setup) { const environmentsBuckets = bucket.environments.buckets; const environments = environmentsBuckets.map( - environmentBucket => environmentBucket.key + environmentBucket => environmentBucket.key as string ); return { - serviceName: bucket.key, - agentName: idx(bucket, _ => _.agents.buckets[0].key), + serviceName: bucket.key as string, + agentName: idx(bucket, _ => _.agents.buckets[0].key) as + | string + | undefined, transactionsPerMinute, errorsPerMinute, avgResponseTime: bucket.avg.value, diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts index a18873e86c5a8..7742bc938816f 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts @@ -49,6 +49,8 @@ export async function getAgentNameByService({ }; const { aggregations } = await client.search(params); - const agentName = idx(aggregations, _ => _.agent_names.buckets[0].key); + const agentName = idx(aggregations, _ => _.agent_names.buckets[0].key) as + | string + | undefined; return { agentName }; } diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_all_environments.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_all_environments.ts index 76ebf75aada29..aad31feef000b 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_all_environments.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_all_environments.ts @@ -58,6 +58,6 @@ export async function getAllEnvironments({ const resp = await client.search(params); const buckets = idx(resp.aggregations, _ => _.environments.buckets) || []; - const environments = buckets.map(bucket => bucket.key); + const environments = buckets.map(bucket => bucket.key as string); return [ALL_OPTION_VALUE, ...environments]; } diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts index 120cc62cc3bc9..6b557f21f3457 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts @@ -44,5 +44,5 @@ export async function getExistingEnvironmentsForService({ const resp = await client.search(params); const buckets = idx(resp.aggregations, _ => _.environments.buckets) || []; - return buckets.map(bucket => bucket.key); + return buckets.map(bucket => bucket.key as string); } diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts index 55af96acbc719..dfc1041c96488 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts @@ -47,6 +47,6 @@ export async function getServiceNames({ setup }: { setup: Setup }) { const resp = await client.search(params); const buckets = idx(resp.aggregations, _ => _.services.buckets) || []; - const serviceNames = buckets.map(bucket => bucket.key).sort(); + const serviceNames = buckets.map(bucket => bucket.key as string).sort(); return [ALL_OPTION_VALUE, ...serviceNames]; } diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts index 664bcb9325472..6b0dc370dd878 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts @@ -3,8 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import { ESSearchHit } from 'elasticsearch'; +import { ESSearchHit } from '../../../../typings/elasticsearch'; import { SERVICE_NAME, SERVICE_ENVIRONMENT @@ -50,7 +49,7 @@ export async function searchConfigurations({ } }; - const resp = await client.search(params); + const resp = await client.search(params); const { hits } = resp.hits; const exactMatch = hits.find( diff --git a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts index 74e16424b1098..71b65982db9f0 100644 --- a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts +++ b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams } from 'elasticsearch'; import { PROCESSOR_EVENT, TRACE_ID, @@ -21,10 +20,10 @@ export async function getTraceItems(traceId: string, setup: Setup) { const { start, end, client, config } = setup; const maxTraceItems = config.get('xpack.apm.ui.maxTraceItems'); - const params: SearchParams = { + const params = { index: [ - config.get('apm_oss.spanIndices'), - config.get('apm_oss.transactionIndices') + config.get('apm_oss.spanIndices'), + config.get('apm_oss.transactionIndices') ], body: { size: maxTraceItems, @@ -41,9 +40,9 @@ export async function getTraceItems(traceId: string, setup: Setup) { } }, sort: [ - { _score: { order: 'asc' } }, - { [TRANSACTION_DURATION]: { order: 'desc' } }, - { [SPAN_DURATION]: { order: 'desc' } } + { _score: { order: 'asc' as const } }, + { [TRANSACTION_DURATION]: { order: 'desc' as const } }, + { [SPAN_DURATION]: { order: 'desc' as const } } ], track_total_hits: true } diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts index a647bd2faff36..bfa46abcad36f 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -12,6 +12,8 @@ import { PromiseReturnType } from '../../../typings/common'; import { Setup } from '../helpers/setup_request'; import { getTransactionGroupsProjection } from '../../../common/projections/transaction_groups'; import { mergeProjection } from '../../../common/projections/util/merge_projection'; +import { SortOptions } from '../../../typings/elasticsearch/aggregations'; +import { Transaction } from '../../../typings/es_schemas/ui/Transaction'; interface TopTransactionOptions { type: 'top_transactions'; @@ -36,6 +38,11 @@ export function transactionGroupsFetcher(options: Options, setup: Setup) { options }); + const sort: SortOptions = [ + { _score: 'desc' as const }, // sort by _score to ensure that buckets with sampled:true ends up on top + { '@timestamp': { order: 'desc' as const } } + ]; + const params = mergeProjection(projection, { body: { size: 0, @@ -48,17 +55,15 @@ export function transactionGroupsFetcher(options: Options, setup: Setup) { aggs: { transactions: { terms: { - order: { sum: 'desc' }, + ...projection.body.aggs.transactions.terms, + order: { sum: 'desc' as const }, size: config.get('xpack.apm.ui.transactionGroupBucketSize') }, aggs: { sample: { top_hits: { size: 1, - sort: [ - { _score: 'desc' }, // sort by _score to ensure that buckets with sampled:true ends up on top - { '@timestamp': { order: 'desc' } } - ] + sort } }, avg: { avg: { field: TRANSACTION_DURATION } }, @@ -72,5 +77,5 @@ export function transactionGroupsFetcher(options: Options, setup: Setup) { } }); - return client.search(params); + return client.search(params); } diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts index b4e70d260f512..3ec64be08d117 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts @@ -6,7 +6,6 @@ import moment from 'moment'; import { idx } from '@kbn/elastic-idx'; -import { Transaction } from '../../../typings/es_schemas/ui/Transaction'; import { ESResponse } from './fetcher'; function calculateRelativeImpacts(transactionGroups: ITransactionGroup[]) { @@ -34,10 +33,10 @@ function getTransactionGroup( const averageResponseTime = bucket.avg.value; const transactionsPerMinute = bucket.doc_count / minutes; const impact = bucket.sum.value; - const sample = bucket.sample.hits.hits[0]._source as Transaction; + const sample = bucket.sample.hits.hits[0]._source; return { - name: bucket.key, + name: bucket.key as string, sample, p95: bucket.p95.values['95.0'], averageResponseTime, diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts index c440ee9c1ecbe..e2989498181ca 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts @@ -63,7 +63,7 @@ export async function getTransactionAvgDurationByCountry({ const buckets = resp.aggregations.country_code.buckets; const avgDurationsByCountry = buckets.map( ({ key, doc_count, avg_duration: { value } }) => ({ - key, + key: key as string, docCount: doc_count, value: value === null ? 0 : value }) diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts index b3c1c6603f315..ecf963fec3fe9 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts @@ -50,7 +50,7 @@ export async function getTransactionBreakdown({ field: SPAN_TYPE, size: 20, order: { - _count: 'desc' + _count: 'desc' as const } }, aggs: { @@ -60,7 +60,7 @@ export async function getTransactionBreakdown({ missing: '', size: 20, order: { - _count: 'desc' + _count: 'desc' as const } }, aggs: { @@ -117,11 +117,11 @@ export async function getTransactionBreakdown({ const breakdowns = flatten( aggs.types.buckets.map(bucket => { - const type = bucket.key; + const type = bucket.key as string; return bucket.subtypes.buckets.map(subBucket => { return { - name: subBucket.key || type, + name: (subBucket.key as string) || type, percentage: (subBucket.total_self_time_per_subtype.value || 0) / sumAllSelfTimes diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts index 293cede80cbfc..27cff20b7ff37 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts @@ -49,7 +49,7 @@ export async function getMlBucketSize({ }; try { - const resp = await client.search(params); + const resp = await client.search(params); return idx(resp, _ => _.hits.hits[0]._source.bucket_span) || 0; } catch (err) { const isHttpError = 'statusCode' in err; diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts index dc5cb925b7202..97368b7d92efc 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESFilter } from 'elasticsearch'; +import { ESFilter } from '../../../../../typings/elasticsearch'; import { PROCESSOR_EVENT, SERVICE_NAME, diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts index f403c64a94aea..42828367f7941 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts @@ -57,7 +57,8 @@ export function getTpmBuckets( }); // Handle empty string result keys - const key = resultKey === '' ? NOT_AVAILABLE_LABEL : resultKey; + const key = + resultKey === '' ? NOT_AVAILABLE_LABEL : (resultKey as string); return { key, dataPoints }; } @@ -65,7 +66,7 @@ export function getTpmBuckets( return sortBy( buckets, - bucket => bucket.key.replace(/^HTTP (\d)xx$/, '00$1') // ensure that HTTP 3xx are sorted at the top + bucket => bucket.key.toString().replace(/^HTTP (\d)xx$/, '00$1') // ensure that HTTP 3xx are sorted at the top ); } diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts index 458aad225fd94..42b35402aab9d 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; import { PROCESSOR_EVENT, SERVICE_NAME, @@ -17,7 +18,7 @@ import { import { rangeFilter } from '../../../helpers/range_filter'; import { Setup } from '../../../helpers/setup_request'; -export function bucketFetcher( +export async function bucketFetcher( serviceName: string, transactionName: string, transactionType: string, @@ -74,5 +75,7 @@ export function bucketFetcher( } }; - return client.search(params); + const response = await client.search(params); + + return response; } diff --git a/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts b/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts index 3b48bfc7a9869..80795ce468c88 100644 --- a/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts +++ b/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESFilter } from 'elasticsearch'; import { idx } from '@kbn/elastic-idx'; import { PROCESSOR_EVENT, @@ -14,6 +13,7 @@ import { import { rangeFilter } from '../helpers/range_filter'; import { Setup } from '../helpers/setup_request'; import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; +import { ESFilter } from '../../../typings/elasticsearch'; export async function getEnvironments(setup: Setup, serviceName?: string) { const { start, end, client, config } = setup; @@ -58,7 +58,7 @@ export async function getEnvironments(setup: Setup, serviceName?: string) { const environmentsBuckets = idx(aggs, _ => _.environments.buckets) || []; const environments = environmentsBuckets.map( - environmentBucket => environmentBucket.key + environmentBucket => environmentBucket.key as string ); return environments; diff --git a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/get_filter_aggregations.ts b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/get_filter_aggregations.ts index 9c9a5c45f697c..b7aaae2ff2652 100644 --- a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/get_filter_aggregations.ts +++ b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/get_filter_aggregations.ts @@ -53,7 +53,7 @@ export const getFilterAggregations = async ({ terms: { field: field.fieldName, order: { - _count: 'desc' + _count: 'desc' as const } }, ...bucketCountAggregation diff --git a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts index 7632b03584167..e8c29c29434e6 100644 --- a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts @@ -68,7 +68,7 @@ export async function getLocalUIFilters({ options: sortByOrder( aggregationsForFilter.by_terms.buckets.map(bucket => { return { - name: bucket.key, + name: bucket.key as string, count: 'bucket_count' in bucket ? bucket.bucket_count.value diff --git a/x-pack/legacy/plugins/apm/server/routes/errors.ts b/x-pack/legacy/plugins/apm/server/routes/errors.ts index 73bb6e97b999b..a315dd10023dc 100644 --- a/x-pack/legacy/plugins/apm/server/routes/errors.ts +++ b/x-pack/legacy/plugins/apm/server/routes/errors.ts @@ -21,7 +21,7 @@ export const errorsRoute = createRoute(core => ({ query: t.intersection([ t.partial({ sortField: t.string, - sortDirection: t.string + sortDirection: t.union([t.literal('asc'), t.literal('desc')]) }), uiFiltersRt, rangeRt diff --git a/x-pack/legacy/plugins/apm/typings/elasticsearch.ts b/x-pack/legacy/plugins/apm/typings/elasticsearch.ts deleted file mode 100644 index 08d1e2b37492e..0000000000000 --- a/x-pack/legacy/plugins/apm/typings/elasticsearch.ts +++ /dev/null @@ -1,203 +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 { StringMap, IndexAsString } from './common'; - -declare module 'elasticsearch' { - // extending SearchResponse to be able to have typed aggregations - - type ESSearchHit = SearchResponse['hits']['hits'][0]; - - type AggregationType = - | 'date_histogram' - | 'histogram' - | 'terms' - | 'avg' - | 'top_hits' - | 'max' - | 'min' - | 'percentiles' - | 'sum' - | 'extended_stats' - | 'filter' - | 'filters' - | 'cardinality' - | 'sampler' - | 'value_count' - | 'derivative' - | 'bucket_script'; - - type AggOptions = AggregationOptionMap & { - [key: string]: any; - }; - - // eslint-disable-next-line @typescript-eslint/consistent-type-definitions - export type AggregationOptionMap = { - aggs?: { - [aggregationName: string]: { - [T in AggregationType]?: AggOptions & AggregationOptionMap; - }; - }; - }; - - type SubAggregation = T extends { aggs: any } - ? AggregationResultMap - : {}; - - // eslint-disable-next-line @typescript-eslint/consistent-type-definitions - type BucketAggregation = { - buckets: Array< - { - key: KeyType; - key_as_string: string; - doc_count: number; - } & (SubAggregation) - >; - }; - - type FilterAggregation = { - doc_count: number; - } & SubAggregation; - - // eslint-disable-next-line @typescript-eslint/consistent-type-definitions - type FiltersAggregation = { - // The filters aggregation can have named filters or anonymous filters, - // which changes the structure of the return - // https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filters-aggregation.html - buckets: SubAggregationMap extends { - filters: { filters: Record }; - } - ? { - [key in keyof SubAggregationMap['filters']['filters']]: { - doc_count: number; - } & SubAggregation; - } - : Array< - { - doc_count: number; - } & SubAggregation - >; - }; - - type SamplerAggregation = SubAggregation< - SubAggregationMap - > & { - doc_count: number; - }; - - interface AggregatedValue { - value: number | null; - } - - interface HitsTotal { - value: number; - relation: 'eq' | 'gte'; - } - - type AggregationResultMap = IndexAsString< - { - [AggregationName in keyof AggregationOption]: { - avg: AggregatedValue; - max: AggregatedValue; - min: AggregatedValue; - sum: AggregatedValue; - value_count: AggregatedValue; - // Elasticsearch might return terms with numbers, but this is a more limited type - terms: BucketAggregation; - date_histogram: BucketAggregation< - AggregationOption[AggregationName], - number - >; - histogram: BucketAggregation< - AggregationOption[AggregationName], - number - >; - top_hits: { - hits: { - total: HitsTotal; - max_score: number | null; - hits: Array<{ - _source: AggregationOption[AggregationName] extends { - Mapping: any; - } - ? AggregationOption[AggregationName]['Mapping'] - : never; - }>; - }; - }; - percentiles: { - values: { - [key: string]: number; - }; - }; - extended_stats: { - count: number; - min: number | null; - max: number | null; - avg: number | null; - sum: number; - sum_of_squares: number | null; - variance: number | null; - std_deviation: number | null; - std_deviation_bounds: { - upper: number | null; - lower: number | null; - }; - }; - filter: FilterAggregation; - filters: FiltersAggregation; - cardinality: { - value: number; - }; - sampler: SamplerAggregation; - derivative: BucketAggregation< - AggregationOption[AggregationName], - number - >; - bucket_script: { - value: number | null; - }; - }[AggregationType & keyof AggregationOption[AggregationName]]; - } - >; - - export type AggregationSearchResponseWithTotalHitsAsInt< - HitType, - SearchParams - > = Pick< - SearchResponse, - Exclude, 'aggregations'> - > & - (SearchParams extends { body: Required } - ? { - aggregations?: AggregationResultMap; - } - : {}); - - type Hits = Pick< - SearchResponse['hits'], - Exclude['hits'], 'total'> - > & { - total: HitsTotal; - }; - - export type AggregationSearchResponseWithTotalHitsAsObject< - HitType, - SearchParams - > = Pick< - AggregationSearchResponseWithTotalHitsAsInt, - Exclude< - keyof AggregationSearchResponseWithTotalHitsAsInt, - 'hits' - > - > & { hits: Hits }; - - export interface ESFilter { - [key: string]: { - [key: string]: string | string[] | number | StringMap | ESFilter[]; - }; - } -} diff --git a/x-pack/legacy/plugins/apm/typings/elasticsearch/aggregations.ts b/x-pack/legacy/plugins/apm/typings/elasticsearch/aggregations.ts new file mode 100644 index 0000000000000..9f17b0197a5b2 --- /dev/null +++ b/x-pack/legacy/plugins/apm/typings/elasticsearch/aggregations.ts @@ -0,0 +1,256 @@ +/* + * 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 { Unionize } from 'utility-types'; + +type SortOrder = 'asc' | 'desc'; +type SortInstruction = Record; +export type SortOptions = SortOrder | SortInstruction | SortInstruction[]; + +type Script = + | string + | { + lang?: string; + id?: string; + source?: string; + params?: Record; + }; + +type BucketsPath = string | Record; + +type SourceOptions = string | string[]; + +type MetricsAggregationOptions = + | { + field: string; + missing?: number; + } + | { + script?: Script; + }; + +interface MetricsAggregationResponsePart { + value: number | null; +} + +export interface AggregationOptionsByType { + terms: { + field: string; + size?: number; + missing?: string; + order?: SortOptions; + }; + date_histogram: { + field: string; + format?: string; + min_doc_count?: number; + extended_bounds?: { + min: number; + max: number; + }; + } & ({ calendar_interval: string } | { fixed_interval: string }); + histogram: { + field: string; + interval: number; + min_doc_count?: number; + extended_bounds?: { + min?: number | string; + max?: number | string; + }; + }; + avg: MetricsAggregationOptions; + max: MetricsAggregationOptions; + min: MetricsAggregationOptions; + sum: MetricsAggregationOptions; + value_count: MetricsAggregationOptions; + cardinality: MetricsAggregationOptions & { + precision_threshold?: number; + }; + percentiles: { + field: string; + percents?: number[]; + }; + extended_stats: { + field: string; + }; + top_hits: { + from?: number; + size?: number; + sort?: SortOptions; + _source?: SourceOptions; + }; + filter: Record; + filters: { + filters: Record | any[]; + }; + sampler: { + shard_size?: number; + }; + derivative: { + buckets_path: BucketsPath; + }; + bucket_script: { + buckets_path: BucketsPath; + script?: Script; + }; +} + +type AggregationType = keyof AggregationOptionsByType; + +type AggregationOptionsMap = Unionize< + { + [TAggregationType in AggregationType]: AggregationOptionsByType[TAggregationType]; + } +> & { aggs?: AggregationInputMap }; + +export interface AggregationInputMap { + [key: string]: AggregationOptionsMap; +} + +type BucketSubAggregationResponse< + TAggregationInputMap extends AggregationInputMap | undefined, + TDocument +> = TAggregationInputMap extends AggregationInputMap + ? AggregationResponseMap + : {}; + +interface AggregationResponsePart< + TAggregationOptionsMap extends AggregationOptionsMap, + TDocument +> { + terms: { + buckets: Array< + { + doc_count: number; + key: string | number; + } & BucketSubAggregationResponse< + TAggregationOptionsMap['aggs'], + TDocument + > + >; + }; + histogram: { + buckets: Array< + { + doc_count: number; + key: number; + } & BucketSubAggregationResponse< + TAggregationOptionsMap['aggs'], + TDocument + > + >; + }; + date_histogram: { + buckets: Array< + { + doc_count: number; + key: number; + key_as_string: string; + } & BucketSubAggregationResponse< + TAggregationOptionsMap['aggs'], + TDocument + > + >; + }; + avg: MetricsAggregationResponsePart; + sum: MetricsAggregationResponsePart; + max: MetricsAggregationResponsePart; + min: MetricsAggregationResponsePart; + value_count: MetricsAggregationResponsePart; + cardinality: { + value: number; + }; + percentiles: { + values: Record; + }; + extended_stats: { + count: number; + min: number | null; + max: number | null; + avg: number | null; + sum: number | null; + sum_of_squares: number | null; + variance: number | null; + std_deviation: number | null; + std_deviation_bounds: { + upper: number | null; + lower: number | null; + }; + }; + top_hits: { + hits: { + total: { + value: number; + relation: 'eq' | 'gte'; + }; + max_score: number | null; + hits: Array<{ + _source: TDocument; + }>; + }; + }; + filter: { + doc_count: number; + } & AggregationResponseMap; + filters: TAggregationOptionsMap extends { filters: { filters: any[] } } + ? Array< + { doc_count: number } & AggregationResponseMap< + TAggregationOptionsMap['aggs'], + TDocument + > + > + : (TAggregationOptionsMap extends { + filters: { + filters: Record; + }; + } + ? { + buckets: { + [key in keyof TAggregationOptionsMap['filters']['filters']]: { + doc_count: number; + } & AggregationResponseMap< + TAggregationOptionsMap['aggs'], + TDocument + >; + }; + } + : never); + sampler: { + doc_count: number; + } & AggregationResponseMap; + derivative: + | { + value: number; + } + | undefined; + bucket_script: + | { + value: number | null; + } + | undefined; +} + +// Type for debugging purposes. If you see an error in AggregationResponseMap +// similar to "cannot be used to index type", uncomment the type below and hover +// over it to see what aggregation response types are missing compared to the +// input map. + +// type MissingAggregationResponseTypes = Exclude< +// AggregationType, +// keyof AggregationResponsePart<{}> +// >; + +export type AggregationResponseMap< + TAggregationInputMap extends AggregationInputMap | undefined, + TDocument +> = TAggregationInputMap extends AggregationInputMap + ? { + [TName in keyof TAggregationInputMap]: AggregationResponsePart< + TAggregationInputMap[TName], + TDocument + >[AggregationType & keyof TAggregationInputMap[TName]]; + } + : undefined; diff --git a/x-pack/legacy/plugins/apm/typings/elasticsearch/index.ts b/x-pack/legacy/plugins/apm/typings/elasticsearch/index.ts new file mode 100644 index 0000000000000..657968fbd704f --- /dev/null +++ b/x-pack/legacy/plugins/apm/typings/elasticsearch/index.ts @@ -0,0 +1,60 @@ +/* + * 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 { Omit } from 'utility-types'; +import { SearchParams, SearchResponse } from 'elasticsearch'; +import { AggregationResponseMap, AggregationInputMap } from './aggregations'; +import { StringMap } from '../common'; + +export interface ESSearchBody { + query?: any; + size?: number; + aggs?: AggregationInputMap; + track_total_hits?: boolean | number; +} + +export type ESSearchRequest = Omit & { + body?: ESSearchBody; +}; + +export interface ESSearchOptions { + restTotalHitsAsInt: boolean; +} + +export type ESSearchHit = SearchResponse['hits']['hits'][0]; + +export type ESSearchResponse< + TDocument, + TSearchRequest extends ESSearchRequest, + TOptions extends ESSearchOptions = { restTotalHitsAsInt: false } +> = Omit, 'aggregations' | 'hits'> & + (TSearchRequest extends { body: { aggs: AggregationInputMap } } + ? { + aggregations?: AggregationResponseMap< + TSearchRequest['body']['aggs'], + TDocument + >; + } + : {}) & + ({ + hits: Omit['hits'], 'total'> & + (TOptions['restTotalHitsAsInt'] extends true + ? { + total: number; + } + : { + total: { + value: number; + relation: 'eq' | 'gte'; + }; + }); + }); + +export interface ESFilter { + [key: string]: { + [key: string]: string | string[] | number | StringMap | ESFilter[]; + }; +} diff --git a/x-pack/legacy/plugins/lens/server/routes/field_stats.ts b/x-pack/legacy/plugins/lens/server/routes/field_stats.ts index e7e378e6b20e4..ceefb492cdabe 100644 --- a/x-pack/legacy/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/legacy/plugins/lens/server/routes/field_stats.ts @@ -7,8 +7,8 @@ import Boom from 'boom'; import DateMath from '@elastic/datemath'; import { schema } from '@kbn/config-schema'; -import { AggregationSearchResponseWithTotalHitsAsInt } from 'elasticsearch'; import { CoreSetup } from 'src/core/server'; +import { ESSearchResponse } from '../../../apm/typings/elasticsearch'; import { FieldStatsResponse, BASE_API_URL } from '../../common'; const SHARD_SIZE = 5000; @@ -135,9 +135,11 @@ export async function getNumberHistogram( }, }; - const minMaxResult = (await aggSearchWithBody( - searchBody - )) as AggregationSearchResponseWithTotalHitsAsInt; + const minMaxResult = (await aggSearchWithBody(searchBody)) as ESSearchResponse< + unknown, + { body: { aggs: typeof searchBody } }, + { restTotalHitsAsInt: true } + >; const minValue = minMaxResult.aggregations!.sample.min_value.value; const maxValue = minMaxResult.aggregations!.sample.max_value.value; @@ -178,11 +180,10 @@ export async function getNumberHistogram( }, }, }; - const histogramResult = (await aggSearchWithBody( - histogramBody - )) as AggregationSearchResponseWithTotalHitsAsInt< + const histogramResult = (await aggSearchWithBody(histogramBody)) as ESSearchResponse< unknown, - { body: { aggs: typeof histogramBody } } + { body: { aggs: typeof histogramBody } }, + { restTotalHitsAsInt: true } >; return { @@ -214,11 +215,10 @@ export async function getStringSamples( }, }, }; - const topValuesResult = (await aggSearchWithBody( - topValuesBody - )) as AggregationSearchResponseWithTotalHitsAsInt< + const topValuesResult = (await aggSearchWithBody(topValuesBody)) as ESSearchResponse< unknown, - { body: { aggs: typeof topValuesBody } } + { body: { aggs: typeof topValuesBody } }, + { restTotalHitsAsInt: true } >; return { @@ -263,11 +263,10 @@ export async function getDateHistogram( const histogramBody = { histo: { date_histogram: { field: field.name, fixed_interval: fixedInterval } }, }; - const results = (await aggSearchWithBody( - histogramBody - )) as AggregationSearchResponseWithTotalHitsAsInt< + const results = (await aggSearchWithBody(histogramBody)) as ESSearchResponse< unknown, - { body: { aggs: typeof histogramBody } } + { body: { aggs: typeof histogramBody } }, + { restTotalHitsAsInt: true } >; return { diff --git a/x-pack/legacy/plugins/lens/server/usage/task.ts b/x-pack/legacy/plugins/lens/server/usage/task.ts index ba74bcd240886..8fc6c8cbefe8a 100644 --- a/x-pack/legacy/plugins/lens/server/usage/task.ts +++ b/x-pack/legacy/plugins/lens/server/usage/task.ts @@ -8,17 +8,13 @@ import moment from 'moment'; import KbnServer, { Server } from 'src/legacy/server/kbn_server'; import { CoreSetup } from 'src/core/server'; import { CallClusterOptions } from 'src/legacy/core_plugins/elasticsearch'; -// This import has the side effect of allowing us to use the elasticsearch type -// extensions below. Without this import, the compiler is unable to find these -// in tests -import {} from '../../../apm/typings/elasticsearch'; import { SearchParams, DeleteDocumentByQueryParams, SearchResponse, DeleteDocumentByQueryResponse, - AggregationSearchResponseWithTotalHitsAsInt, } from 'elasticsearch'; +import { ESSearchResponse } from '../../../apm/typings/elasticsearch'; import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; import { RunContext } from '../../../task_manager'; import { getVisualizationCounts } from './visualization_counts'; @@ -135,11 +131,12 @@ export async function getDailyEvents( }, }; - const metrics: AggregationSearchResponseWithTotalHitsAsInt< + const metrics: ESSearchResponse< unknown, { body: { aggs: typeof aggs }; - } + }, + { restTotalHitsAsInt: true } > = await callCluster('search', { index: kibanaIndex, rest_total_hits_as_int: true,