From 549edb99b63ab4747318fb80666c5c0ec5529249 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 30 Jul 2021 11:16:14 -0400 Subject: [PATCH 1/9] adding comparison to transactions pages --- .../apm/public/components/routing/service_detail/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx b/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx index 19db296c428c8..6404d28f2c36f 100644 --- a/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx @@ -101,6 +101,7 @@ export const serviceDetail = { element: , searchBarOptions: { showTransactionTypeSelector: true, + showTimeComparison: true, }, }), children: [ From 90ce3d0241ca8e5726e05d75e33d8380153b03d0 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 30 Jul 2021 12:55:57 -0400 Subject: [PATCH 2/9] adding new transactions table --- .../index.tsx | 48 +++++++++++------- .../app/transaction_overview/index.tsx | 9 ++-- .../transaction_list.stories.tsx | 50 ------------------- 3 files changed, 35 insertions(+), 72 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/app/transaction_overview/transaction_list/transaction_list.stories.tsx 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 b2d7424f1aa49..baa744d4a836a 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 @@ -35,13 +35,20 @@ const INITIAL_STATE = { type SortField = 'name' | 'latency' | 'throughput' | 'errorRate' | 'impact'; type SortDirection = 'asc' | 'desc'; -const PAGE_SIZE = 5; const DEFAULT_SORT = { direction: 'desc' as const, field: 'impact' as const, }; -export function ServiceOverviewTransactionsTable() { +interface Props { + hideViewTransactionsLink?: boolean; + numberOfTransactionsPerPage?: number; +} + +export function ServiceOverviewTransactionsTable({ + hideViewTransactionsLink = false, + numberOfTransactionsPerPage = 5, +}: Props) { const [tableOptions, setTableOptions] = useState<{ pageIndex: number; sort: { @@ -100,7 +107,10 @@ export function ServiceOverviewTransactionsTable() { response.transactionGroups, field, direction - ).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE); + ).slice( + pageIndex * numberOfTransactionsPerPage, + (pageIndex + 1) * numberOfTransactionsPerPage + ); return { ...response, @@ -186,7 +196,7 @@ export function ServiceOverviewTransactionsTable() { const pagination = { pageIndex, - pageSize: PAGE_SIZE, + pageSize: numberOfTransactionsPerPage, totalItemCount: transactionGroupsTotalItems, hidePerPageOptions: true, }; @@ -207,20 +217,22 @@ export function ServiceOverviewTransactionsTable() { - - - {i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableLinkText', - { - defaultMessage: 'View transactions', - } - )} - - + {!hideViewTransactionsLink && ( + + + {i18n.translate( + 'xpack.apm.serviceOverview.transactionsTableLinkText', + { + defaultMessage: 'View transactions', + } + )} + + + )} 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 819292095403a..ee05b5ef353a9 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 @@ -23,6 +23,7 @@ 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 { TransactionList } from './transaction_list'; import { useRedirect } from './useRedirect'; import { useTransactionListFetcher } from './use_transaction_list'; @@ -73,10 +74,6 @@ export function TransactionOverview() { - -

Transactions

-
- {!transactionListData.isAggregationAccurate && ( +
); diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_list/transaction_list.stories.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_list/transaction_list.stories.tsx deleted file mode 100644 index 0253e39b99503..0000000000000 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_list/transaction_list.stories.tsx +++ /dev/null @@ -1,50 +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 React, { ComponentType } from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; -import { TransactionList } from './'; - -type TransactionGroup = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups'>['items'][0]; - -export default { - title: 'app/TransactionOverview/TransactionList', - component: TransactionList, - decorators: [ - (Story: ComponentType) => ( - - - - - - ), - ], -}; - -export function SingleRow() { - const items: TransactionGroup[] = [ - { - key: { - ['service.name']: 'adminconsole', - ['transaction.name']: - 'GET /api/v1/regions/azure-eastus2/clusters/elasticsearch/xc18de071deb4262be54baebf5f6a1ce/proxy/_snapshot/found-snapshots/_all', - }, - transactionName: - 'GET /api/v1/regions/azure-eastus2/clusters/elasticsearch/xc18de071deb4262be54baebf5f6a1ce/proxy/_snapshot/found-snapshots/_all', - serviceName: 'adminconsole', - transactionType: 'request', - p95: 11974156, - averageResponseTime: 8087434.558974359, - transactionsPerMinute: 0.40625, - impact: 100, - }, - ]; - - return ; -} From 6ddf2af36d343570093691b5f5b3e8ea918ff78a Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 30 Jul 2021 14:37:20 -0400 Subject: [PATCH 3/9] adding throughput --- .../app/transaction_overview/index.tsx | 19 +-- .../charts/transaction_charts/index.tsx | 37 +---- ...se_transaction_throughput_chart_fetcher.ts | 63 -------- .../throughput_chart_selectors.test.ts | 81 ---------- .../selectors/throughput_chart_selectors.ts | 84 ----------- .../get_throughput_charts/index.ts | 140 ------------------ .../get_throughput_charts/transform.ts | 55 ------- .../plugins/apm/server/routes/transactions.ts | 52 +------ .../tests/feature_controls.ts | 14 -- .../test/apm_api_integration/tests/index.ts | 4 - .../tests/transactions/throughput.ts | 71 --------- 11 files changed, 10 insertions(+), 610 deletions(-) delete mode 100644 x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts delete mode 100644 x-pack/plugins/apm/public/selectors/throughput_chart_selectors.test.ts delete mode 100644 x-pack/plugins/apm/public/selectors/throughput_chart_selectors.ts delete mode 100644 x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts delete mode 100644 x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/transform.ts delete mode 100644 x-pack/test/apm_api_integration/tests/transactions/throughput.ts 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 ee05b5ef353a9..6813f2a24fcb7 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,13 +5,7 @@ * 2.0. */ -import { - EuiCallOut, - EuiCode, - EuiPanel, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; +import { EuiCallOut, EuiCode, EuiPanel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { Location } from 'history'; @@ -24,7 +18,6 @@ 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 { TransactionList } from './transaction_list'; import { useRedirect } from './useRedirect'; import { useTransactionListFetcher } from './use_transaction_list'; @@ -58,10 +51,7 @@ export function TransactionOverview() { // redirect to first transaction type useRedirect(getRedirectLocation({ location, transactionType, urlParams })); - const { - transactionListData, - transactionListStatus, - } = useTransactionListFetcher(); + const { transactionListData } = useTransactionListFetcher(); // TODO: improve urlParams typings. // `serviceName` or `transactionType` will never be undefined here, and this check should not be needed @@ -74,6 +64,7 @@ export function TransactionOverview() { + {/* TODO: check if it should be calculated in the new table */} {!transactionListData.isAggregationAccurate && ( )} - + /> */} @@ -43,22 +27,7 @@ export function TransactionCharts() {
- - - - {i18n.translate( - 'xpack.apm.metrics.transactionChart.throughputLabel', - { defaultMessage: 'Throughput' } - )} - - - - + diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts deleted file mode 100644 index 72e469178a100..0000000000000 --- a/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts +++ /dev/null @@ -1,63 +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 { useMemo } from 'react'; -import { useFetcher } from './use_fetcher'; -import { useUrlParams } from '../context/url_params_context/use_url_params'; -import { getThroughputChartSelector } from '../selectors/throughput_chart_selectors'; -import { useTheme } from './use_theme'; -import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; - -export function useTransactionThroughputChartsFetcher() { - const { transactionType, serviceName } = useApmServiceContext(); - const theme = useTheme(); - const { - urlParams: { environment, kuery, start, end, transactionName }, - } = useUrlParams(); - - const { data, error, status } = useFetcher( - (callApmApi) => { - if (transactionType && serviceName && start && end) { - return callApmApi({ - endpoint: - 'GET /api/apm/services/{serviceName}/transactions/charts/throughput', - params: { - path: { serviceName }, - query: { - environment, - kuery, - start, - end, - transactionType, - transactionName, - }, - }, - }); - } - }, - [ - environment, - kuery, - serviceName, - start, - end, - transactionName, - transactionType, - ] - ); - - const memoizedData = useMemo( - () => getThroughputChartSelector({ throughputChart: data, theme }), - [data, theme] - ); - - return { - throughputChartsData: memoizedData, - throughputChartsStatus: status, - throughputChartsError: error, - }; -} diff --git a/x-pack/plugins/apm/public/selectors/throughput_chart_selectors.test.ts b/x-pack/plugins/apm/public/selectors/throughput_chart_selectors.test.ts deleted file mode 100644 index b76b77abaa7bd..0000000000000 --- a/x-pack/plugins/apm/public/selectors/throughput_chart_selectors.test.ts +++ /dev/null @@ -1,81 +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 { EuiTheme } from '../../../../../src/plugins/kibana_react/common'; -import { - getThroughputChartSelector, - ThroughputChartsResponse, -} from './throughput_chart_selectors'; - -const theme = { - eui: { - euiColorVis1: 'green', - euiColorVis2: 'black', - euiColorVis3: 'gray', - euiColorVis4: 'blue', - euiColorVis6: 'red', - euiColorVis8: 'yellow', - euiColorSecondary: 'white', - euiColorDanger: 'purple', - }, -} as EuiTheme; - -const throughputData = { - throughputTimeseries: [ - { key: 'HTTP 2xx', avg: 1, dataPoints: [{ x: 1, y: 2 }] }, - { key: 'HTTP 4xx', avg: 1, dataPoints: [{ x: 1, y: 2 }] }, - { key: 'HTTP 5xx', avg: 1, dataPoints: [{ x: 1, y: 2 }] }, - ], -} as ThroughputChartsResponse; - -describe('getThroughputChartSelector', () => { - it('returns default values when data is undefined', () => { - const throughputTimeseries = getThroughputChartSelector({ theme }); - expect(throughputTimeseries).toEqual({ throughputTimeseries: [] }); - }); - - it('returns default values when timeseries is empty', () => { - const throughputTimeseries = getThroughputChartSelector({ - theme, - throughputChart: { throughputTimeseries: [] }, - }); - expect(throughputTimeseries).toEqual({ throughputTimeseries: [] }); - }); - - it('return throughput time series', () => { - const throughputTimeseries = getThroughputChartSelector({ - theme, - throughputChart: throughputData, - }); - - expect(throughputTimeseries).toEqual({ - throughputTimeseries: [ - { - title: 'HTTP 2xx', - data: [{ x: 1, y: 2 }], - legendValue: '1.0 tpm', - type: 'linemark', - color: '#327a42', - }, - { - title: 'HTTP 4xx', - data: [{ x: 1, y: 2 }], - legendValue: '1.0 tpm', - type: 'linemark', - color: '#f5a700', - }, - { - title: 'HTTP 5xx', - data: [{ x: 1, y: 2 }], - legendValue: '1.0 tpm', - type: 'linemark', - color: '#c23c2b', - }, - ], - }); - }); -}); diff --git a/x-pack/plugins/apm/public/selectors/throughput_chart_selectors.ts b/x-pack/plugins/apm/public/selectors/throughput_chart_selectors.ts deleted file mode 100644 index f334212536778..0000000000000 --- a/x-pack/plugins/apm/public/selectors/throughput_chart_selectors.ts +++ /dev/null @@ -1,84 +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 { difference, zipObject } from 'lodash'; -import { EuiTheme } from '../../../../../src/plugins/kibana_react/common'; -import { asTransactionRate } from '../../common/utils/formatters'; -import { Coordinate, TimeSeries } from '../../typings/timeseries'; -import { APIReturnType } from '../services/rest/createCallApmApi'; -import { httpStatusCodeToColor } from '../utils/httpStatusCodeToColor'; - -export type ThroughputChartsResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/throughput'>; - -export interface ThroughputChart { - throughputTimeseries: Array>; -} - -export function getThroughputChartSelector({ - theme, - throughputChart, -}: { - theme: EuiTheme; - throughputChart?: ThroughputChartsResponse; -}): ThroughputChart { - if (!throughputChart) { - return { throughputTimeseries: [] }; - } - - return { - throughputTimeseries: getThroughputTimeseries({ throughputChart, theme }), - }; -} - -function getThroughputTimeseries({ - throughputChart, - theme, -}: { - theme: EuiTheme; - throughputChart: ThroughputChartsResponse; -}) { - const { throughputTimeseries } = throughputChart; - const bucketKeys = throughputTimeseries.map(({ key }) => key); - const getColor = getColorByKey(bucketKeys, theme); - - return throughputTimeseries.map((bucket) => { - return { - title: bucket.key, - data: bucket.dataPoints, - legendValue: asTransactionRate(bucket.avg), - type: 'linemark', - color: getColor(bucket.key), - }; - }); -} - -function colorMatch(key: string, theme: EuiTheme) { - if (/ok|success/i.test(key)) { - return theme.eui.euiColorSecondary; - } else if (/error|fail/i.test(key)) { - return theme.eui.euiColorDanger; - } -} - -function getColorByKey(keys: string[], theme: EuiTheme) { - const assignedColors = ['HTTP 2xx', 'HTTP 3xx', 'HTTP 4xx', 'HTTP 5xx']; - - const unknownKeys = difference(keys, assignedColors); - const unassignedColors: Record = zipObject(unknownKeys, [ - theme.eui.euiColorVis1, - theme.eui.euiColorVis3, - theme.eui.euiColorVis4, - theme.eui.euiColorVis6, - theme.eui.euiColorVis2, - theme.eui.euiColorVis8, - ]); - - return (key: string) => - colorMatch(key, theme) || - httpStatusCodeToColor(key) || - unassignedColors[key]; -} diff --git a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts deleted file mode 100644 index d5fff20496280..0000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts +++ /dev/null @@ -1,140 +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 { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; -import { PromiseReturnType } from '../../../../../observability/typings/common'; -import { - SERVICE_NAME, - TRANSACTION_NAME, - TRANSACTION_RESULT, - TRANSACTION_TYPE, -} from '../../../../common/elasticsearch_fieldnames'; -import { kqlQuery, rangeQuery } from '../../../../../observability/server'; -import { environmentQuery } from '../../../../common/utils/environment_query'; -import { - getDocumentTypeFilterForAggregatedTransactions, - getProcessorEventForAggregatedTransactions, -} from '../../../lib/helpers/aggregated_transactions'; -import { Setup, SetupTimeRange } from '../../../lib/helpers/setup_request'; -import { getBucketSizeForAggregatedTransactions } from '../../helpers/get_bucket_size_for_aggregated_transactions'; -import { getThroughputBuckets } from './transform'; - -export type ThroughputChartsResponse = PromiseReturnType< - typeof searchThroughput ->; - -function searchThroughput({ - environment, - kuery, - serviceName, - transactionType, - transactionName, - setup, - searchAggregatedTransactions, - intervalString, -}: { - environment?: string; - kuery?: string; - serviceName: string; - transactionType: string; - transactionName: string | undefined; - setup: Setup & SetupTimeRange; - searchAggregatedTransactions: boolean; - intervalString: string; -}) { - const { start, end, apmEventClient } = setup; - - const filter: ESFilter[] = [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, - ...getDocumentTypeFilterForAggregatedTransactions( - searchAggregatedTransactions - ), - ...rangeQuery(start, end), - ...environmentQuery(environment), - ...kqlQuery(kuery), - ]; - - if (transactionName) { - filter.push({ term: { [TRANSACTION_NAME]: transactionName } }); - } - - const params = { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { bool: { filter } }, - aggs: { - throughput: { - terms: { field: TRANSACTION_RESULT, missing: '' }, - aggs: { - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, - min_doc_count: 0, - extended_bounds: { min: start, max: end }, - }, - }, - }, - }, - }, - }, - }; - - return apmEventClient.search('get_transaction_throughput_series', params); -} - -export async function getThroughputCharts({ - environment, - kuery, - serviceName, - transactionType, - transactionName, - setup, - searchAggregatedTransactions, -}: { - environment?: string; - kuery?: string; - serviceName: string; - transactionType: string; - transactionName: string | undefined; - setup: Setup & SetupTimeRange; - searchAggregatedTransactions: boolean; -}) { - const { bucketSize, intervalString } = getBucketSizeForAggregatedTransactions( - { - ...setup, - searchAggregatedTransactions, - } - ); - - const response = await searchThroughput({ - environment, - kuery, - serviceName, - transactionType, - transactionName, - setup, - searchAggregatedTransactions, - intervalString, - }); - - return { - throughputTimeseries: getThroughputBuckets({ - throughputResultBuckets: response.aggregations?.throughput.buckets, - bucketSize, - setupTimeRange: setup, - }), - }; -} diff --git a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/transform.ts b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/transform.ts deleted file mode 100644 index 1f26e65c460e5..0000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/transform.ts +++ /dev/null @@ -1,55 +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 { sortBy } from 'lodash'; -import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; -import { ThroughputChartsResponse } from '.'; -import { calculateThroughput } from '../../helpers/calculate_throughput'; -import { SetupTimeRange } from '../../helpers/setup_request'; - -type ThroughputResultBuckets = Required['aggregations']['throughput']['buckets']; - -export function getThroughputBuckets({ - throughputResultBuckets = [], - bucketSize, - setupTimeRange, -}: { - throughputResultBuckets?: ThroughputResultBuckets; - bucketSize: number; - setupTimeRange: SetupTimeRange; -}) { - const { start, end } = setupTimeRange; - const buckets = throughputResultBuckets.map( - ({ key: resultKey, timeseries }) => { - const dataPoints = timeseries.buckets.map((bucket) => { - return { - x: bucket.key, - // divide by minutes - y: bucket.doc_count / (bucketSize / 60), - }; - }); - - // Handle empty string result keys - const key = - resultKey === '' ? NOT_AVAILABLE_LABEL : (resultKey as string); - - const docCountTotal = timeseries.buckets - .map((bucket) => bucket.doc_count) - .reduce((a, b) => a + b, 0); - - // calculate average throughput - const avg = calculateThroughput({ start, end, value: docCountTotal }); - - return { key, dataPoints, avg }; - } - ); - - return sortBy( - buckets, - (bucket) => bucket.key.toString().replace(/^HTTP (\d)xx$/, '00$1') // ensure that HTTP 3xx are sorted at the top - ); -} diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index c20de31847e8a..0cd8289ffedbd 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -5,9 +5,8 @@ * 2.0. */ -import { jsonRt } from '@kbn/io-ts-utils'; +import { jsonRt, toNumberRt } from '@kbn/io-ts-utils'; import * as t from 'io-ts'; -import { toNumberRt } from '@kbn/io-ts-utils'; import { LatencyAggregationType, latencyAggregationTypeRt, @@ -20,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 { getThroughputCharts } from '../lib/transactions/get_throughput_charts'; import { getTransactionGroupList } from '../lib/transaction_groups'; import { getErrorRatePeriods } from '../lib/transaction_groups/get_error_rate'; import { createApmServerRoute } from './create_apm_server_route'; @@ -28,8 +26,8 @@ import { createApmServerRouteRepository } from './create_apm_server_route_reposi import { comparisonRangeRt, environmentRt, - rangeRt, kueryRt, + rangeRt, } from './default_api_types'; /** @@ -250,51 +248,6 @@ const transactionLatencyChartsRoute = createApmServerRoute({ }, }); -const transactionThroughputChartsRoute = createApmServerRoute({ - endpoint: - 'GET /api/apm/services/{serviceName}/transactions/charts/throughput', - params: t.type({ - path: t.type({ - serviceName: t.string, - }), - query: t.intersection([ - t.type({ transactionType: t.string }), - t.partial({ transactionName: 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, - transactionName, - } = params.query; - - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ - ...setup, - kuery, - }); - - return await getThroughputCharts({ - environment, - kuery, - serviceName, - transactionType, - transactionName, - setup, - searchAggregatedTransactions, - }); - }, -}); - const transactionChartsDistributionRoute = createApmServerRoute({ endpoint: 'GET /api/apm/services/{serviceName}/transactions/charts/distribution', @@ -439,7 +392,6 @@ export const transactionRouteRepository = createApmServerRouteRepository() .add(transactionGroupsMainStatisticsRoute) .add(transactionGroupsDetailedStatisticsRoute) .add(transactionLatencyChartsRoute) - .add(transactionThroughputChartsRoute) .add(transactionChartsDistributionRoute) .add(transactionChartsBreakdownRoute) .add(transactionChartsErrorRateRoute); 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 553f22fc2279e..b56cd4b9c6ed8 100644 --- a/x-pack/test/apm_api_integration/tests/feature_controls.ts +++ b/x-pack/test/apm_api_integration/tests/feature_controls.ts @@ -121,20 +121,6 @@ export default function featureControlsTests({ getService }: FtrProviderContext) expectForbidden: expect403, expectResponse: expect200, }, - { - req: { - url: `/api/apm/services/foo/transactions/charts/throughput?environment=testing&start=${start}&end=${end}&transactionType=bar`, - }, - expectForbidden: expect403, - expectResponse: expect200, - }, - { - req: { - url: `/api/apm/services/foo/transactions/charts/throughput?environment=testing&start=${start}&end=${end}&transactionType=bar&transactionName=baz`, - }, - expectForbidden: expect403, - expectResponse: expect200, - }, { req: { url: `/api/apm/services/foo/transactions/charts/distribution?start=${start}&end=${end}&transactionType=bar&transactionName=baz`, diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index a00fa1723fa3e..a8748b73b1203 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/throughput', function () { - loadTestFile(require.resolve('./transactions/throughput')); - }); - describe('transactions/top_transaction_groups', function () { loadTestFile(require.resolve('./transactions/top_transaction_groups')); }); diff --git a/x-pack/test/apm_api_integration/tests/transactions/throughput.ts b/x-pack/test/apm_api_integration/tests/transactions/throughput.ts deleted file mode 100644 index 5c2de185fdf79..0000000000000 --- a/x-pack/test/apm_api_integration/tests/transactions/throughput.ts +++ /dev/null @@ -1,71 +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 url from 'url'; -import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; -import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { registry } from '../../common/registry'; - -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, end } = metadata; - - registry.when('Throughput when data is not loaded', { config: 'basic', archives: [] }, () => { - it('handles the empty state', async () => { - const response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-node/transactions/charts/throughput`, - query: { - environment: 'testing', - start, - end, - transactionType: 'request', - }, - }) - ); - - expect(response.status).to.be(200); - - expect(response.body.throughputTimeseries.length).to.be(0); - }); - }); - - registry.when( - 'Throughput when data is loaded', - { config: 'basic', archives: [archiveName] }, - () => { - let response: PromiseReturnType; - - before(async () => { - response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-node/transactions/charts/throughput`, - query: { - environment: 'testing', - start, - end, - transactionType: 'request', - }, - }) - ); - }); - - it('returns throughput timeseries', async () => { - expect(response.status).to.be(200); - - expect(response.body.throughputTimeseries.length).to.be.greaterThan(0); - }); - } - ); -} From 2882952c7ae2d3a838f7248318df533ea581fc72 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 30 Jul 2021 17:01:20 -0400 Subject: [PATCH 4/9] refactoring transacon group api --- .../index.tsx | 63 ++++- .../components/app/trace_overview/index.tsx | 2 - .../app/transaction_overview/index.tsx | 51 +--- .../transaction_list/index.tsx | 162 ------------ .../use_transaction_list.ts | 53 ---- .../get_service_transaction_groups.ts | 6 +- .../__snapshots__/queries.test.ts.snap | 239 ----------------- .../server/lib/transaction_groups/fetcher.ts | 241 ++++++++---------- .../get_transaction_group_stats.ts | 34 +-- .../server/lib/transaction_groups/index.ts | 9 +- .../lib/transaction_groups/queries.test.ts | 29 +-- .../server/projections/transaction_groups.ts | 61 ----- .../apm/server/projections/transactions.ts | 79 ------ x-pack/plugins/apm/server/routes/traces.ts | 6 +- .../plugins/apm/server/routes/transactions.ts | 45 ---- .../tests/feature_controls.ts | 7 - .../test/apm_api_integration/tests/index.ts | 4 - .../tests/traces/top_traces.ts | 1 - .../transactions/top_transaction_groups.ts | 72 ------ 19 files changed, 179 insertions(+), 985 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/app/transaction_overview/transaction_list/index.tsx delete mode 100644 x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts delete mode 100644 x-pack/plugins/apm/server/projections/transaction_groups.ts delete mode 100644 x-pack/plugins/apm/server/projections/transactions.ts delete mode 100644 x-pack/test/apm_api_integration/tests/transactions/top_transaction_groups.ts 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(); - }); - } - ); -} From 3a06f5bf88179e30007e74a7b5a1aeeac3c06750 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 2 Aug 2021 11:34:35 -0400 Subject: [PATCH 5/9] adding missing filter --- .../server/lib/transaction_groups/fetcher.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) 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 28c1290204f5c..fc1e800bb0543 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -12,8 +12,10 @@ import { Unionize } from 'utility-types'; import { AggregationOptionsByType } from '../../../../../../src/core/types/elasticsearch'; import { kqlQuery, rangeQuery } from '../../../../observability/server'; import { + PARENT_ID, SERVICE_NAME, TRANSACTION_NAME, + TRANSACTION_ROOT, } from '../../../common/elasticsearch_fieldnames'; import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { environmentQuery } from '../../../common/utils/environment_query'; @@ -92,7 +94,27 @@ function getRequest( ...rangeQuery(start, end), ...environmentQuery(environment), ...kqlQuery(kuery), + ...(searchAggregatedTransactions + ? [ + { + term: { + [TRANSACTION_ROOT]: true, + }, + }, + ] + : []), ] as QueryDslQueryContainer[], + must_not: [ + ...(!searchAggregatedTransactions + ? [ + { + exists: { + field: PARENT_ID, + }, + }, + ] + : []), + ], }, }, aggs: { From a69a346496493bc49184146e5ceec52b84e012c9 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 2 Aug 2021 12:14:24 -0400 Subject: [PATCH 6/9] fixing i18n --- x-pack/plugins/translations/translations/ja-JP.json | 8 -------- x-pack/plugins/translations/translations/zh-CN.json | 8 -------- 2 files changed, 16 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 6c33c5f8e3ba6..6e7c11f63bfec 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5763,7 +5763,6 @@ "xpack.apm.metrics.transactionChart.machineLearningLabel": "機械学習:", "xpack.apm.metrics.transactionChart.machineLearningTooltip": "ストリームには、平均レイテンシの想定境界が表示されます。赤色の垂直の注釈は、異常スコアが75以上の異常値を示します。", "xpack.apm.metrics.transactionChart.machineLearningTooltip.withKuery": "フィルタリングで検索バーを使用しているときには、機械学習結果が表示されません", - "xpack.apm.metrics.transactionChart.throughputLabel": "スループット", "xpack.apm.metrics.transactionChart.viewJob": "ジョブを表示", "xpack.apm.navigation.serviceMapTitle": "サービスマップ", "xpack.apm.navigation.servicesTitle": "サービス", @@ -6195,13 +6194,6 @@ "xpack.apm.transactions.latency.chart.95thPercentileLabel": "95 パーセンタイル", "xpack.apm.transactions.latency.chart.99thPercentileLabel": "99 パーセンタイル", "xpack.apm.transactions.latency.chart.averageLabel": "平均", - "xpack.apm.transactionsTable.95thPercentileColumnLabel": "95 パーセンタイル", - "xpack.apm.transactionsTable.avgDurationColumnLabel": "平均期間", - "xpack.apm.transactionsTable.impactColumnDescription": "ご利用のサービスで最も頻繁に使用されていて、最も遅いエンドポイントです。レイテンシとスループットを乗算した結果です", - "xpack.apm.transactionsTable.impactColumnLabel": "インパクト", - "xpack.apm.transactionsTable.nameColumnLabel": "名前", - "xpack.apm.transactionsTable.notFoundLabel": "トランザクションが見つかりませんでした。", - "xpack.apm.transactionsTable.throughputColumnLabel": "スループット", "xpack.apm.tutorial.agent_config.choosePolicy.helper": "選択したポリシー構成を下のスニペットに追加します。", "xpack.apm.tutorial.agent_config.choosePolicyLabel": "ポリシーを選択", "xpack.apm.tutorial.agent_config.defaultStandaloneConfig": "デフォルトのダッシュボード構成", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index af269112574f9..610e70b6d210b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5797,7 +5797,6 @@ "xpack.apm.metrics.transactionChart.machineLearningLabel": "Machine Learning", "xpack.apm.metrics.transactionChart.machineLearningTooltip": "流显示平均延迟的预期边界。红色垂直标注表示异常分数等于或大于 75 的异常。", "xpack.apm.metrics.transactionChart.machineLearningTooltip.withKuery": "使用搜索栏筛选时,Machine Learning 结果处于隐藏状态", - "xpack.apm.metrics.transactionChart.throughputLabel": "吞吐量", "xpack.apm.metrics.transactionChart.viewJob": "查看作业", "xpack.apm.navigation.serviceMapTitle": "服务地图", "xpack.apm.navigation.servicesTitle": "服务", @@ -6235,13 +6234,6 @@ "xpack.apm.transactions.latency.chart.95thPercentileLabel": "第 95 个百分位", "xpack.apm.transactions.latency.chart.99thPercentileLabel": "第 99 个百分位", "xpack.apm.transactions.latency.chart.averageLabel": "平均值", - "xpack.apm.transactionsTable.95thPercentileColumnLabel": "第 95 个百分位", - "xpack.apm.transactionsTable.avgDurationColumnLabel": "平均持续时间", - "xpack.apm.transactionsTable.impactColumnDescription": "服务中最常用的和最慢的终端节点。这是延迟和吞吐量相乘的结果", - "xpack.apm.transactionsTable.impactColumnLabel": "影响", - "xpack.apm.transactionsTable.nameColumnLabel": "名称", - "xpack.apm.transactionsTable.notFoundLabel": "未找到任何事务。", - "xpack.apm.transactionsTable.throughputColumnLabel": "吞吐量", "xpack.apm.tutorial.agent_config.choosePolicy.helper": "将选定的策略配置添加到下面的代码片段。", "xpack.apm.tutorial.agent_config.choosePolicyLabel": "选择策略", "xpack.apm.tutorial.agent_config.defaultStandaloneConfig": "默认单机版配置", From 797d3af8cd4c466a4d1325ed88eded4392767aff Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 2 Aug 2021 14:11:38 -0400 Subject: [PATCH 7/9] fixing tests --- .../__snapshots__/queries.test.ts.snap | 231 ++++++++++++++++++ .../lib/transaction_groups/queries.test.ts | 14 ++ 2 files changed, 245 insertions(+) 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 1d9a2aed42bca..5e3bbdf6ee06e 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 @@ -1,5 +1,215 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`transaction group queries fetches metrics top traces 1`] = ` +Array [ + Object { + "apm": Object { + "events": Array [ + "metric", + ], + }, + "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", + }, + }, + }, + }, + "composite": Object { + "size": 10000, + "sources": Array [ + Object { + "service.name": Object { + "terms": Object { + "field": "service.name", + }, + }, + }, + Object { + "transaction.name": Object { + "terms": Object { + "field": "transaction.name", + }, + }, + }, + ], + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "exists": Object { + "field": "transaction.duration.histogram", + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, + Object { + "term": Object { + "transaction.root": true, + }, + }, + ], + "must_not": Array [], + }, + }, + "size": 0, + }, + }, + Object { + "apm": Object { + "events": Array [ + "metric", + ], + }, + "body": Object { + "aggs": Object { + "transaction_groups": Object { + "aggs": Object { + "avg": Object { + "avg": Object { + "field": "transaction.duration.histogram", + }, + }, + }, + "composite": Object { + "size": 10000, + "sources": Array [ + Object { + "service.name": Object { + "terms": Object { + "field": "service.name", + }, + }, + }, + Object { + "transaction.name": Object { + "terms": Object { + "field": "transaction.name", + }, + }, + }, + ], + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "exists": Object { + "field": "transaction.duration.histogram", + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, + Object { + "term": Object { + "transaction.root": true, + }, + }, + ], + "must_not": Array [], + }, + }, + "size": 0, + }, + }, + Object { + "apm": Object { + "events": Array [ + "metric", + ], + }, + "body": Object { + "aggs": Object { + "transaction_groups": Object { + "aggs": Object { + "sum": Object { + "sum": Object { + "field": "transaction.duration.histogram", + }, + }, + }, + "composite": Object { + "size": 10000, + "sources": Array [ + Object { + "service.name": Object { + "terms": Object { + "field": "service.name", + }, + }, + }, + Object { + "transaction.name": Object { + "terms": Object { + "field": "transaction.name", + }, + }, + }, + ], + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "exists": Object { + "field": "transaction.duration.histogram", + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, + Object { + "term": Object { + "transaction.root": true, + }, + }, + ], + "must_not": Array [], + }, + }, + "size": 0, + }, + }, +] +`; + exports[`transaction group queries fetches top traces 1`] = ` Array [ Object { @@ -59,6 +269,13 @@ Array [ }, }, ], + "must_not": Array [ + Object { + "exists": Object { + "field": "parent.id", + }, + }, + ], }, }, "size": 0, @@ -114,6 +331,13 @@ Array [ }, }, ], + "must_not": Array [ + Object { + "exists": Object { + "field": "parent.id", + }, + }, + ], }, }, "size": 0, @@ -169,6 +393,13 @@ Array [ }, }, ], + "must_not": Array [ + Object { + "exists": Object { + "field": "parent.id", + }, + }, + ], }, }, "size": 0, 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 b0309886e8596..18f1c1bee50dc 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 @@ -30,6 +30,20 @@ describe('transaction group queries', () => { const allParams = mock.spy.mock.calls.map((call) => call[1]); + expect(allParams).toMatchSnapshot(); + }); + it('fetches metrics top traces', async () => { + mock = await inspectSearchParams((setup) => + topTransactionGroupsFetcher( + { + searchAggregatedTransactions: true, + }, + setup + ) + ); + + const allParams = mock.spy.mock.calls.map((call) => call[1]); + expect(allParams).toMatchSnapshot(); }); }); From 1a74d0b330e4b209ec6f1a8dac362d062779c3ea Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 3 Aug 2021 09:41:32 -0400 Subject: [PATCH 8/9] addressing PR comments --- .../index.tsx | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) 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 6330867d6c23d..0a01a9a0c541e 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 @@ -31,17 +31,21 @@ import { ElasticDocsLink } from '../../../shared/Links/ElasticDocsLink'; type ApiResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/main_statistics'>; -type InitialState = ApiResponse & { +interface InitialState { requestId: string; - transactionGroupsTotalItems: number; -}; + mainStatisticsData: ApiResponse & { + transactionGroupsTotalItems: number; + }; +} const INITIAL_STATE: InitialState = { - transactionGroups: [], - isAggregationAccurate: true, requestId: '', - transactionGroupsTotalItems: 0, - bucketSize: 0, + mainStatisticsData: { + transactionGroups: [], + isAggregationAccurate: true, + bucketSize: 0, + transactionGroupsTotalItems: 0, + }, }; type SortField = 'name' | 'latency' | 'throughput' | 'errorRate' | 'impact'; @@ -126,11 +130,13 @@ export function ServiceOverviewTransactionsTable({ ); return { - ...response, // Everytime the main statistics is refetched, updates the requestId making the detailed API to be refetched. requestId: uuid(), - transactionGroupsTotalItems: response.transactionGroups.length, - transactionGroups: currentPageTransactionGroups, + mainStatisticsData: { + ...response, + transactionGroups: currentPageTransactionGroups, + transactionGroupsTotalItems: response.transactionGroups.length, + }, }; }); }, @@ -154,11 +160,13 @@ export function ServiceOverviewTransactionsTable({ ); const { - transactionGroups, requestId, - transactionGroupsTotalItems, - isAggregationAccurate, - bucketSize, + mainStatisticsData: { + transactionGroups, + isAggregationAccurate, + bucketSize, + transactionGroupsTotalItems, + }, } = data; const { From b8209e6b5024c90f28e491f87bf26b9985ebb810 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 3 Aug 2021 12:06:27 -0400 Subject: [PATCH 9/9] moving table to shared folder --- .../components/app/service_overview/index.tsx | 4 ++-- .../get_columns.tsx | 2 +- .../app/transaction_overview/index.tsx | 4 ++-- .../transactions_table}/get_columns.tsx | 18 ++++++++--------- .../get_latency_column_label.ts | 0 .../transactions_table}/index.tsx | 20 +++++++++---------- 6 files changed, 24 insertions(+), 24 deletions(-) rename x-pack/plugins/apm/public/components/{app/service_overview/service_overview_transactions_table => shared/transactions_table}/get_columns.tsx (89%) rename x-pack/plugins/apm/public/components/{app/service_overview => shared/transactions_table}/get_latency_column_label.ts (100%) rename x-pack/plugins/apm/public/components/{app/service_overview/service_overview_transactions_table => shared/transactions_table}/index.tsx (92%) diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 11cc569af8609..620eefda05b27 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -19,7 +19,7 @@ import { ServiceOverviewDependenciesTable } from './service_overview_dependencie import { ServiceOverviewErrorsTable } from './service_overview_errors_table'; import { ServiceOverviewInstancesChartAndTable } from './service_overview_instances_chart_and_table'; import { ServiceOverviewThroughputChart } from './service_overview_throughput_chart'; -import { ServiceOverviewTransactionsTable } from './service_overview_transactions_table'; +import { TransactionsTable } from '../../shared/transactions_table'; /** * The height a chart should be if it's next to a table with 5 rows and a title. @@ -57,7 +57,7 @@ export function ServiceOverview() {
- + diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx index f9600b9d7f418..d956612a9ac49 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx @@ -30,7 +30,7 @@ import { SparkPlot } from '../../../shared/charts/spark_plot'; import { MetricOverviewLink } from '../../../shared/Links/apm/MetricOverviewLink'; import { ServiceNodeMetricOverviewLink } from '../../../shared/Links/apm/ServiceNodeMetricOverviewLink'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; -import { getLatencyColumnLabel } from '../get_latency_column_label'; +import { getLatencyColumnLabel } from '../../../shared/transactions_table/get_latency_column_label'; import { MainStatsServiceInstanceItem } from '../service_overview_instances_chart_and_table'; import { InstanceActionsMenu } from './instance_actions_menu'; 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 6f857c3e76128..a0c2108948f23 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 @@ -14,7 +14,7 @@ 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 { fromQuery, toQuery } from '../../shared/Links/url_helpers'; -import { ServiceOverviewTransactionsTable } from '../service_overview/service_overview_transactions_table'; +import { TransactionsTable } from '../../shared/transactions_table'; import { useRedirect } from './useRedirect'; function getRedirectLocation({ @@ -58,7 +58,7 @@ export function TransactionOverview() { - ; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/get_latency_column_label.ts b/x-pack/plugins/apm/public/components/shared/transactions_table/get_latency_column_label.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/service_overview/get_latency_column_label.ts rename to x-pack/plugins/apm/public/components/shared/transactions_table/get_latency_column_label.ts 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/shared/transactions_table/index.tsx similarity index 92% rename from x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx rename to x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx index 0a01a9a0c541e..7f1dc2cc150d7 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx @@ -18,16 +18,16 @@ 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'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { TransactionOverviewLink } from '../../../shared/Links/apm/transaction_overview_link'; -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 { 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'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { TransactionOverviewLink } from '../Links/apm/transaction_overview_link'; +import { TableFetchWrapper } from '../table_fetch_wrapper'; +import { getTimeRangeComparison } from '../time_comparison/get_time_range_comparison'; +import { OverviewTableContainer } from '../overview_table_container'; import { getColumns } from './get_columns'; -import { ElasticDocsLink } from '../../../shared/Links/ElasticDocsLink'; +import { ElasticDocsLink } from '../Links/ElasticDocsLink'; type ApiResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/main_statistics'>; @@ -61,7 +61,7 @@ interface Props { showAggregationAccurateCallout?: boolean; } -export function ServiceOverviewTransactionsTable({ +export function TransactionsTable({ hideViewTransactionsLink = false, numberOfTransactionsPerPage = 5, showAggregationAccurateCallout = false,