diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx index baa744d4a836a..6330867d6c23d 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx @@ -15,6 +15,9 @@ import { i18n } from '@kbn/i18n'; import { orderBy } from 'lodash'; import React, { useState } from 'react'; import uuid from 'uuid'; +import { EuiCallOut } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiCode } from '@elastic/eui'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; @@ -24,13 +27,21 @@ import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; import { getTimeRangeComparison } from '../../../shared/time_comparison/get_time_range_comparison'; import { OverviewTableContainer } from '../../../shared/overview_table_container'; import { getColumns } from './get_columns'; +import { ElasticDocsLink } from '../../../shared/Links/ElasticDocsLink'; type ApiResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/main_statistics'>; -const INITIAL_STATE = { - transactionGroups: [] as ApiResponse['transactionGroups'], + +type InitialState = ApiResponse & { + requestId: string; + transactionGroupsTotalItems: number; +}; + +const INITIAL_STATE: InitialState = { + transactionGroups: [], isAggregationAccurate: true, requestId: '', transactionGroupsTotalItems: 0, + bucketSize: 0, }; type SortField = 'name' | 'latency' | 'throughput' | 'errorRate' | 'impact'; @@ -43,11 +54,13 @@ const DEFAULT_SORT = { interface Props { hideViewTransactionsLink?: boolean; numberOfTransactionsPerPage?: number; + showAggregationAccurateCallout?: boolean; } export function ServiceOverviewTransactionsTable({ hideViewTransactionsLink = false, numberOfTransactionsPerPage = 5, + showAggregationAccurateCallout = false, }: Props) { const [tableOptions, setTableOptions] = useState<{ pageIndex: number; @@ -140,7 +153,13 @@ export function ServiceOverviewTransactionsTable({ ] ); - const { transactionGroups, requestId, transactionGroupsTotalItems } = data; + const { + transactionGroups, + requestId, + transactionGroupsTotalItems, + isAggregationAccurate, + bucketSize, + } = data; const { data: transactionGroupDetailedStatistics, @@ -235,6 +254,44 @@ export function ServiceOverviewTransactionsTable({ )} + {showAggregationAccurateCallout && !isAggregationAccurate && ( + + + + xpack.apm.ui.transactionGroupBucketSize + ), + }} + /> + + + {i18n.translate( + 'xpack.apm.transactionCardinalityWarning.docsLink', + { defaultMessage: 'Learn more in the docs' } + )} + + + + + )} diff --git a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx index ccb5fea72432c..eed0750a9e390 100644 --- a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx @@ -15,8 +15,6 @@ import { TraceList } from './trace_list'; type TracesAPIResponse = APIReturnType<'GET /api/apm/traces'>; const DEFAULT_RESPONSE: TracesAPIResponse = { items: [], - isAggregationAccurate: true, - bucketSize: 0, }; export function TraceOverview() { diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx index 6813f2a24fcb7..6f857c3e76128 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -5,9 +5,7 @@ * 2.0. */ -import { EuiCallOut, EuiCode, EuiPanel, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiPanel, EuiSpacer } from '@elastic/eui'; import { Location } from 'history'; import React from 'react'; import { useLocation } from 'react-router-dom'; @@ -15,11 +13,9 @@ import { useApmServiceContext } from '../../../context/apm_service/use_apm_servi import { IUrlParams } from '../../../context/url_params_context/types'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { TransactionCharts } from '../../shared/charts/transaction_charts'; -import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; import { ServiceOverviewTransactionsTable } from '../service_overview/service_overview_transactions_table'; import { useRedirect } from './useRedirect'; -import { useTransactionListFetcher } from './use_transaction_list'; function getRedirectLocation({ location, @@ -51,8 +47,6 @@ export function TransactionOverview() { // redirect to first transaction type useRedirect(getRedirectLocation({ location, transactionType, urlParams })); - const { transactionListData } = useTransactionListFetcher(); - // TODO: improve urlParams typings. // `serviceName` or `transactionType` will never be undefined here, and this check should not be needed if (!serviceName) { @@ -64,51 +58,10 @@ export function TransactionOverview() { - {/* TODO: check if it should be calculated in the new table */} - {!transactionListData.isAggregationAccurate && ( - - - xpack.apm.ui.transactionGroupBucketSize - ), - }} - /> - - - {i18n.translate( - 'xpack.apm.transactionCardinalityWarning.docsLink', - { defaultMessage: 'Learn more in the docs' } - )} - - - - )} - - {/* */} > diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_list/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_list/index.tsx deleted file mode 100644 index dc3bf924d6fdc..0000000000000 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_list/index.tsx +++ /dev/null @@ -1,162 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiToolTip, EuiIconTip } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React, { useMemo } from 'react'; -import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { - asMillisecondDuration, - asTransactionRate, -} from '../../../../../common/utils/formatters'; -import { truncate } from '../../../../utils/style'; -import { ImpactBar } from '../../../shared/ImpactBar'; -import { ITableColumn, ManagedTable } from '../../../shared/managed_table'; -import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; -import { EmptyMessage } from '../../../shared/EmptyMessage'; -import { TransactionDetailLink } from '../../../shared/Links/apm/transaction_detail_link'; - -type TransactionGroup = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups'>['items'][0]; - -// Truncate both the link and the child span (the tooltip anchor.) The link so -// it doesn't overflow, and the anchor so we get the ellipsis. -const TransactionNameLink = euiStyled(TransactionDetailLink)` - font-family: ${({ theme }) => theme.eui.euiCodeFontFamily}; - white-space: nowrap; - ${truncate('100%')}; - - > span { - ${truncate('100%')}; - } -`; - -interface Props { - items: TransactionGroup[]; - isLoading: boolean; -} - -export function TransactionList({ items, isLoading }: Props) { - const { - urlParams: { latencyAggregationType }, - } = useUrlParams(); - const columns: Array> = useMemo( - () => [ - { - field: 'name', - name: i18n.translate('xpack.apm.transactionsTable.nameColumnLabel', { - defaultMessage: 'Name', - }), - width: '50%', - sortable: true, - render: ( - _, - { serviceName, transactionName, transactionType }: TransactionGroup - ) => { - return ( - - - <>{transactionName}> - - - ); - }, - }, - { - field: 'averageResponseTime', - name: i18n.translate( - 'xpack.apm.transactionsTable.avgDurationColumnLabel', - { - defaultMessage: 'Avg. duration', - } - ), - sortable: true, - dataType: 'number', - render: (time: number) => asMillisecondDuration(time), - }, - { - field: 'p95', - name: i18n.translate( - 'xpack.apm.transactionsTable.95thPercentileColumnLabel', - { - defaultMessage: '95th percentile', - } - ), - sortable: true, - dataType: 'number', - render: (time: number) => asMillisecondDuration(time), - }, - { - field: 'transactionsPerMinute', - name: i18n.translate( - 'xpack.apm.transactionsTable.throughputColumnLabel', - { defaultMessage: 'Throughput' } - ), - sortable: true, - dataType: 'number', - render: (value: number) => asTransactionRate(value), - }, - { - field: 'impact', - name: ( - <> - {i18n.translate('xpack.apm.transactionsTable.impactColumnLabel', { - defaultMessage: 'Impact', - })}{' '} - - > - ), - sortable: true, - dataType: 'number', - render: (value: number) => , - }, - ], - [latencyAggregationType] - ); - - const noItemsMessage = ( - - ); - - return ( - : noItemsMessage} - columns={columns} - items={items} - initialSortField="impact" - initialSortDirection="desc" - initialPageSize={25} - /> - ); -} diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts b/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts deleted file mode 100644 index 59207a6a499a2..0000000000000 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts +++ /dev/null @@ -1,53 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { APIReturnType } from '../../../services/rest/createCallApmApi'; -import { useFetcher } from '../../../hooks/use_fetcher'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; - -type TransactionsAPIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups'>; - -const DEFAULT_RESPONSE: Partial = { - items: undefined, - isAggregationAccurate: true, - bucketSize: 0, -}; - -export function useTransactionListFetcher() { - const { - urlParams: { environment, kuery, transactionType, start, end }, - } = useUrlParams(); - const { serviceName } = useApmServiceContext(); - - const { data = DEFAULT_RESPONSE, error, status } = useFetcher( - (callApmApi) => { - if (serviceName && start && end && transactionType) { - return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/transactions/groups', - params: { - path: { serviceName }, - query: { - environment, - kuery, - start, - end, - transactionType, - }, - }, - }); - } - }, - [environment, kuery, serviceName, start, end, transactionType] - ); - - return { - transactionListData: data, - transactionListStatus: status, - transactionListError: error, - }; -} diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts index 223abf972ee24..e3f2795eb38e8 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts @@ -52,7 +52,8 @@ export async function getServiceTransactionGroups({ transactionType: string; latencyAggregationType: LatencyAggregationType; }) { - const { apmEventClient, start, end } = setup; + const { apmEventClient, start, end, config } = setup; + const bucketSize = config['xpack.apm.ui.transactionGroupBucketSize']; const field = getTransactionDurationFieldForAggregatedTransactions( searchAggregatedTransactions @@ -89,7 +90,7 @@ export async function getServiceTransactionGroups({ transaction_groups: { terms: { field: TRANSACTION_NAME, - size: 500, + size: bucketSize, order: { _count: 'desc' }, }, aggs: { @@ -147,5 +148,6 @@ export async function getServiceTransactionGroups({ isAggregationAccurate: (response.aggregations?.transaction_groups.sum_other_doc_count ?? 0) === 0, + bucketSize, }; } diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap index a4ff487645a4b..1d9a2aed42bca 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap @@ -59,13 +59,6 @@ Array [ }, }, ], - "must_not": Array [ - Object { - "exists": Object { - "field": "parent.id", - }, - }, - ], }, }, "size": 0, @@ -121,13 +114,6 @@ Array [ }, }, ], - "must_not": Array [ - Object { - "exists": Object { - "field": "parent.id", - }, - }, - ], }, }, "size": 0, @@ -183,231 +169,6 @@ Array [ }, }, ], - "must_not": Array [ - Object { - "exists": Object { - "field": "parent.id", - }, - }, - ], - }, - }, - "size": 0, - }, - }, -] -`; - -exports[`transaction group queries fetches top transactions 1`] = ` -Array [ - Object { - "apm": Object { - "events": Array [ - "transaction", - ], - }, - "body": Object { - "aggs": Object { - "transaction_groups": Object { - "aggs": Object { - "transaction_type": Object { - "top_metrics": Object { - "metrics": Array [ - Object { - "field": "transaction.type", - }, - ], - "sort": Object { - "@timestamp": "desc", - }, - }, - }, - }, - "terms": Object { - "field": "transaction.name", - "size": 101, - }, - }, - }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "term": Object { - "service.name": "foo", - }, - }, - Object { - "term": Object { - "transaction.type": "bar", - }, - }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, - ], - }, - }, - "size": 0, - }, - }, - Object { - "apm": Object { - "events": Array [ - "transaction", - ], - }, - "body": Object { - "aggs": Object { - "transaction_groups": Object { - "aggs": Object { - "avg": Object { - "avg": Object { - "field": "transaction.duration.us", - }, - }, - }, - "terms": Object { - "field": "transaction.name", - "size": 101, - }, - }, - }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "term": Object { - "service.name": "foo", - }, - }, - Object { - "term": Object { - "transaction.type": "bar", - }, - }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, - ], - }, - }, - "size": 0, - }, - }, - Object { - "apm": Object { - "events": Array [ - "transaction", - ], - }, - "body": Object { - "aggs": Object { - "transaction_groups": Object { - "aggs": Object { - "sum": Object { - "sum": Object { - "field": "transaction.duration.us", - }, - }, - }, - "terms": Object { - "field": "transaction.name", - "size": 101, - }, - }, - }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "term": Object { - "service.name": "foo", - }, - }, - Object { - "term": Object { - "transaction.type": "bar", - }, - }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, - ], - }, - }, - "size": 0, - }, - }, - Object { - "apm": Object { - "events": Array [ - "transaction", - ], - }, - "body": Object { - "aggs": Object { - "transaction_groups": Object { - "aggs": Object { - "p95": Object { - "percentiles": Object { - "field": "transaction.duration.us", - "hdr": Object { - "number_of_significant_value_digits": 2, - }, - "percents": Array [ - 95, - ], - }, - }, - }, - "terms": Object { - "field": "transaction.name", - "size": 101, - }, - }, - }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "term": Object { - "service.name": "foo", - }, - }, - Object { - "term": Object { - "transaction.type": "bar", - }, - }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, - ], }, }, "size": 0, diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts index 85f36b3999060..28c1290204f5c 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -5,73 +5,126 @@ * 2.0. */ -import { sortBy, take } from 'lodash'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; +import { sortBy } from 'lodash'; import moment from 'moment'; import { Unionize } from 'utility-types'; -import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { AggregationOptionsByType } from '../../../../../../src/core/types/elasticsearch'; -import { PromiseReturnType } from '../../../../observability/typings/common'; +import { kqlQuery, rangeQuery } from '../../../../observability/server'; import { SERVICE_NAME, TRANSACTION_NAME, } from '../../../common/elasticsearch_fieldnames'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { joinByKey } from '../../../common/utils/join_by_key'; -import { getTransactionGroupsProjection } from '../../projections/transaction_groups'; -import { mergeProjection } from '../../projections/util/merge_projection'; import { withApmSpan } from '../../utils/with_apm_span'; -import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { - getAverages, - getCounts, - getPercentiles, - getSums, -} from './get_transaction_group_stats'; + getDocumentTypeFilterForAggregatedTransactions, + getProcessorEventForAggregatedTransactions, +} from '../helpers/aggregated_transactions'; +import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { getAverages, getCounts, getSums } from './get_transaction_group_stats'; -interface TopTransactionOptions { +export interface TopTraceOptions { environment?: string; kuery?: string; - type: 'top_transactions'; - serviceName: string; - transactionType: string; transactionName?: string; searchAggregatedTransactions: boolean; } -interface TopTraceOptions { - environment?: string; - kuery?: string; - type: 'top_traces'; - transactionName?: string; - searchAggregatedTransactions: boolean; -} +type Key = Record<'service.name' | 'transaction.name', string>; -export type Options = TopTransactionOptions | TopTraceOptions; +export interface TransactionGroup { + key: Key; + serviceName: string; + transactionName: string; + transactionType: string; + averageResponseTime: number | null | undefined; + transactionsPerMinute: number; + impact: number; +} -export type ESResponse = PromiseReturnType; +export type ESResponse = Promise<{ items: TransactionGroup[] }>; -export type TransactionGroupRequestBase = ReturnType< - typeof getTransactionGroupsProjection -> & { +export type TransactionGroupRequestBase = ReturnType & { body: { aggs: { - transaction_groups: Unionize< - Pick - >; + transaction_groups: Unionize>; }; }; }; +function getRequest( + topTraceOptions: TopTraceOptions, + setup: TransactionGroupSetup +) { + const { start, end } = setup; + + const { + searchAggregatedTransactions, + environment, + kuery, + transactionName, + } = topTraceOptions; + + const transactionNameFilter = transactionName + ? [{ term: { [TRANSACTION_NAME]: transactionName } }] + : []; + + return { + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + ...transactionNameFilter, + ...getDocumentTypeFilterForAggregatedTransactions( + searchAggregatedTransactions + ), + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ] as QueryDslQueryContainer[], + }, + }, + aggs: { + transaction_groups: { + composite: { + sources: asMutableArray([ + { [SERVICE_NAME]: { terms: { field: SERVICE_NAME } } }, + { + [TRANSACTION_NAME]: { + terms: { field: TRANSACTION_NAME }, + }, + }, + ] as const), + // traces overview is hardcoded to 10000 + size: 10000, + }, + }, + }, + }, + }; +} + export type TransactionGroupSetup = Setup & SetupTimeRange; function getItemsWithRelativeImpact( setup: TransactionGroupSetup, items: Array<{ sum?: number | null; - key: string | Record<'service.name' | 'transaction.name', string>; + key: Key; avg?: number | null; count?: number | null; transactionType?: string; - p95?: number | null; }> ) { const values = items @@ -94,137 +147,47 @@ function getItemsWithRelativeImpact( item.sum !== null && item.sum !== undefined ? ((item.sum - min) / (max - min)) * 100 || 0 : 0, - p95: item.p95, }; }); return itemsWithRelativeImpact; } -export function transactionGroupsFetcher( - options: Options, - setup: TransactionGroupSetup, - bucketSize: number -) { - const spanName = - options.type === 'top_traces' ? 'get_top_traces' : 'get_top_transactions'; - - return withApmSpan(spanName, async () => { - const projection = getTransactionGroupsProjection({ - setup, - options, - }); - - const isTopTraces = options.type === 'top_traces'; - - // @ts-expect-error - delete projection.body.aggs; - - // traces overview is hardcoded to 10000 - // transactions overview: 1 extra bucket is added to check whether the total number of buckets exceed the specified bucket size. - const expectedBucketSize = isTopTraces ? 10000 : bucketSize; - const size = isTopTraces ? 10000 : expectedBucketSize + 1; - - const request = mergeProjection(projection, { - body: { - size: 0, - aggs: { - transaction_groups: { - ...(isTopTraces - ? { - composite: { - sources: asMutableArray([ - { [SERVICE_NAME]: { terms: { field: SERVICE_NAME } } }, - { - [TRANSACTION_NAME]: { - terms: { field: TRANSACTION_NAME }, - }, - }, - ] as const), - size, - }, - } - : { - terms: { - field: TRANSACTION_NAME, - size, - }, - }), - }, - }, - }, - }); +export function topTransactionGroupsFetcher( + topTraceOptions: TopTraceOptions, + setup: TransactionGroupSetup +): Promise<{ items: TransactionGroup[] }> { + return withApmSpan('get_top_traces', async () => { + const request = getRequest(topTraceOptions, setup); const params = { request, setup, - searchAggregatedTransactions: options.searchAggregatedTransactions, + searchAggregatedTransactions: + topTraceOptions.searchAggregatedTransactions, }; - const [counts, averages, sums, percentiles] = await Promise.all([ + const [counts, averages, sums] = await Promise.all([ getCounts(params), getAverages(params), getSums(params), - !isTopTraces ? getPercentiles(params) : Promise.resolve(undefined), ]); - const stats = [ - ...averages, - ...counts, - ...sums, - ...(percentiles ? percentiles : []), - ]; + const stats = [...averages, ...counts, ...sums]; const items = joinByKey(stats, 'key'); const itemsWithRelativeImpact = getItemsWithRelativeImpact(setup, items); - const defaultServiceName = - options.type === 'top_transactions' ? options.serviceName : undefined; - - const itemsWithKeys: TransactionGroup[] = itemsWithRelativeImpact.map( - (item) => { - let transactionName: string; - let serviceName: string; - - if (typeof item.key === 'string') { - transactionName = item.key; - serviceName = defaultServiceName!; - } else { - transactionName = item.key[TRANSACTION_NAME]; - serviceName = item.key[SERVICE_NAME]; - } - - return { - ...item, - transactionName, - serviceName, - }; - } - ); + const itemsWithKeys = itemsWithRelativeImpact.map((item) => ({ + ...item, + transactionName: item.key[TRANSACTION_NAME], + serviceName: item.key[SERVICE_NAME], + })); return { - items: take( - // sort by impact by default so most impactful services are not cut off - sortBy(itemsWithKeys, 'impact').reverse(), - bucketSize - ), - // The aggregation is considered accurate if the configured bucket size is larger or equal to the number of buckets returned - // the actual number of buckets retrieved are `bucketsize + 1` to detect whether it's above the limit - isAggregationAccurate: - expectedBucketSize >= itemsWithRelativeImpact.length, - bucketSize, + // sort by impact by default so most impactful services are not cut off + items: sortBy(itemsWithKeys, 'impact').reverse(), }; }); } - -export interface TransactionGroup { - key: string | Record<'service.name' | 'transaction.name', string>; - serviceName: string; - transactionName: string; - transactionType: string; - averageResponseTime: number | null | undefined; - transactionsPerMinute: number; - p95: number | null | undefined; - impact: number; -} diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts index 34fd86f2fc598..980d8f10610c8 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts @@ -18,7 +18,7 @@ interface MetricParams { searchAggregatedTransactions: boolean; } -type BucketKey = string | Record; +type BucketKey = Record; function mergeRequestWithAggs< TRequestBase extends TransactionGroupRequestBase, @@ -131,35 +131,3 @@ export async function getSums({ }; }); } - -export async function getPercentiles({ - request, - setup, - searchAggregatedTransactions, -}: MetricParams) { - const params = mergeRequestWithAggs(request, { - p95: { - percentiles: { - field: getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ), - hdr: { number_of_significant_value_digits: 2 }, - percents: [95], - }, - }, - }); - - const response = await setup.apmEventClient.search( - 'get_transaction_group_latency_percentiles', - params - ); - - return arrayUnionToCallable( - response.aggregations?.transaction_groups.buckets ?? [] - ).map((bucket) => { - return { - key: bucket.key as BucketKey, - p95: Object.values(bucket.p95.values)[0], - }; - }); -} diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/index.ts b/x-pack/plugins/apm/server/lib/transaction_groups/index.ts index ba2adb4172fcf..7bee3b358a4c3 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/index.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/index.ts @@ -6,12 +6,11 @@ */ import { Setup, SetupTimeRange } from '../helpers/setup_request'; -import { transactionGroupsFetcher, Options } from './fetcher'; +import { topTransactionGroupsFetcher, TopTraceOptions } from './fetcher'; -export async function getTransactionGroupList( - options: Options, +export async function getTopTransactionGroupList( + options: TopTraceOptions, setup: Setup & SetupTimeRange ) { - const bucketSize = setup.config['xpack.apm.ui.transactionGroupBucketSize']; - return await transactionGroupsFetcher(options, setup, bucketSize); + return await topTransactionGroupsFetcher(options, setup); } diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/queries.test.ts b/x-pack/plugins/apm/server/lib/transaction_groups/queries.test.ts index 5c1754cd36ef4..b0309886e8596 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/queries.test.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/queries.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { transactionGroupsFetcher } from './fetcher'; +import { topTransactionGroupsFetcher } from './fetcher'; import { SearchParamsMock, inspectSearchParams, @@ -18,36 +18,13 @@ describe('transaction group queries', () => { mock.teardown(); }); - it('fetches top transactions', async () => { - const bucketSize = 100; - mock = await inspectSearchParams((setup) => - transactionGroupsFetcher( - { - type: 'top_transactions', - serviceName: 'foo', - transactionType: 'bar', - searchAggregatedTransactions: false, - }, - setup, - bucketSize - ) - ); - - const allParams = mock.spy.mock.calls.map((call) => call[1]); - - expect(allParams).toMatchSnapshot(); - }); - it('fetches top traces', async () => { - const bucketSize = 100; mock = await inspectSearchParams((setup) => - transactionGroupsFetcher( + topTransactionGroupsFetcher( { - type: 'top_traces', searchAggregatedTransactions: false, }, - setup, - bucketSize + setup ) ); diff --git a/x-pack/plugins/apm/server/projections/transaction_groups.ts b/x-pack/plugins/apm/server/projections/transaction_groups.ts deleted file mode 100644 index 2381c2964c96a..0000000000000 --- a/x-pack/plugins/apm/server/projections/transaction_groups.ts +++ /dev/null @@ -1,61 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { omit } from 'lodash'; -import { Setup, SetupTimeRange } from '../../server/lib/helpers/setup_request'; -import { - TRANSACTION_NAME, - PARENT_ID, - TRANSACTION_ROOT, -} from '../../common/elasticsearch_fieldnames'; -import { Options } from '../../server/lib/transaction_groups/fetcher'; -import { getTransactionsProjection } from './transactions'; -import { mergeProjection } from './util/merge_projection'; - -export function getTransactionGroupsProjection({ - setup, - options, -}: { - setup: Setup & SetupTimeRange; - options: Options; -}) { - const transactionsProjection = getTransactionsProjection({ - setup, - ...(omit(options, 'type') as Omit), - }); - - if (options.type === 'top_traces') { - if (options.searchAggregatedTransactions) { - transactionsProjection.body.query.bool.filter.push({ - term: { - [TRANSACTION_ROOT]: true, - }, - }); - } else { - // @ts-expect-error: Property 'must_not' does not exist on type '{ filter: ESFilter[]; }'. - transactionsProjection.body.query.bool.must_not = [ - { - exists: { - field: PARENT_ID, - }, - }, - ]; - } - } - - return mergeProjection(transactionsProjection, { - body: { - aggs: { - transactions: { - terms: { - field: TRANSACTION_NAME, - }, - }, - }, - }, - }); -} diff --git a/x-pack/plugins/apm/server/projections/transactions.ts b/x-pack/plugins/apm/server/projections/transactions.ts deleted file mode 100644 index 1efd9679cae9c..0000000000000 --- a/x-pack/plugins/apm/server/projections/transactions.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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; -import { Setup, SetupTimeRange } from '../../server/lib/helpers/setup_request'; -import { - SERVICE_NAME, - TRANSACTION_TYPE, - TRANSACTION_NAME, -} from '../../common/elasticsearch_fieldnames'; -import { rangeQuery, kqlQuery } from '../../../observability/server'; -import { environmentQuery } from '../../common/utils/environment_query'; -import { - getProcessorEventForAggregatedTransactions, - getDocumentTypeFilterForAggregatedTransactions, -} from '../lib/helpers/aggregated_transactions'; - -export function getTransactionsProjection({ - environment, - kuery, - setup, - serviceName, - transactionName, - transactionType, - searchAggregatedTransactions, -}: { - environment?: string; - kuery?: string; - setup: Setup & SetupTimeRange; - serviceName?: string; - transactionName?: string; - transactionType?: string; - searchAggregatedTransactions: boolean; -}) { - const { start, end } = setup; - - const transactionNameFilter = transactionName - ? [{ term: { [TRANSACTION_NAME]: transactionName } }] - : []; - const transactionTypeFilter = transactionType - ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] - : []; - const serviceNameFilter = serviceName - ? [{ term: { [SERVICE_NAME]: serviceName } }] - : []; - - const bool = { - filter: [ - ...serviceNameFilter, - ...transactionNameFilter, - ...transactionTypeFilter, - ...getDocumentTypeFilterForAggregatedTransactions( - searchAggregatedTransactions - ), - ...rangeQuery(start, end), - ...environmentQuery(environment), - ...kqlQuery(kuery), - ] as QueryDslQueryContainer[], - }; - - return { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - query: { - bool, - }, - }, - }; -} diff --git a/x-pack/plugins/apm/server/routes/traces.ts b/x-pack/plugins/apm/server/routes/traces.ts index bed7252dd20fd..11747c847fcbd 100644 --- a/x-pack/plugins/apm/server/routes/traces.ts +++ b/x-pack/plugins/apm/server/routes/traces.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; import { setupRequest } from '../lib/helpers/setup_request'; import { getTrace } from '../lib/traces/get_trace'; -import { getTransactionGroupList } from '../lib/transaction_groups'; +import { getTopTransactionGroupList } from '../lib/transaction_groups'; import { createApmServerRoute } from './create_apm_server_route'; import { environmentRt, kueryRt, rangeRt } from './default_api_types'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; @@ -31,8 +31,8 @@ const tracesRoute = createApmServerRoute({ kuery, }); - return getTransactionGroupList( - { environment, kuery, type: 'top_traces', searchAggregatedTransactions }, + return getTopTransactionGroupList( + { environment, kuery, searchAggregatedTransactions }, setup ); }, diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index 0cd8289ffedbd..f211e722958c5 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -19,7 +19,6 @@ import { getTransactionBreakdown } from '../lib/transactions/breakdown'; import { getTransactionDistribution } from '../lib/transactions/distribution'; import { getAnomalySeries } from '../lib/transactions/get_anomaly_data'; import { getLatencyPeriods } from '../lib/transactions/get_latency_charts'; -import { getTransactionGroupList } from '../lib/transaction_groups'; import { getErrorRatePeriods } from '../lib/transaction_groups/get_error_rate'; import { createApmServerRoute } from './create_apm_server_route'; import { createApmServerRouteRepository } from './create_apm_server_route_repository'; @@ -30,49 +29,6 @@ import { rangeRt, } from './default_api_types'; -/** - * Returns a list of transactions grouped by name - * //TODO: delete this once we moved away from the old table in the transaction overview page. It should be replaced by /transactions/groups/main_statistics/ - */ -const transactionGroupsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/transactions/groups', - params: t.type({ - path: t.type({ - serviceName: t.string, - }), - query: t.intersection([ - t.type({ transactionType: t.string }), - environmentRt, - kueryRt, - rangeRt, - ]), - }), - options: { tags: ['access:apm'] }, - handler: async (resources) => { - const setup = await setupRequest(resources); - const { params } = resources; - const { serviceName } = params.path; - const { environment, kuery, transactionType } = params.query; - - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ - ...setup, - kuery, - }); - - return getTransactionGroupList( - { - environment, - kuery, - type: 'top_transactions', - serviceName, - transactionType, - searchAggregatedTransactions, - }, - setup - ); - }, -}); - const transactionGroupsMainStatisticsRoute = createApmServerRoute({ endpoint: 'GET /api/apm/services/{serviceName}/transactions/groups/main_statistics', @@ -388,7 +344,6 @@ const transactionChartsErrorRateRoute = createApmServerRoute({ }); export const transactionRouteRepository = createApmServerRouteRepository() - .add(transactionGroupsRoute) .add(transactionGroupsMainStatisticsRoute) .add(transactionGroupsDetailedStatisticsRoute) .add(transactionLatencyChartsRoute) diff --git a/x-pack/test/apm_api_integration/tests/feature_controls.ts b/x-pack/test/apm_api_integration/tests/feature_controls.ts index b56cd4b9c6ed8..2f24e947d053c 100644 --- a/x-pack/test/apm_api_integration/tests/feature_controls.ts +++ b/x-pack/test/apm_api_integration/tests/feature_controls.ts @@ -100,13 +100,6 @@ export default function featureControlsTests({ getService }: FtrProviderContext) expectForbidden: expect403, expectResponse: expect200, }, - { - req: { - url: `/api/apm/services/foo/transactions/groups?start=${start}&end=${end}&transactionType=bar`, - }, - expectForbidden: expect403, - expectResponse: expect200, - }, { req: { url: `/api/apm/services/foo/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=bar&latencyAggregationType=avg`, diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index a8748b73b1203..4b102402f6f73 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -166,10 +166,6 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte loadTestFile(require.resolve('./transactions/latency')); }); - describe('transactions/top_transaction_groups', function () { - loadTestFile(require.resolve('./transactions/top_transaction_groups')); - }); - describe('transactions/transactions_groups_main_statistics', function () { loadTestFile(require.resolve('./transactions/transactions_groups_main_statistics')); }); diff --git a/x-pack/test/apm_api_integration/tests/traces/top_traces.ts b/x-pack/test/apm_api_integration/tests/traces/top_traces.ts index debdfe461e6fb..5547c9b6ce67b 100644 --- a/x-pack/test/apm_api_integration/tests/traces/top_traces.ts +++ b/x-pack/test/apm_api_integration/tests/traces/top_traces.ts @@ -27,7 +27,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expect(response.body.items.length).to.be(0); - expect(response.body.isAggregationAccurate).to.be(true); }); }); diff --git a/x-pack/test/apm_api_integration/tests/transactions/top_transaction_groups.ts b/x-pack/test/apm_api_integration/tests/transactions/top_transaction_groups.ts deleted file mode 100644 index 2125a61ac4ba2..0000000000000 --- a/x-pack/test/apm_api_integration/tests/transactions/top_transaction_groups.ts +++ /dev/null @@ -1,72 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { sortBy } from 'lodash'; -import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { registry } from '../../common/registry'; - -function sortTransactionGroups(items: any[]) { - return sortBy(items, 'impact'); -} - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(metadata.start); - const end = encodeURIComponent(metadata.end); - const transactionType = 'request'; - - registry.when( - 'Top transaction groups when data is not loaded', - { config: 'basic', archives: [] }, - () => { - it('handles empty state', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/groups?start=${start}&end=${end}&transactionType=${transactionType}` - ); - - expect(response.status).to.be(200); - - expect(response.body.isAggregationAccurate).to.be(true); - expect(response.body.items.length).to.be(0); - }); - } - ); - - registry.when( - 'Top transaction groups when data is loaded', - { config: 'basic', archives: [archiveName] }, - () => { - let response: any; - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/groups?start=${start}&end=${end}&transactionType=${transactionType}` - ); - }); - - it('returns the correct metadata', () => { - expect(response.status).to.be(200); - expect(response.body.isAggregationAccurate).to.be(true); - expect(response.body.items.length).to.be.greaterThan(0); - }); - - it('returns the correct number of buckets', () => { - expectSnapshot(response.body.items.length).toMatchInline(`15`); - }); - - it('returns the correct buckets (when ignoring samples)', async () => { - expectSnapshot(sortTransactionGroups(response.body.items)).toMatch(); - }); - } - ); -}
+ xpack.apm.ui.transactionGroupBucketSize + ), + }} + /> + + + {i18n.translate( + 'xpack.apm.transactionCardinalityWarning.docsLink', + { defaultMessage: 'Learn more in the docs' } + )} + +
- xpack.apm.ui.transactionGroupBucketSize - ), - }} - /> - - - {i18n.translate( - 'xpack.apm.transactionCardinalityWarning.docsLink', - { defaultMessage: 'Learn more in the docs' } - )} - -