From f2a32af01694a81d88c35bd582314f989a4b393d Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 21 Jan 2021 11:29:28 +0100 Subject: [PATCH 01/31] removing pagination from server --- .../index.tsx | 181 ++++++++++-------- ..._timeseries_data_for_transaction_groups.ts | 4 +- .../get_transaction_groups_for_page.ts | 6 +- .../get_service_transaction_groups/index.ts | 7 - .../plugins/apm/server/routes/transactions.ts | 6 - 5 files changed, 98 insertions(+), 106 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 c77e80d0176de..43c813d201ec7 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 @@ -81,90 +81,14 @@ function getLatencyAggregationTypeLabel(latencyAggregationType?: string) { } } -export function ServiceOverviewTransactionsTable(props: Props) { - const { serviceName } = props; - const { transactionType } = useApmServiceContext(); - const { - uiFilters, - urlParams: { start, end, latencyAggregationType }, - } = useUrlParams(); - - const [tableOptions, setTableOptions] = useState<{ - pageIndex: number; - sort: { - direction: SortDirection; - field: SortField; - }; - }>({ - pageIndex: 0, - sort: DEFAULT_SORT, - }); - - const { - data = { - totalItemCount: 0, - items: [], - tableOptions: { - pageIndex: 0, - sort: DEFAULT_SORT, - }, - }, - status, - } = useFetcher(() => { - if (!start || !end || !latencyAggregationType || !transactionType) { - return; - } - - return callApmApi({ - endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/overview', - params: { - path: { serviceName }, - query: { - start, - end, - uiFilters: JSON.stringify(uiFilters), - size: PAGE_SIZE, - numBuckets: 20, - pageIndex: tableOptions.pageIndex, - sortField: tableOptions.sort.field, - sortDirection: tableOptions.sort.direction, - transactionType, - latencyAggregationType: latencyAggregationType as LatencyAggregationType, - }, - }, - }).then((response) => { - return { - items: response.transactionGroups, - totalItemCount: response.totalTransactionGroups, - tableOptions: { - pageIndex: tableOptions.pageIndex, - sort: { - field: tableOptions.sort.field, - direction: tableOptions.sort.direction, - }, - }, - }; - }); - }, [ - serviceName, - start, - end, - uiFilters, - tableOptions.pageIndex, - tableOptions.sort.field, - tableOptions.sort.direction, - transactionType, - latencyAggregationType, - ]); - - const { - items, - totalItemCount, - tableOptions: { pageIndex, sort }, - } = data; - - const columns: Array> = [ +function getColumns({ + serviceName, + latencyAggregationType, +}: { + serviceName: string; + latencyAggregationType?: string; +}): Array> { + return [ { field: 'name', name: i18n.translate( @@ -258,6 +182,90 @@ export function ServiceOverviewTransactionsTable(props: Props) { }, }, ]; +} + +export function ServiceOverviewTransactionsTable(props: Props) { + const { serviceName } = props; + const { transactionType } = useApmServiceContext(); + const { + uiFilters, + urlParams: { start, end, latencyAggregationType }, + } = useUrlParams(); + + const [tableOptions, setTableOptions] = useState<{ + pageIndex: number; + sort: { + direction: SortDirection; + field: SortField; + }; + }>({ + pageIndex: 0, + sort: DEFAULT_SORT, + }); + + const { + data = { + totalItemCount: 0, + items: [], + tableOptions: { + pageIndex: 0, + sort: DEFAULT_SORT, + }, + }, + status, + } = useFetcher(() => { + if (!start || !end || !latencyAggregationType || !transactionType) { + return; + } + + return callApmApi({ + endpoint: + 'GET /api/apm/services/{serviceName}/transactions/groups/overview', + params: { + path: { serviceName }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + numBuckets: 20, + sortField: tableOptions.sort.field, + sortDirection: tableOptions.sort.direction, + transactionType, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, + }, + }, + }).then((response) => { + return { + items: response.transactionGroups, + totalItemCount: response.totalTransactionGroups, + tableOptions: { + pageIndex: tableOptions.pageIndex, + sort: { + field: tableOptions.sort.field, + direction: tableOptions.sort.direction, + }, + }, + }; + }); + }, [ + serviceName, + start, + end, + uiFilters, + tableOptions.pageIndex, + tableOptions.sort.field, + tableOptions.sort.direction, + transactionType, + latencyAggregationType, + ]); + + const { + items, + totalItemCount, + tableOptions: { pageIndex, sort }, + } = data; + + const columns = getColumns({ serviceName, latencyAggregationType }); return ( @@ -300,7 +308,10 @@ export function ServiceOverviewTransactionsTable(props: Props) { > Date: Thu, 21 Jan 2021 17:44:06 +0100 Subject: [PATCH 02/31] new metrics api --- .../index.tsx | 74 ++++++++++-- .../shared/charts/spark_plot/index.tsx | 17 +-- ..._timeseries_data_for_transaction_groups.ts | 2 +- .../get_service_transaction_groups/index.ts | 33 ++---- .../merge_transaction_group_data.ts | 90 -------------- .../index.ts | 110 ++++++++++++++++++ .../apm/server/routes/create_apm_api.ts | 2 + .../plugins/apm/server/routes/transactions.ts | 48 ++++++++ 8 files changed, 244 insertions(+), 132 deletions(-) delete mode 100644 x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/merge_transaction_group_data.ts create mode 100644 x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.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 43c813d201ec7..8f82e0a2f16aa 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 @@ -12,7 +12,7 @@ import { EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { ValuesType } from 'utility-types'; import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; import { @@ -40,6 +40,8 @@ type ServiceTransactionGroupItem = ValuesType< APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/overview'>['transactionGroups'] >; +type TransactionGroupMetrics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/metrics'>; + interface Props { serviceName: string; } @@ -84,9 +86,11 @@ function getLatencyAggregationTypeLabel(latencyAggregationType?: string) { function getColumns({ serviceName, latencyAggregationType, + transactionsMetricsData, }: { serviceName: string; latencyAggregationType?: string; + transactionsMetricsData: TransactionGroupMetrics; }): Array> { return [ { @@ -119,12 +123,15 @@ function getColumns({ field: 'latency', name: getLatencyAggregationTypeLabel(latencyAggregationType), width: px(unit * 10), - render: (_, { latency }) => { + render: (_, { latency, name }) => { + const timeseries = transactionsMetricsData + ? transactionsMetricsData[name]?.latency.timeseries + : undefined; return ( ); @@ -137,12 +144,15 @@ function getColumns({ { defaultMessage: 'Throughput' } ), width: px(unit * 10), - render: (_, { throughput }) => { + render: (_, { throughput, name }) => { + const timeseries = transactionsMetricsData + ? transactionsMetricsData[name]?.throughput.timeseries + : undefined; return ( ); @@ -157,12 +167,15 @@ function getColumns({ } ), width: px(unit * 8), - render: (_, { errorRate }) => { + render: (_, { errorRate, name }) => { + const timeseries = transactionsMetricsData + ? transactionsMetricsData[name]?.errorRate.timeseries + : undefined; return ( ); @@ -265,7 +278,47 @@ export function ServiceOverviewTransactionsTable(props: Props) { tableOptions: { pageIndex, sort }, } = data; - const columns = getColumns({ serviceName, latencyAggregationType }); + const transactionNames = useMemo(() => items.map(({ name }) => name), [ + items, + ]); + + const { + data: transactionsMetricsData, + status: transactionsMetricsStatus, + } = useFetcher(() => { + if (transactionNames.length && start && end && transactionType) { + return callApmApi({ + endpoint: + 'GET /api/apm/services/{serviceName}/transactions/groups/metrics', + params: { + path: { serviceName }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + numBuckets: 20, + transactionType, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, + transactionNames: transactionNames.join(), + }, + }, + }); + } + }, [ + serviceName, + start, + end, + uiFilters, + transactionType, + latencyAggregationType, + transactionNames, + ]); + + const columns = getColumns({ + serviceName, + latencyAggregationType, + transactionsMetricsData, + }); return ( @@ -319,7 +372,10 @@ export function ServiceOverviewTransactionsTable(props: Props) { pageSizeOptions: [PAGE_SIZE], hidePerPageOptions: true, }} - loading={status === FETCH_STATUS.LOADING} + loading={ + status === FETCH_STATUS.LOADING || + transactionsMetricsStatus === FETCH_STATUS.LOADING + } onChange={(newTableOptions: { page?: { index: number; diff --git a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx index 7b5ff4fd56e91..59e48f13ad1c8 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx @@ -15,6 +15,7 @@ import { Settings, } from '@elastic/charts'; import { merge } from 'lodash'; +import { Coordinate } from '../../../../../typings/timeseries'; import { useChartTheme } from '../../../../../../observability/public'; import { px, unit } from '../../../../style/variables'; import { useTheme } from '../../../../hooks/use_theme'; @@ -38,7 +39,7 @@ export function SparkPlot({ compact, }: { color: Color; - series?: Array<{ x: number; y: number | null }> | null; + series?: Coordinate[] | null; valueLabel: React.ReactNode; compact?: boolean; }) { @@ -57,18 +58,18 @@ export function SparkPlot({ const colorValue = theme.eui[color]; + const chartSize = { + height: px(24), + width: compact ? px(unit * 3) : px(unit * 4), + }; + return ( {!series || series.every((point) => point.y === null) ? ( - + ) : ( - + group.name); - - const timeseriesData = await getTimeseriesDataForTransactionGroups({ - apmEventClient, - start, - end, - esFilter, - numBuckets, - searchAggregatedTransactions, - serviceName, - transactionNames, - transactionType, - latencyAggregationType, - }); - return { - transactionGroups: mergeTransactionGroupData({ - transactionGroups, - timeseriesData, - start, - end, - latencyAggregationType, - transactionType, + transactionGroups: transactionGroups.map((transactionGroup) => { + return { + name: transactionGroup.name, + transactionType, + latency: { value: transactionGroup.latency }, + throughput: { value: transactionGroup.throughput }, + errorRate: { value: transactionGroup.errorRate }, + impact: transactionGroup.impact, + }; }), totalTransactionGroups, isAggregationAccurate, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/merge_transaction_group_data.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/merge_transaction_group_data.ts deleted file mode 100644 index a8794e3c09a40..0000000000000 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/merge_transaction_group_data.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames'; -import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; -import { getLatencyValue } from '../../helpers/latency_aggregation_type'; -import { TransactionGroupTimeseriesData } from './get_timeseries_data_for_transaction_groups'; -import { TransactionGroupWithoutTimeseriesData } from './get_transaction_groups_for_page'; - -export function mergeTransactionGroupData({ - start, - end, - transactionGroups, - timeseriesData, - latencyAggregationType, - transactionType, -}: { - start: number; - end: number; - transactionGroups: TransactionGroupWithoutTimeseriesData[]; - timeseriesData: TransactionGroupTimeseriesData; - latencyAggregationType: LatencyAggregationType; - transactionType: string; -}) { - const deltaAsMinutes = (end - start) / 1000 / 60; - - return transactionGroups.map((transactionGroup) => { - const groupBucket = timeseriesData.find( - ({ key }) => key === transactionGroup.name - ); - - const timeseriesBuckets = groupBucket?.timeseries.buckets ?? []; - - return timeseriesBuckets.reduce( - (acc, point) => { - return { - ...acc, - latency: { - ...acc.latency, - timeseries: acc.latency.timeseries.concat({ - x: point.key, - y: getLatencyValue({ - latencyAggregationType, - aggregation: point.latency, - }), - }), - }, - throughput: { - ...acc.throughput, - timeseries: acc.throughput.timeseries.concat({ - x: point.key, - y: point.transaction_count.value / deltaAsMinutes, - }), - }, - errorRate: { - ...acc.errorRate, - timeseries: acc.errorRate.timeseries.concat({ - x: point.key, - y: - point.transaction_count.value > 0 - ? (point[EVENT_OUTCOME].transaction_count.value ?? 0) / - point.transaction_count.value - : null, - }), - }, - }; - }, - { - name: transactionGroup.name, - transactionType, - latency: { - value: transactionGroup.latency, - timeseries: [] as Array<{ x: number; y: number | null }>, - }, - throughput: { - value: transactionGroup.throughput, - timeseries: [] as Array<{ x: number; y: number }>, - }, - errorRate: { - value: transactionGroup.errorRate, - timeseries: [] as Array<{ x: number; y: number | null }>, - }, - impact: transactionGroup.impact, - } - ); - }); -} diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts new file mode 100644 index 0000000000000..e508ccbc851b4 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Coordinate } from '../../../../typings/timeseries'; +import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames'; +import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; +import { getLatencyValue } from '../../helpers/latency_aggregation_type'; +import { Setup, SetupTimeRange } from '../../helpers/setup_request'; +import { getTimeseriesDataForTransactionGroups } from '../get_service_transaction_groups/get_timeseries_data_for_transaction_groups'; + +export async function getServiceTransactionGroupsMetrics({ + serviceName, + transactionNames, + setup, + numBuckets, + searchAggregatedTransactions, + transactionType, + latencyAggregationType, +}: { + serviceName: string; + transactionNames: string[]; + setup: Setup & SetupTimeRange; + numBuckets: number; + searchAggregatedTransactions: boolean; + transactionType: string; + latencyAggregationType: LatencyAggregationType; +}): Promise< + Record< + string, + { + latency: { timeseries: Coordinate[] }; + throughput: { timeseries: Coordinate[] }; + errorRate: { timeseries: Coordinate[] }; + } + > +> { + const { apmEventClient, start, end, esFilter } = setup; + + const buckets = await getTimeseriesDataForTransactionGroups({ + apmEventClient, + start, + end, + esFilter, + numBuckets, + searchAggregatedTransactions, + serviceName, + transactionNames, + transactionType, + latencyAggregationType, + }); + const deltaAsMinutes = (end - start) / 1000 / 60; + + return buckets.reduce((bucketAcc, bucket) => { + const transactionName = bucket.key; + return { + ...bucketAcc, + [transactionName]: bucket.timeseries.buckets.reduce( + (acc, timeseriesBucket) => { + const x = timeseriesBucket.key; + return { + ...acc, + latency: { + timeseries: [ + ...acc.latency.timeseries, + { + x, + y: getLatencyValue({ + latencyAggregationType, + aggregation: timeseriesBucket.latency, + }), + }, + ], + }, + throughput: { + timeseries: [ + ...acc.throughput.timeseries, + { + x, + y: timeseriesBucket.transaction_count.value / deltaAsMinutes, + }, + ], + }, + errorRate: { + timeseries: [ + ...acc.errorRate.timeseries, + { + x, + y: + timeseriesBucket.transaction_count.value > 0 + ? (timeseriesBucket[EVENT_OUTCOME].transaction_count + .value ?? 0) / + timeseriesBucket.transaction_count.value + : null, + }, + ], + }, + }; + }, + { + latency: { timeseries: [] as Coordinate[] }, + throughput: { timeseries: [] as Coordinate[] }, + errorRate: { timeseries: [] as Coordinate[] }, + } + ), + }; + }, {}); +} diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index aeaa6f90a7426..d4de99865ed33 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -62,6 +62,7 @@ import { transactionGroupsOverviewRoute, transactionLatencyChatsRoute, transactionThroughputChatsRoute, + transactionGroupsMetricsRoute, } from './transactions'; import { errorGroupsLocalFiltersRoute, @@ -172,6 +173,7 @@ const createApmApi = () => { .add(transactionGroupsOverviewRoute) .add(transactionLatencyChatsRoute) .add(transactionThroughputChatsRoute) + .add(transactionGroupsMetricsRoute) // UI filters .add(errorGroupsLocalFiltersRoute) diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index 4a8d722547fb0..201493dea24bd 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -23,6 +23,7 @@ import { LatencyAggregationType, latencyAggregationTypeRt, } from '../../common/latency_aggregation_types'; +import { getServiceTransactionGroupsMetrics } from '../lib/services/get_service_transaction_groups_metrics'; /** * Returns a list of transactions grouped by name @@ -118,6 +119,53 @@ export const transactionGroupsOverviewRoute = createRoute({ }, }); +export const transactionGroupsMetricsRoute = createRoute({ + endpoint: 'GET /api/apm/services/{serviceName}/transactions/groups/metrics', + params: t.type({ + path: t.type({ serviceName: t.string }), + query: t.intersection([ + rangeRt, + uiFiltersRt, + t.type({ + transactionNames: t.string, + numBuckets: toNumberRt, + transactionType: t.string, + latencyAggregationType: latencyAggregationTypeRt, + }), + ]), + }), + options: { + tags: ['access:apm'], + }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + + const searchAggregatedTransactions = await getSearchAggregatedTransactions( + setup + ); + + const { + path: { serviceName }, + query: { + transactionNames, + latencyAggregationType, + numBuckets, + transactionType, + }, + } = context.params; + + return getServiceTransactionGroupsMetrics({ + setup, + serviceName, + transactionNames: transactionNames.split(','), + searchAggregatedTransactions, + transactionType, + numBuckets, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, + }); + }, +}); + export const transactionLatencyChatsRoute = createRoute({ endpoint: 'GET /api/apm/services/{serviceName}/transactions/charts/latency', params: t.type({ From 654928df7afaf2e83ddc4b14a4d7ad72efb3d4e9 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 25 Jan 2021 10:14:34 +0100 Subject: [PATCH 03/31] adding api tests --- .../apm_api_integration/basic/tests/index.ts | 1 + .../transactions_groups_metrics.ts | 112 ++++++++++++++++++ .../transactions_groups_overview.ts | 94 +++------------ 3 files changed, 127 insertions(+), 80 deletions(-) create mode 100644 x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_metrics.ts diff --git a/x-pack/test/apm_api_integration/basic/tests/index.ts b/x-pack/test/apm_api_integration/basic/tests/index.ts index 4e66c6e6f76c3..b2e06ef2991b1 100644 --- a/x-pack/test/apm_api_integration/basic/tests/index.ts +++ b/x-pack/test/apm_api_integration/basic/tests/index.ts @@ -58,6 +58,7 @@ export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderCont loadTestFile(require.resolve('./transactions/breakdown')); loadTestFile(require.resolve('./transactions/distribution')); loadTestFile(require.resolve('./transactions/transactions_groups_overview')); + loadTestFile(require.resolve('./transactions/transactions_groups_metrics')); }); describe('Observability overview', function () { diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_metrics.ts b/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_metrics.ts new file mode 100644 index 0000000000000..13bb4edd54c0a --- /dev/null +++ b/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_metrics.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import url from 'url'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import archives from '../../../common/fixtures/es_archiver/archives_metadata'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + const archiveName = 'apm_8.0.0'; + const { start, end } = archives[archiveName]; + + const transactionNames = [ + 'DispatcherServlet#doGet', + 'APIRestController#customers', + 'APIRestController#order', + 'APIRestController#stats', + 'APIRestController#customerWhoBought', + 'APIRestController#customer', + 'APIRestController#topProducts', + 'APIRestController#orders', + 'APIRestController#product', + 'ResourceHttpRequestHandler', + 'APIRestController#products', + 'DispatcherServlet#doPost', + ].join(); + + describe('Transactions groups Metrics', () => { + describe('when data is not loaded', () => { + it('handles the empty state', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/transactions/groups/metrics`, + query: { + start, + end, + uiFilters: '{}', + numBuckets: 20, + latencyAggregationType: 'avg', + transactionType: 'request', + transactionNames, + }, + }) + ); + + expect(response.status).to.be(200); + expect(response.body).to.eql({}); + }); + }); + + describe('when data is loaded', () => { + before(() => esArchiver.load(archiveName)); + after(() => esArchiver.unload(archiveName)); + + it('returns the correct data', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/transactions/groups/metrics`, + query: { + start, + end, + uiFilters: '{}', + numBuckets: 20, + latencyAggregationType: 'avg', + transactionType: 'request', + transactionNames, + }, + }) + ); + + expect(response.status).to.be(200); + + expectSnapshot(Object.keys(response.body)).toMatchInline(` + Array [ + "DispatcherServlet#doGet", + "APIRestController#customerWhoBought", + "APIRestController#order", + "APIRestController#customer", + "ResourceHttpRequestHandler", + "APIRestController#customers", + "APIRestController#stats", + "APIRestController#topProducts", + "APIRestController#orders", + "APIRestController#product", + "APIRestController#products", + "DispatcherServlet#doPost", + ] + `); + + const firstItem = response.body['DispatcherServlet#doGet']; + + expectSnapshot( + firstItem.latency.timeseries.filter(({ y }: any) => y > 0).length + ).toMatchInline(`9`); + + expectSnapshot( + firstItem.throughput.timeseries.filter(({ y }: any) => y > 0).length + ).toMatchInline(`9`); + + expectSnapshot( + firstItem.errorRate.timeseries.filter(({ y }: any) => y > 0).length + ).toMatchInline(`1`); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts b/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts index be978b2a82618..c5ee5c662c9f7 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts @@ -27,9 +27,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { start, end, uiFilters: '{}', - size: 5, numBuckets: 20, - pageIndex: 0, sortDirection: 'desc', sortField: 'impact', latencyAggregationType: 'avg', @@ -59,9 +57,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { start, end, uiFilters: '{}', - size: 5, numBuckets: 20, - pageIndex: 0, sortDirection: 'desc', sortField: 'impact', transactionType: 'request', @@ -82,6 +78,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { "APIRestController#order", "APIRestController#stats", "APIRestController#customerWhoBought", + "APIRestController#customer", + "APIRestController#topProducts", + "APIRestController#orders", + "APIRestController#product", + "ResourceHttpRequestHandler", + "APIRestController#products", + "DispatcherServlet#doPost", ] `); @@ -93,6 +96,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { 0.953769516915408, 0.905498741191481, 0.894989230293471, + 0.734894148230161, + 0.496596820588832, + 0.465199881087606, + 0.269203783423923, + 0.142856373806016, + 0.0557715877137418, + 0, ] `); @@ -115,18 +125,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, } `); - - expectSnapshot( - firstItem.latency.timeseries.filter(({ y }: any) => y > 0).length - ).toMatchInline(`9`); - - expectSnapshot( - firstItem.throughput.timeseries.filter(({ y }: any) => y > 0).length - ).toMatchInline(`9`); - - expectSnapshot( - firstItem.errorRate.timeseries.filter(({ y }: any) => y > 0).length - ).toMatchInline(`1`); }); it('sorts items in the correct order', async () => { @@ -137,9 +135,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { start, end, uiFilters: '{}', - size: 5, numBuckets: 20, - pageIndex: 0, sortDirection: 'desc', sortField: 'impact', transactionType: 'request', @@ -163,9 +159,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { start, end, uiFilters: '{}', - size: 5, numBuckets: 20, - pageIndex: 0, sortDirection: 'desc', sortField: 'impact', transactionType: 'request', @@ -189,9 +183,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { start, end, uiFilters: '{}', - size: 5, numBuckets: 20, - pageIndex: 0, sortDirection: 'desc', sortField: 'latency', transactionType: 'request', @@ -206,64 +198,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(latencies).to.eql(sortBy(latencies.concat()).reverse()); }); - - it('paginates through the items', async () => { - const size = 1; - - const firstPage = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/overview`, - query: { - start, - end, - uiFilters: '{}', - size, - numBuckets: 20, - pageIndex: 0, - sortDirection: 'desc', - sortField: 'impact', - transactionType: 'request', - latencyAggregationType: 'avg', - }, - }) - ); - - expect(firstPage.status).to.eql(200); - - const totalItems = firstPage.body.totalTransactionGroups; - - const pages = Math.floor(totalItems / size); - - const items = await new Array(pages) - .fill(undefined) - .reduce(async (prevItemsPromise, _, pageIndex) => { - const prevItems = await prevItemsPromise; - - const thisPage = await supertest.get( - url.format({ - pathname: '/api/apm/services/opbeans-java/transactions/groups/overview', - query: { - start, - end, - uiFilters: '{}', - size, - numBuckets: 20, - pageIndex, - sortDirection: 'desc', - sortField: 'impact', - transactionType: 'request', - latencyAggregationType: 'avg', - }, - }) - ); - - return prevItems.concat(thisPage.body.transactionGroups); - }, Promise.resolve([])); - - expect(items.length).to.eql(totalItems); - - expect(uniqBy(items, 'name').length).to.eql(totalItems); - }); }); }); } From 284b39d0a52a0b649de62680fd6ed26831ee0311 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 25 Jan 2021 11:03:42 +0100 Subject: [PATCH 04/31] fixing TS --- .../service_overview_transactions_table/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8f82e0a2f16aa..664140dbb9508 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 @@ -90,7 +90,7 @@ function getColumns({ }: { serviceName: string; latencyAggregationType?: string; - transactionsMetricsData: TransactionGroupMetrics; + transactionsMetricsData?: TransactionGroupMetrics; }): Array> { return [ { From d5b871d42ee6842aa8c0bda0d941ce5ef7f3be2a Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 25 Jan 2021 14:58:05 +0100 Subject: [PATCH 05/31] new metrics api --- .../index.tsx | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 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 664140dbb9508..5c64b6d6e07dd 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 @@ -285,34 +285,31 @@ export function ServiceOverviewTransactionsTable(props: Props) { const { data: transactionsMetricsData, status: transactionsMetricsStatus, - } = useFetcher(() => { - if (transactionNames.length && start && end && transactionType) { - return callApmApi({ - endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/metrics', - params: { - path: { serviceName }, - query: { - start, - end, - uiFilters: JSON.stringify(uiFilters), - numBuckets: 20, - transactionType, - latencyAggregationType: latencyAggregationType as LatencyAggregationType, - transactionNames: transactionNames.join(), + } = useFetcher( + () => { + if (transactionNames.length && start && end && transactionType) { + return callApmApi({ + endpoint: + 'GET /api/apm/services/{serviceName}/transactions/groups/metrics', + params: { + path: { serviceName }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + numBuckets: 20, + transactionType, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, + transactionNames: transactionNames.join(), + }, }, - }, - }); - } - }, [ - serviceName, - start, - end, - uiFilters, - transactionType, - latencyAggregationType, - transactionNames, - ]); + }); + } + }, + // only fetches metrics when transaction names change + // eslint-disable-next-line react-hooks/exhaustive-deps + [transactionNames] + ); const columns = getColumns({ serviceName, From 89b845c9c262c2704bde053ab7405efe95b4e2c5 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 25 Jan 2021 15:58:13 +0100 Subject: [PATCH 06/31] fixing TS --- .../basic/tests/transactions/transactions_groups_overview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts b/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts index c5ee5c662c9f7..4f235a3d275ee 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; -import { pick, uniqBy, sortBy } from 'lodash'; +import { pick, sortBy } from 'lodash'; import url from 'url'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import archives from '../../../common/fixtures/es_archiver/archives_metadata'; From 0a3b80010db848ff470969ad19ea6b190049fa27 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 26 Jan 2021 11:10:51 +0100 Subject: [PATCH 07/31] addressing PR comments --- .../get_columns.tsx | 161 ++++++++++ .../index.tsx | 294 ++---------------- .../__snapshots__/ManagedTable.test.js.snap | 2 + .../components/shared/ManagedTable/index.tsx | 3 + ..._for_page.ts => get_transaction_groups.ts} | 41 +-- .../get_service_transaction_groups/index.ts | 31 +- ..._timeseries_data_for_transaction_groups.ts | 0 .../index.ts | 77 +++-- .../plugins/apm/server/routes/transactions.ts | 20 +- 9 files changed, 246 insertions(+), 383 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx rename x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/{get_transaction_groups_for_page.ts => get_transaction_groups.ts} (80%) rename x-pack/plugins/apm/server/lib/services/{get_service_transaction_groups => get_service_transaction_groups_metrics}/get_timeseries_data_for_transaction_groups.ts (100%) diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx new file mode 100644 index 0000000000000..71482eba345ad --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiBasicTableColumn } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { ValuesType } from 'utility-types'; +import { + asMillisecondDuration, + asPercent, + asTransactionRate, +} from '../../../../../common/utils/formatters'; +import { APIReturnType } from '../../../../services/rest/createCallApmApi'; +import { px, unit } from '../../../../style/variables'; +import { SparkPlot } from '../../../shared/charts/spark_plot'; +import { ImpactBar } from '../../../shared/ImpactBar'; +import { TransactionDetailLink } from '../../../shared/Links/apm/transaction_detail_link'; +import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; +import { TransactionGroupsOverview } from './'; + +export type ServiceTransactionGroupItem = ValuesType< + TransactionGroupsOverview['transactionGroups'] +>; +type TransactionGroupMetrics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/metrics'>; + +function getLatencyAggregationTypeLabel(latencyAggregationType?: string) { + switch (latencyAggregationType) { + case 'avg': + return i18n.translate( + 'xpack.apm.serviceOverview.transactionsTableColumnLatency.avg', + { defaultMessage: 'Latency (avg.)' } + ); + + case 'p95': + return i18n.translate( + 'xpack.apm.serviceOverview.transactionsTableColumnLatency.p95', + { defaultMessage: 'Latency (95th)' } + ); + + case 'p99': + return i18n.translate( + 'xpack.apm.serviceOverview.transactionsTableColumnLatency.p99', + { defaultMessage: 'Latency (99th)' } + ); + } +} + +export function getColumns({ + serviceName, + latencyAggregationType, + transactionsMetricsData, +}: { + serviceName: string; + latencyAggregationType?: string; + transactionsMetricsData?: TransactionGroupMetrics; +}): Array> { + return [ + { + field: 'name', + sortable: true, + name: i18n.translate( + 'xpack.apm.serviceOverview.transactionsTableColumnName', + { defaultMessage: 'Name' } + ), + render: (_, { name, transactionType: type }) => { + return ( + + {name} + + } + /> + ); + }, + }, + { + field: 'latency', + sortable: true, + name: getLatencyAggregationTypeLabel(latencyAggregationType), + width: px(unit * 10), + render: (_, { latency, name }) => { + const timeseries = transactionsMetricsData + ? transactionsMetricsData[name]?.latency + : undefined; + return ( + + ); + }, + }, + { + field: 'throughput', + sortable: true, + name: i18n.translate( + 'xpack.apm.serviceOverview.transactionsTableColumnThroughput', + { defaultMessage: 'Throughput' } + ), + width: px(unit * 10), + render: (_, { throughput, name }) => { + const timeseries = transactionsMetricsData + ? transactionsMetricsData[name]?.throughput + : undefined; + return ( + + ); + }, + }, + { + field: 'errorRate', + sortable: true, + name: i18n.translate( + 'xpack.apm.serviceOverview.transactionsTableColumnErrorRate', + { defaultMessage: 'Error rate' } + ), + width: px(unit * 8), + render: (_, { errorRate, name }) => { + const timeseries = transactionsMetricsData + ? transactionsMetricsData[name]?.errorRate + : undefined; + return ( + + ); + }, + }, + { + field: 'impact', + sortable: true, + name: i18n.translate( + 'xpack.apm.serviceOverview.transactionsTableColumnImpact', + { defaultMessage: 'Impact' } + ), + width: px(unit * 5), + render: (_, { impact }) => { + return ; + }, + }, + ]; +} 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 5c64b6d6e07dd..e2242aa68b77c 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 @@ -4,22 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiBasicTable, - EuiBasicTableColumn, - EuiFlexGroup, - EuiFlexItem, - EuiTitle, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useMemo, useState } from 'react'; -import { ValuesType } from 'utility-types'; +import React, { useMemo } from 'react'; import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; -import { - asMillisecondDuration, - asPercent, - asTransactionRate, -} from '../../../../../common/utils/formatters'; 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'; @@ -27,176 +15,23 @@ import { APIReturnType, callApmApi, } from '../../../../services/rest/createCallApmApi'; -import { px, unit } from '../../../../style/variables'; -import { SparkPlot } from '../../../shared/charts/spark_plot'; -import { ImpactBar } from '../../../shared/ImpactBar'; -import { TransactionDetailLink } from '../../../shared/Links/apm/transaction_detail_link'; import { TransactionOverviewLink } from '../../../shared/Links/apm/transaction_overview_link'; +import { ManagedTable } from '../../../shared/ManagedTable'; import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; -import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; +import { getColumns } from './get_columns'; -type ServiceTransactionGroupItem = ValuesType< - APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/overview'>['transactionGroups'] ->; - -type TransactionGroupMetrics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/metrics'>; +export type TransactionGroupsOverview = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/overview'>; interface Props { serviceName: string; } -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, +const INITIAL_STATE: TransactionGroupsOverview = { + transactionGroups: [], + isAggregationAccurate: true, }; -function getLatencyAggregationTypeLabel(latencyAggregationType?: string) { - switch (latencyAggregationType) { - case 'avg': - return i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableColumnLatency.avg', - { - defaultMessage: 'Latency (avg.)', - } - ); - - case 'p95': - return i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableColumnLatency.p95', - { - defaultMessage: 'Latency (95th)', - } - ); - - case 'p99': - return i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableColumnLatency.p99', - { - defaultMessage: 'Latency (99th)', - } - ); - } -} - -function getColumns({ - serviceName, - latencyAggregationType, - transactionsMetricsData, -}: { - serviceName: string; - latencyAggregationType?: string; - transactionsMetricsData?: TransactionGroupMetrics; -}): Array> { - return [ - { - field: 'name', - name: i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableColumnName', - { - defaultMessage: 'Name', - } - ), - render: (_, { name, transactionType: type }) => { - return ( - - {name} - - } - /> - ); - }, - }, - { - field: 'latency', - name: getLatencyAggregationTypeLabel(latencyAggregationType), - width: px(unit * 10), - render: (_, { latency, name }) => { - const timeseries = transactionsMetricsData - ? transactionsMetricsData[name]?.latency.timeseries - : undefined; - return ( - - ); - }, - }, - { - field: 'throughput', - name: i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableColumnThroughput', - { defaultMessage: 'Throughput' } - ), - width: px(unit * 10), - render: (_, { throughput, name }) => { - const timeseries = transactionsMetricsData - ? transactionsMetricsData[name]?.throughput.timeseries - : undefined; - return ( - - ); - }, - }, - { - field: 'errorRate', - name: i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableColumnErrorRate', - { - defaultMessage: 'Error rate', - } - ), - width: px(unit * 8), - render: (_, { errorRate, name }) => { - const timeseries = transactionsMetricsData - ? transactionsMetricsData[name]?.errorRate.timeseries - : undefined; - return ( - - ); - }, - }, - { - field: 'impact', - name: i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableColumnImpact', - { - defaultMessage: 'Impact', - } - ), - width: px(unit * 5), - render: (_, { impact }) => { - return ; - }, - }, - ]; -} - export function ServiceOverviewTransactionsTable(props: Props) { const { serviceName } = props; const { transactionType } = useApmServiceContext(); @@ -205,32 +40,10 @@ export function ServiceOverviewTransactionsTable(props: Props) { urlParams: { start, end, latencyAggregationType }, } = useUrlParams(); - const [tableOptions, setTableOptions] = useState<{ - pageIndex: number; - sort: { - direction: SortDirection; - field: SortField; - }; - }>({ - pageIndex: 0, - sort: DEFAULT_SORT, - }); - - const { - data = { - totalItemCount: 0, - items: [], - tableOptions: { - pageIndex: 0, - sort: DEFAULT_SORT, - }, - }, - status, - } = useFetcher(() => { + const { data = INITIAL_STATE, status } = useFetcher(() => { if (!start || !end || !latencyAggregationType || !transactionType) { return; } - return callApmApi({ endpoint: 'GET /api/apm/services/{serviceName}/transactions/groups/overview', @@ -240,54 +53,33 @@ export function ServiceOverviewTransactionsTable(props: Props) { start, end, uiFilters: JSON.stringify(uiFilters), - numBuckets: 20, - sortField: tableOptions.sort.field, - sortDirection: tableOptions.sort.direction, transactionType, latencyAggregationType: latencyAggregationType as LatencyAggregationType, }, }, - }).then((response) => { - return { - items: response.transactionGroups, - totalItemCount: response.totalTransactionGroups, - tableOptions: { - pageIndex: tableOptions.pageIndex, - sort: { - field: tableOptions.sort.field, - direction: tableOptions.sort.direction, - }, - }, - }; }); }, [ serviceName, start, end, uiFilters, - tableOptions.pageIndex, - tableOptions.sort.field, - tableOptions.sort.direction, transactionType, latencyAggregationType, ]); - const { - items, - totalItemCount, - tableOptions: { pageIndex, sort }, - } = data; + const { transactionGroups } = data; - const transactionNames = useMemo(() => items.map(({ name }) => name), [ - items, - ]); + const transactionNames = useMemo( + () => transactionGroups.map(({ name }) => name).join(), + [transactionGroups] + ); const { data: transactionsMetricsData, status: transactionsMetricsStatus, } = useFetcher( () => { - if (transactionNames.length && start && end && transactionType) { + if (transactionNames && start && end && transactionType) { return callApmApi({ endpoint: 'GET /api/apm/services/{serviceName}/transactions/groups/metrics', @@ -300,7 +92,7 @@ export function ServiceOverviewTransactionsTable(props: Props) { numBuckets: 20, transactionType, latencyAggregationType: latencyAggregationType as LatencyAggregationType, - transactionNames: transactionNames.join(), + transactionNames, }, }, }); @@ -317,6 +109,10 @@ export function ServiceOverviewTransactionsTable(props: Props) { transactionsMetricsData, }); + const isLoading = + status === FETCH_STATUS.LOADING || + transactionsMetricsStatus === FETCH_STATUS.LOADING; + return ( @@ -352,50 +148,16 @@ export function ServiceOverviewTransactionsTable(props: Props) { - { - setTableOptions({ - pageIndex: newTableOptions.page?.index ?? 0, - sort: newTableOptions.sort - ? { - field: newTableOptions.sort.field as SortField, - direction: newTableOptions.sort.direction, - } - : DEFAULT_SORT, - }); - }} - sorting={{ - enableAllColumns: true, - sort: { - direction: sort.direction, - field: sort.field, - }, - }} + items={transactionGroups} + initialSortField="impact" + initialSortDirection="desc" + initialPageSize={5} /> diff --git a/x-pack/plugins/apm/public/components/shared/ManagedTable/__snapshots__/ManagedTable.test.js.snap b/x-pack/plugins/apm/public/components/shared/ManagedTable/__snapshots__/ManagedTable.test.js.snap index 655fc5a25b9ef..f077ca301d5f9 100644 --- a/x-pack/plugins/apm/public/components/shared/ManagedTable/__snapshots__/ManagedTable.test.js.snap +++ b/x-pack/plugins/apm/public/components/shared/ManagedTable/__snapshots__/ManagedTable.test.js.snap @@ -33,6 +33,7 @@ exports[`ManagedTable component should render a page-full of items, with default }, ] } + loading={false} noItemsMessage="No items found" onChange={[Function]} pagination={ @@ -81,6 +82,7 @@ exports[`ManagedTable component should render when specifying initial values 1`] }, ] } + loading={false} noItemsMessage="No items found" onChange={[Function]} pagination={ diff --git a/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx b/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx index 8033b6415319e..6d462a20924ce 100644 --- a/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx @@ -39,6 +39,7 @@ interface Props { sortDirection: 'asc' | 'desc' ) => T[]; pagination?: boolean; + isLoading?: boolean; } function defaultSortFn( @@ -63,6 +64,7 @@ function UnoptimizedManagedTable(props: Props) { sortItems = true, sortFn = defaultSortFn, pagination = true, + isLoading = false, } = props; const { @@ -124,6 +126,7 @@ function UnoptimizedManagedTable(props: Props) { return ( >} // EuiBasicTableColumn is stricter than ITableColumn diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_transaction_groups_for_page.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_transaction_groups.ts similarity index 80% rename from x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_transaction_groups_for_page.ts rename to x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_transaction_groups.ts index 7fec791a6255e..f99826b1269bf 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_transaction_groups_for_page.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_transaction_groups.ts @@ -3,19 +3,17 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { orderBy } from 'lodash'; -import { ValuesType } from 'utility-types'; -import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; -import { PromiseReturnType } from '../../../../../observability/typings/common'; -import { EventOutcome } from '../../../../common/event_outcome'; +import { sortBy } from 'lodash'; import { ESFilter } from '../../../../../../typings/elasticsearch'; -import { rangeFilter } from '../../../../common/utils/range_filter'; import { EVENT_OUTCOME, SERVICE_NAME, TRANSACTION_NAME, TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; +import { EventOutcome } from '../../../../common/event_outcome'; +import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; +import { rangeFilter } from '../../../../common/utils/range_filter'; import { getProcessorEventForAggregatedTransactions, getTransactionDurationFieldForAggregatedTransactions, @@ -26,26 +24,13 @@ import { getLatencyValue, } from '../../helpers/latency_aggregation_type'; -export type ServiceOverviewTransactionGroupSortField = - | 'name' - | 'latency' - | 'throughput' - | 'errorRate' - | 'impact'; - -export type TransactionGroupWithoutTimeseriesData = ValuesType< - PromiseReturnType['transactionGroups'] ->; - -export async function getTransactionGroupsForPage({ +export async function getTransactionGroups({ apmEventClient, searchAggregatedTransactions, serviceName, start, end, esFilter, - sortField, - sortDirection, transactionType, latencyAggregationType, }: { @@ -55,8 +40,6 @@ export async function getTransactionGroupsForPage({ start: number; end: number; esFilter: ESFilter[]; - sortField: ServiceOverviewTransactionGroupSortField; - sortDirection: 'asc' | 'desc'; transactionType: string; latencyAggregationType: LatencyAggregationType; }) { @@ -140,18 +123,14 @@ export async function getTransactionGroupsForPage({ 100, })); - // Sort transaction groups first, and only get timeseries for data in view. - // This is to limit the possibility of creating too many buckets. - - const sortedAndSlicedTransactionGroups = orderBy( + // By default sorts transactions by impact + const sortedTransactionGroups = sortBy( transactionGroupsWithImpact, - sortField, - [sortDirection] - ); + 'impact' + ).reverse(); return { - transactionGroups: sortedAndSlicedTransactionGroups, - totalTransactionGroups: transactionGroups.length, + transactionGroups: sortedTransactionGroups, isAggregationAccurate: (response.aggregations?.transaction_groups.sum_other_doc_count ?? 0) === 0, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts index b6a0808bbb6fa..0053b556dd099 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts @@ -6,26 +6,17 @@ import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; -import { - getTransactionGroupsForPage, - ServiceOverviewTransactionGroupSortField, -} from './get_transaction_groups_for_page'; +import { getTransactionGroups } from './get_transaction_groups'; export async function getServiceTransactionGroups({ serviceName, setup, - numBuckets, - sortDirection, - sortField, searchAggregatedTransactions, transactionType, latencyAggregationType, }: { serviceName: string; setup: Setup & SetupTimeRange; - numBuckets: number; - sortDirection: 'asc' | 'desc'; - sortField: ServiceOverviewTransactionGroupSortField; searchAggregatedTransactions: boolean; transactionType: string; latencyAggregationType: LatencyAggregationType; @@ -34,33 +25,23 @@ export async function getServiceTransactionGroups({ const { transactionGroups, - totalTransactionGroups, isAggregationAccurate, - } = await getTransactionGroupsForPage({ + } = await getTransactionGroups({ apmEventClient, start, end, serviceName, esFilter, - sortField, - sortDirection, searchAggregatedTransactions, transactionType, latencyAggregationType, }); return { - transactionGroups: transactionGroups.map((transactionGroup) => { - return { - name: transactionGroup.name, - transactionType, - latency: { value: transactionGroup.latency }, - throughput: { value: transactionGroup.throughput }, - errorRate: { value: transactionGroup.errorRate }, - impact: transactionGroup.impact, - }; - }), - totalTransactionGroups, + transactionGroups: transactionGroups.map((transactionGroup) => ({ + ...transactionGroup, + transactionType, + })), isAggregationAccurate, }; } diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_timeseries_data_for_transaction_groups.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/get_timeseries_data_for_transaction_groups.ts similarity index 100% rename from x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_timeseries_data_for_transaction_groups.ts rename to x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/get_timeseries_data_for_transaction_groups.ts diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts index e508ccbc851b4..5841bd69905d9 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts @@ -9,7 +9,7 @@ import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames'; import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; import { getLatencyValue } from '../../helpers/latency_aggregation_type'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; -import { getTimeseriesDataForTransactionGroups } from '../get_service_transaction_groups/get_timeseries_data_for_transaction_groups'; +import { getTimeseriesDataForTransactionGroups } from './get_timeseries_data_for_transaction_groups'; export async function getServiceTransactionGroupsMetrics({ serviceName, @@ -31,9 +31,9 @@ export async function getServiceTransactionGroupsMetrics({ Record< string, { - latency: { timeseries: Coordinate[] }; - throughput: { timeseries: Coordinate[] }; - errorRate: { timeseries: Coordinate[] }; + latency: Coordinate[]; + throughput: Coordinate[]; + errorRate: Coordinate[]; } > > { @@ -62,47 +62,40 @@ export async function getServiceTransactionGroupsMetrics({ const x = timeseriesBucket.key; return { ...acc, - latency: { - timeseries: [ - ...acc.latency.timeseries, - { - x, - y: getLatencyValue({ - latencyAggregationType, - aggregation: timeseriesBucket.latency, - }), - }, - ], - }, - throughput: { - timeseries: [ - ...acc.throughput.timeseries, - { - x, - y: timeseriesBucket.transaction_count.value / deltaAsMinutes, - }, - ], - }, - errorRate: { - timeseries: [ - ...acc.errorRate.timeseries, - { - x, - y: - timeseriesBucket.transaction_count.value > 0 - ? (timeseriesBucket[EVENT_OUTCOME].transaction_count - .value ?? 0) / - timeseriesBucket.transaction_count.value - : null, - }, - ], - }, + latency: [ + ...acc.latency, + { + x, + y: getLatencyValue({ + latencyAggregationType, + aggregation: timeseriesBucket.latency, + }), + }, + ], + throughput: [ + ...acc.throughput, + { + x, + y: timeseriesBucket.transaction_count.value / deltaAsMinutes, + }, + ], + errorRate: [ + ...acc.errorRate, + { + x, + y: + timeseriesBucket.transaction_count.value > 0 + ? (timeseriesBucket[EVENT_OUTCOME].transaction_count + .value ?? 0) / timeseriesBucket.transaction_count.value + : null, + }, + ], }; }, { - latency: { timeseries: [] as Coordinate[] }, - throughput: { timeseries: [] as Coordinate[] }, - errorRate: { timeseries: [] as Coordinate[] }, + latency: [] as Coordinate[], + throughput: [] as Coordinate[], + errorRate: [] as Coordinate[], } ), }; diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index 201493dea24bd..db259c1ca7656 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -71,15 +71,6 @@ export const transactionGroupsOverviewRoute = createRoute({ rangeRt, uiFiltersRt, t.type({ - numBuckets: toNumberRt, - sortDirection: t.union([t.literal('asc'), t.literal('desc')]), - sortField: t.union([ - t.literal('name'), - t.literal('latency'), - t.literal('throughput'), - t.literal('errorRate'), - t.literal('impact'), - ]), transactionType: t.string, latencyAggregationType: latencyAggregationTypeRt, }), @@ -97,23 +88,14 @@ export const transactionGroupsOverviewRoute = createRoute({ const { path: { serviceName }, - query: { - latencyAggregationType, - numBuckets, - sortDirection, - sortField, - transactionType, - }, + query: { latencyAggregationType, transactionType }, } = context.params; return getServiceTransactionGroups({ setup, serviceName, searchAggregatedTransactions, - sortDirection, - sortField, transactionType, - numBuckets, latencyAggregationType: latencyAggregationType as LatencyAggregationType, }); }, From 8a10a56737b1646d10098a917973e31288c82557 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 26 Jan 2021 14:12:17 +0100 Subject: [PATCH 08/31] addressing PR comments --- .../get_transaction_groups.ts | 138 -------------- .../get_service_transaction_groups/index.ts | 119 ++++++++++-- ..._timeseries_data_for_transaction_groups.ts | 114 ------------ .../index.ts | 169 ++++++++++++------ .../transactions_groups_metrics.ts | 14 +- .../transactions_groups_overview.ts | 56 +----- 6 files changed, 224 insertions(+), 386 deletions(-) delete mode 100644 x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_transaction_groups.ts delete mode 100644 x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/get_timeseries_data_for_transaction_groups.ts diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_transaction_groups.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_transaction_groups.ts deleted file mode 100644 index f99826b1269bf..0000000000000 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_transaction_groups.ts +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { sortBy } from 'lodash'; -import { ESFilter } from '../../../../../../typings/elasticsearch'; -import { - EVENT_OUTCOME, - SERVICE_NAME, - TRANSACTION_NAME, - TRANSACTION_TYPE, -} from '../../../../common/elasticsearch_fieldnames'; -import { EventOutcome } from '../../../../common/event_outcome'; -import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; -import { rangeFilter } from '../../../../common/utils/range_filter'; -import { - getProcessorEventForAggregatedTransactions, - getTransactionDurationFieldForAggregatedTransactions, -} from '../../helpers/aggregated_transactions'; -import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client'; -import { - getLatencyAggregation, - getLatencyValue, -} from '../../helpers/latency_aggregation_type'; - -export async function getTransactionGroups({ - apmEventClient, - searchAggregatedTransactions, - serviceName, - start, - end, - esFilter, - transactionType, - latencyAggregationType, -}: { - apmEventClient: APMEventClient; - searchAggregatedTransactions: boolean; - serviceName: string; - start: number; - end: number; - esFilter: ESFilter[]; - transactionType: string; - latencyAggregationType: LatencyAggregationType; -}) { - const deltaAsMinutes = (end - start) / 1000 / 60; - - const field = getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ); - - const response = await apmEventClient.search({ - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, - ...esFilter, - ], - }, - }, - aggs: { - transaction_groups: { - terms: { - field: TRANSACTION_NAME, - size: 500, - order: { _count: 'desc' }, - }, - aggs: { - ...getLatencyAggregation(latencyAggregationType, field), - transaction_count: { value_count: { field } }, - [EVENT_OUTCOME]: { - filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } }, - aggs: { transaction_count: { value_count: { field } } }, - }, - }, - }, - }, - }, - }); - - const transactionGroups = - response.aggregations?.transaction_groups.buckets.map((bucket) => { - const errorRate = - bucket.transaction_count.value > 0 - ? (bucket[EVENT_OUTCOME].transaction_count.value ?? 0) / - bucket.transaction_count.value - : null; - - return { - name: bucket.key as string, - latency: getLatencyValue({ - latencyAggregationType, - aggregation: bucket.latency, - }), - throughput: bucket.transaction_count.value / deltaAsMinutes, - errorRate, - }; - }) ?? []; - - const totalDurationValues = transactionGroups.map( - (group) => (group.latency ?? 0) * group.throughput - ); - - const minTotalDuration = Math.min(...totalDurationValues); - const maxTotalDuration = Math.max(...totalDurationValues); - - const transactionGroupsWithImpact = transactionGroups.map((group) => ({ - ...group, - impact: - (((group.latency ?? 0) * group.throughput - minTotalDuration) / - (maxTotalDuration - minTotalDuration)) * - 100, - })); - - // By default sorts transactions by impact - const sortedTransactionGroups = sortBy( - transactionGroupsWithImpact, - 'impact' - ).reverse(); - - return { - transactionGroups: sortedTransactionGroups, - isAggregationAccurate: - (response.aggregations?.transaction_groups.sum_other_doc_count ?? 0) === - 0, - }; -} diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts index 0053b556dd099..af2cedae19464 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts @@ -4,9 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ +import { sortBy } from 'lodash'; +import { + EVENT_OUTCOME, + SERVICE_NAME, + TRANSACTION_NAME, + TRANSACTION_TYPE, +} from '../../../../common/elasticsearch_fieldnames'; +import { EventOutcome } from '../../../../common/event_outcome'; import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; +import { rangeFilter } from '../../../../common/utils/range_filter'; +import { + getProcessorEventForAggregatedTransactions, + getTransactionDurationFieldForAggregatedTransactions, +} from '../../helpers/aggregated_transactions'; +import { + getLatencyAggregation, + getLatencyValue, +} from '../../helpers/latency_aggregation_type'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; -import { getTransactionGroups } from './get_transaction_groups'; export async function getServiceTransactionGroups({ serviceName, @@ -22,26 +38,99 @@ export async function getServiceTransactionGroups({ latencyAggregationType: LatencyAggregationType; }) { const { apmEventClient, start, end, esFilter } = setup; + const deltaAsMinutes = (end - start) / 1000 / 60; - const { - transactionGroups, - isAggregationAccurate, - } = await getTransactionGroups({ - apmEventClient, - start, - end, - serviceName, - esFilter, - searchAggregatedTransactions, - transactionType, - latencyAggregationType, + const field = getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ); + + const response = await apmEventClient.search({ + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + { range: rangeFilter(start, end) }, + ...esFilter, + ], + }, + }, + aggs: { + transaction_groups: { + terms: { + field: TRANSACTION_NAME, + size: 500, + order: { _count: 'desc' }, + }, + aggs: { + ...getLatencyAggregation(latencyAggregationType, field), + transaction_count: { value_count: { field } }, + [EVENT_OUTCOME]: { + filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } }, + aggs: { transaction_count: { value_count: { field } } }, + }, + }, + }, + }, + }, }); + const transactionGroups = + response.aggregations?.transaction_groups.buckets.map((bucket) => { + const errorRate = + bucket.transaction_count.value > 0 + ? (bucket[EVENT_OUTCOME].transaction_count.value ?? 0) / + bucket.transaction_count.value + : null; + + return { + name: bucket.key as string, + latency: getLatencyValue({ + latencyAggregationType, + aggregation: bucket.latency, + }), + throughput: bucket.transaction_count.value / deltaAsMinutes, + errorRate, + }; + }) ?? []; + + const totalDurationValues = transactionGroups.map( + (group) => (group.latency ?? 0) * group.throughput + ); + + const minTotalDuration = Math.min(...totalDurationValues); + const maxTotalDuration = Math.max(...totalDurationValues); + + const transactionGroupsWithImpact = transactionGroups.map((group) => ({ + ...group, + impact: + (((group.latency ?? 0) * group.throughput - minTotalDuration) / + (maxTotalDuration - minTotalDuration)) * + 100, + })); + + // By default sorts transactions by impact + const sortedTransactionGroups = sortBy( + transactionGroupsWithImpact, + 'impact' + ).reverse(); + return { - transactionGroups: transactionGroups.map((transactionGroup) => ({ + transactionGroups: sortedTransactionGroups.map((transactionGroup) => ({ ...transactionGroup, transactionType, })), - isAggregationAccurate, + isAggregationAccurate: + (response.aggregations?.transaction_groups.sum_other_doc_count ?? 0) === + 0, }; } diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/get_timeseries_data_for_transaction_groups.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/get_timeseries_data_for_transaction_groups.ts deleted file mode 100644 index d97da74b0e369..0000000000000 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/get_timeseries_data_for_transaction_groups.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; -import { PromiseReturnType } from '../../../../../observability/typings/common'; -import { EventOutcome } from '../../../../common/event_outcome'; -import { rangeFilter } from '../../../../common/utils/range_filter'; -import { - EVENT_OUTCOME, - SERVICE_NAME, - TRANSACTION_NAME, - TRANSACTION_TYPE, -} from '../../../../common/elasticsearch_fieldnames'; - -import { ESFilter } from '../../../../../../typings/elasticsearch'; -import { - getProcessorEventForAggregatedTransactions, - getTransactionDurationFieldForAggregatedTransactions, -} from '../../helpers/aggregated_transactions'; -import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client'; -import { getBucketSize } from '../../helpers/get_bucket_size'; -import { getLatencyAggregation } from '../../helpers/latency_aggregation_type'; - -export type TransactionGroupTimeseriesData = PromiseReturnType< - typeof getTimeseriesDataForTransactionGroups ->; - -export async function getTimeseriesDataForTransactionGroups({ - apmEventClient, - start, - end, - serviceName, - transactionNames, - esFilter, - searchAggregatedTransactions, - numBuckets, - transactionType, - latencyAggregationType, -}: { - apmEventClient: APMEventClient; - start: number; - end: number; - serviceName: string; - transactionNames: string[]; - esFilter: ESFilter[]; - searchAggregatedTransactions: boolean; - numBuckets: number; - transactionType: string; - latencyAggregationType: LatencyAggregationType; -}) { - const { intervalString } = getBucketSize({ start, end, numBuckets }); - - const field = getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ); - - const timeseriesResponse = await apmEventClient.search({ - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - { terms: { [TRANSACTION_NAME]: transactionNames } }, - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, - ...esFilter, - ], - }, - }, - aggs: { - transaction_groups: { - terms: { - field: TRANSACTION_NAME, - size: 500, - }, - aggs: { - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, - min_doc_count: 0, - extended_bounds: { - min: start, - max: end, - }, - }, - aggs: { - ...getLatencyAggregation(latencyAggregationType, field), - transaction_count: { value_count: { field } }, - [EVENT_OUTCOME]: { - filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } }, - aggs: { transaction_count: { value_count: { field } } }, - }, - }, - }, - }, - }, - }, - }, - }); - - return timeseriesResponse.aggregations?.transaction_groups.buckets ?? []; -} diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts index 5841bd69905d9..c9c79bd452e4d 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts @@ -4,12 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Coordinate } from '../../../../typings/timeseries'; -import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames'; +import { + EVENT_OUTCOME, + SERVICE_NAME, + TRANSACTION_NAME, + TRANSACTION_TYPE, +} from '../../../../common/elasticsearch_fieldnames'; +import { EventOutcome } from '../../../../common/event_outcome'; import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; -import { getLatencyValue } from '../../helpers/latency_aggregation_type'; +import { rangeFilter } from '../../../../common/utils/range_filter'; +import { Coordinate } from '../../../../typings/timeseries'; +import { + getProcessorEventForAggregatedTransactions, + getTransactionDurationFieldForAggregatedTransactions, +} from '../../helpers/aggregated_transactions'; +import { getBucketSize } from '../../helpers/get_bucket_size'; +import { + getLatencyAggregation, + getLatencyValue, +} from '../../helpers/latency_aggregation_type'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; -import { getTimeseriesDataForTransactionGroups } from './get_timeseries_data_for_transaction_groups'; export async function getServiceTransactionGroupsMetrics({ serviceName, @@ -38,66 +52,103 @@ export async function getServiceTransactionGroupsMetrics({ > > { const { apmEventClient, start, end, esFilter } = setup; - - const buckets = await getTimeseriesDataForTransactionGroups({ - apmEventClient, - start, - end, - esFilter, - numBuckets, - searchAggregatedTransactions, - serviceName, - transactionNames, - transactionType, - latencyAggregationType, - }); const deltaAsMinutes = (end - start) / 1000 / 60; - return buckets.reduce((bucketAcc, bucket) => { - const transactionName = bucket.key; - return { - ...bucketAcc, - [transactionName]: bucket.timeseries.buckets.reduce( - (acc, timeseriesBucket) => { - const x = timeseriesBucket.key; - return { - ...acc, - latency: [ - ...acc.latency, - { - x, - y: getLatencyValue({ - latencyAggregationType, - aggregation: timeseriesBucket.latency, - }), - }, - ], - throughput: [ - ...acc.throughput, - { - x, - y: timeseriesBucket.transaction_count.value / deltaAsMinutes, + const { intervalString } = getBucketSize({ start, end, numBuckets }); + + const field = getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ); + + const response = await apmEventClient.search({ + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + { terms: { [TRANSACTION_NAME]: transactionNames } }, + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + { range: rangeFilter(start, end) }, + ...esFilter, + ], + }, + }, + aggs: { + transaction_groups: { + terms: { + field: TRANSACTION_NAME, + size: transactionNames.length, + }, + aggs: { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { + min: start, + max: end, + }, }, - ], - errorRate: [ - ...acc.errorRate, - { - x, - y: - timeseriesBucket.transaction_count.value > 0 - ? (timeseriesBucket[EVENT_OUTCOME].transaction_count - .value ?? 0) / timeseriesBucket.transaction_count.value - : null, + aggs: { + ...getLatencyAggregation(latencyAggregationType, field), + transaction_count: { value_count: { field } }, + [EVENT_OUTCOME]: { + filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } }, + aggs: { transaction_count: { value_count: { field } } }, + }, }, - ], - }; + }, + }, }, - { - latency: [] as Coordinate[], - throughput: [] as Coordinate[], - errorRate: [] as Coordinate[], - } - ), + }, + }, + }); + + const buckets = response.aggregations?.transaction_groups.buckets ?? []; + + return buckets.reduce((acc, bucket) => { + const transactionName = bucket.key; + + const latency: Coordinate[] = []; + const throughput: Coordinate[] = []; + const errorRate: Coordinate[] = []; + + bucket.timeseries.buckets.forEach((timeseriesBucket) => { + const x = timeseriesBucket.key; + latency.push({ + x, + y: getLatencyValue({ + latencyAggregationType, + aggregation: timeseriesBucket.latency, + }), + }); + + throughput.push({ + x, + y: timeseriesBucket.transaction_count.value / deltaAsMinutes, + }); + + errorRate.push({ + x, + y: + timeseriesBucket.transaction_count.value > 0 + ? (timeseriesBucket[EVENT_OUTCOME].transaction_count.value ?? 0) / + timeseriesBucket.transaction_count.value + : null, + }); + }); + return { + ...acc, + [transactionName]: { latency, throughput, errorRate }, }; }, {}); } diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_metrics.ts b/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_metrics.ts index 13bb4edd54c0a..87aa24d2aea67 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_metrics.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_metrics.ts @@ -95,17 +95,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { const firstItem = response.body['DispatcherServlet#doGet']; - expectSnapshot( - firstItem.latency.timeseries.filter(({ y }: any) => y > 0).length - ).toMatchInline(`9`); + expectSnapshot(firstItem.latency.filter(({ y }: any) => y > 0).length).toMatchInline(`9`); - expectSnapshot( - firstItem.throughput.timeseries.filter(({ y }: any) => y > 0).length - ).toMatchInline(`9`); + expectSnapshot(firstItem.throughput.filter(({ y }: any) => y > 0).length).toMatchInline( + `9` + ); - expectSnapshot( - firstItem.errorRate.timeseries.filter(({ y }: any) => y > 0).length - ).toMatchInline(`1`); + expectSnapshot(firstItem.errorRate.filter(({ y }: any) => y > 0).length).toMatchInline(`1`); }); }); }); diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts b/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts index 4f235a3d275ee..fdf3c55276dad 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts @@ -27,9 +27,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { start, end, uiFilters: '{}', - numBuckets: 20, - sortDirection: 'desc', - sortField: 'impact', latencyAggregationType: 'avg', transactionType: 'request', }, @@ -38,7 +35,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expect(response.body).to.eql({ - totalTransactionGroups: 0, transactionGroups: [], isAggregationAccurate: true, }); @@ -57,9 +53,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { start, end, uiFilters: '{}', - numBuckets: 20, - sortDirection: 'desc', - sortField: 'impact', transactionType: 'request', latencyAggregationType: 'avg', }, @@ -68,8 +61,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); - expectSnapshot(response.body.totalTransactionGroups).toMatchInline(`12`); - expectSnapshot(response.body.transactionGroups.map((group: any) => group.name)) .toMatchInline(` Array [ @@ -108,21 +99,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { const firstItem = response.body.transactionGroups[0]; - expectSnapshot( - pick(firstItem, 'name', 'latency.value', 'throughput.value', 'errorRate.value', 'impact') - ).toMatchInline(` + expectSnapshot(pick(firstItem, 'name', 'latency', 'throughput', 'errorRate', 'impact')) + .toMatchInline(` Object { - "errorRate": Object { - "value": 0.0625, - }, + "errorRate": 0.0625, "impact": 100, - "latency": Object { - "value": 1044995.1875, - }, + "latency": 1044995.1875, "name": "DispatcherServlet#doGet", - "throughput": Object { - "value": 0.533333333333333, - }, + "throughput": 0.533333333333333, } `); }); @@ -135,9 +119,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { start, end, uiFilters: '{}', - numBuckets: 20, - sortDirection: 'desc', - sortField: 'impact', transactionType: 'request', latencyAggregationType: 'avg', }, @@ -159,9 +140,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { start, end, uiFilters: '{}', - numBuckets: 20, - sortDirection: 'desc', - sortField: 'impact', transactionType: 'request', latencyAggregationType: 'avg', }, @@ -174,30 +152,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(ascendingOccurrences).to.eql(sortBy(ascendingOccurrences.concat()).reverse()); }); - - it('sorts items by the correct field', async () => { - const response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/overview`, - query: { - start, - end, - uiFilters: '{}', - numBuckets: 20, - sortDirection: 'desc', - sortField: 'latency', - transactionType: 'request', - latencyAggregationType: 'avg', - }, - }) - ); - - expect(response.status).to.be(200); - - const latencies = response.body.transactionGroups.map((group: any) => group.latency.value); - - expect(latencies).to.eql(sortBy(latencies.concat()).reverse()); - }); }); }); } From 3eaf6b75609f9a10087ad0fd44327dbaacc1aea2 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 26 Jan 2021 17:32:50 +0100 Subject: [PATCH 09/31] addressing PR comments --- .../index.tsx | 140 +++++++++++++----- .../get_service_transaction_groups/index.ts | 2 + .../index.ts | 32 ++-- 3 files changed, 124 insertions(+), 50 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 e2242aa68b77c..602b166d2bb87 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 @@ -4,9 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; +import { + EuiBasicTable, + EuiFlexGroup, + EuiFlexItem, + EuiTitle, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useMemo } from 'react'; +import { orderBy } from 'lodash'; +import React, { useMemo, useState } from 'react'; import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; @@ -16,7 +22,6 @@ import { callApmApi, } from '../../../../services/rest/createCallApmApi'; import { TransactionOverviewLink } from '../../../shared/Links/apm/transaction_overview_link'; -import { ManagedTable } from '../../../shared/ManagedTable'; import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; import { getColumns } from './get_columns'; @@ -30,10 +35,31 @@ interface Props { const INITIAL_STATE: TransactionGroupsOverview = { transactionGroups: [], isAggregationAccurate: true, + requestId: '', +}; + +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(props: Props) { - const { serviceName } = props; +export function ServiceOverviewTransactionsTable({ serviceName }: Props) { + const [tableOptions, setTableOptions] = useState<{ + pageIndex: number; + sort: { + direction: SortDirection; + field: SortField; + }; + }>({ + pageIndex: 0, + sort: DEFAULT_SORT, + }); + + const { pageIndex, sort } = tableOptions; + const { transactionType } = useApmServiceContext(); const { uiFilters, @@ -67,11 +93,16 @@ export function ServiceOverviewTransactionsTable(props: Props) { latencyAggregationType, ]); - const { transactionGroups } = data; + const { transactionGroups, requestId } = data; + const sortedSlicedItems = orderBy( + transactionGroups, + sort.field, + sort.direction + ).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE); const transactionNames = useMemo( - () => transactionGroups.map(({ name }) => name).join(), - [transactionGroups] + () => sortedSlicedItems.map(({ name }) => name).join(), + [sortedSlicedItems] ); const { @@ -79,40 +110,67 @@ export function ServiceOverviewTransactionsTable(props: Props) { status: transactionsMetricsStatus, } = useFetcher( () => { - if (transactionNames && start && end && transactionType) { - return callApmApi({ - endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/metrics', - params: { - path: { serviceName }, - query: { - start, - end, - uiFilters: JSON.stringify(uiFilters), - numBuckets: 20, - transactionType, - latencyAggregationType: latencyAggregationType as LatencyAggregationType, - transactionNames, + async function fetchMetrics() { + if ( + !isEmpty(requestId) && + transactionNames && + start && + end && + transactionType + ) { + const metrics = await callApmApi({ + endpoint: + 'GET /api/apm/services/{serviceName}/transactions/groups/metrics', + params: { + path: { serviceName }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + numBuckets: 20, + transactionType, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, + transactionNames, + }, }, - }, - }); + }); + + return { [requestId]: metrics }; + } } + return fetchMetrics(); }, - // only fetches metrics when transaction names change + // only fetches metrics when requestId changes or transaction names changes // eslint-disable-next-line react-hooks/exhaustive-deps - [transactionNames] + [requestId, transactionNames] ); const columns = getColumns({ serviceName, latencyAggregationType, - transactionsMetricsData, + transactionsMetricsData: transactionsMetricsData + ? transactionsMetricsData[requestId] + : undefined, }); const isLoading = status === FETCH_STATUS.LOADING || transactionsMetricsStatus === FETCH_STATUS.LOADING; + const pagination = { + pageIndex, + pageSize: PAGE_SIZE, + totalItemCount: transactionGroups.length, + hidePerPageOptions: true, + }; + + const sorting = { + sort: { + field: sort.field, + direction: sort.direction, + }, + }; + return ( @@ -150,14 +208,28 @@ export function ServiceOverviewTransactionsTable(props: Props) { - { + setTableOptions({ + pageIndex: newTableOptions.page?.index ?? 0, + sort: newTableOptions.sort + ? { + field: newTableOptions.sort.field as SortField, + direction: newTableOptions.sort.direction, + } + : DEFAULT_SORT, + }); + }} /> diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts index af2cedae19464..b989d5a30b845 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts @@ -5,6 +5,7 @@ */ import { sortBy } from 'lodash'; +import uuid from 'uuid'; import { EVENT_OUTCOME, SERVICE_NAME, @@ -125,6 +126,7 @@ export async function getServiceTransactionGroups({ ).reverse(); return { + requestId: uuid(), transactionGroups: sortedTransactionGroups.map((transactionGroup) => ({ ...transactionGroup, transactionType, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts index c9c79bd452e4d..0948a4a51b7a2 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts @@ -118,34 +118,34 @@ export async function getServiceTransactionGroupsMetrics({ return buckets.reduce((acc, bucket) => { const transactionName = bucket.key; - const latency: Coordinate[] = []; - const throughput: Coordinate[] = []; - const errorRate: Coordinate[] = []; - - bucket.timeseries.buckets.forEach((timeseriesBucket) => { - const x = timeseriesBucket.key; - latency.push({ - x, + const latency: Coordinate[] = bucket.timeseries.buckets.map( + (timeseriesBucket) => ({ + x: timeseriesBucket.key, y: getLatencyValue({ latencyAggregationType, aggregation: timeseriesBucket.latency, }), - }); + }) + ); - throughput.push({ - x, + const throughput: Coordinate[] = bucket.timeseries.buckets.map( + (timeseriesBucket) => ({ + x: timeseriesBucket.key, y: timeseriesBucket.transaction_count.value / deltaAsMinutes, - }); + }) + ); - errorRate.push({ - x, + const errorRate: Coordinate[] = bucket.timeseries.buckets.map( + (timeseriesBucket) => ({ + x: timeseriesBucket.key, y: timeseriesBucket.transaction_count.value > 0 ? (timeseriesBucket[EVENT_OUTCOME].transaction_count.value ?? 0) / timeseriesBucket.transaction_count.value : null, - }); - }); + }) + ); + return { ...acc, [transactionName]: { latency, throughput, errorRate }, From aab9c339d68a243cc3135a2da7774ce79b7139fb Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 1 Feb 2021 11:35:47 +0100 Subject: [PATCH 10/31] addressing PR comments --- .../get_columns.tsx | 25 ++-- .../index.tsx | 29 ++-- ...x.ts => get_service_transaction_groups.ts} | 14 +- ...service_transaction_groups_agg_results.ts} | 133 ++++++++++++++++-- .../apm/server/routes/create_apm_api.ts | 4 +- .../plugins/apm/server/routes/transactions.ts | 9 +- .../transactions_groups_metrics.ts | 108 -------------- .../transactions_groups_overview.ts | 51 +------ 8 files changed, 168 insertions(+), 205 deletions(-) rename x-pack/plugins/apm/server/lib/services/{get_service_transaction_groups/index.ts => get_service_transaction_groups.ts} (89%) rename x-pack/plugins/apm/server/lib/services/{get_service_transaction_groups_metrics/index.ts => get_service_transaction_groups_agg_results.ts} (51%) delete mode 100644 x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_metrics.ts diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx index 71482eba345ad..06268b9652c96 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx @@ -23,7 +23,7 @@ import { TransactionGroupsOverview } from './'; export type ServiceTransactionGroupItem = ValuesType< TransactionGroupsOverview['transactionGroups'] >; -type TransactionGroupMetrics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/metrics'>; +type TransactionGroupMetrics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/agg_results'>; function getLatencyAggregationTypeLabel(latencyAggregationType?: string) { switch (latencyAggregationType) { @@ -50,11 +50,11 @@ function getLatencyAggregationTypeLabel(latencyAggregationType?: string) { export function getColumns({ serviceName, latencyAggregationType, - transactionsMetricsData, + transactionGroupsAggResults, }: { serviceName: string; latencyAggregationType?: string; - transactionsMetricsData?: TransactionGroupMetrics; + transactionGroupsAggResults?: TransactionGroupMetrics; }): Array> { return [ { @@ -88,8 +88,8 @@ export function getColumns({ name: getLatencyAggregationTypeLabel(latencyAggregationType), width: px(unit * 10), render: (_, { latency, name }) => { - const timeseries = transactionsMetricsData - ? transactionsMetricsData[name]?.latency + const timeseries = transactionGroupsAggResults + ? transactionGroupsAggResults[name]?.latency : undefined; return ( { - const timeseries = transactionsMetricsData - ? transactionsMetricsData[name]?.throughput + const timeseries = transactionGroupsAggResults + ? transactionGroupsAggResults[name]?.throughput : undefined; return ( { - const timeseries = transactionsMetricsData - ? transactionsMetricsData[name]?.errorRate + const timeseries = transactionGroupsAggResults + ? transactionGroupsAggResults[name]?.errorRate : undefined; return ( { - return ; + render: (_, { name }) => { + const impact = transactionGroupsAggResults + ? transactionGroupsAggResults[name]?.impact + : 0; + return ; }, }, ]; 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 72fe8e8cff4c9..29c46e0341471 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 @@ -12,7 +12,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isEmpty, orderBy } from 'lodash'; -import React, { useMemo, useState } from 'react'; +import React, { useState } from 'react'; import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; @@ -94,23 +94,22 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { ]); const { transactionGroups, requestId } = data; - const sortedSlicedItems = orderBy( + const currentPageTransactionGroups = orderBy( transactionGroups, sort.field, sort.direction ).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE); - const transactionNames = useMemo( - () => sortedSlicedItems.map(({ name }) => name).join(), - [sortedSlicedItems] - ); + const transactionNames = currentPageTransactionGroups + .map(({ name }) => name) + .join(); const { - data: transactionsMetricsData, - status: transactionsMetricsStatus, + data: transactionGroupsAggResults, + status: transactionGroupsAggResultsStatus, } = useFetcher( () => { - async function fetchMetrics() { + async function fetchAggResults() { if ( !isEmpty(requestId) && transactionNames && @@ -120,7 +119,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { ) { const metrics = await callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/metrics', + 'GET /api/apm/services/{serviceName}/transactions/groups/agg_results', params: { path: { serviceName }, query: { @@ -138,7 +137,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { return { [requestId]: metrics }; } } - return fetchMetrics(); + return fetchAggResults(); }, // only fetches metrics when requestId changes or transaction names changes // eslint-disable-next-line react-hooks/exhaustive-deps @@ -148,14 +147,14 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { const columns = getColumns({ serviceName, latencyAggregationType, - transactionsMetricsData: transactionsMetricsData - ? transactionsMetricsData[requestId] + transactionGroupsAggResults: transactionGroupsAggResults + ? transactionGroupsAggResults[requestId] : undefined, }); const isLoading = status === FETCH_STATUS.LOADING || - transactionsMetricsStatus === FETCH_STATUS.LOADING; + transactionGroupsAggResultsStatus === FETCH_STATUS.LOADING; const pagination = { pageIndex, @@ -210,7 +209,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { > > { + const { apmEventClient, start, end, esFilter } = setup; + const deltaAsMinutes = (end - start) / 1000 / 60; + + const field = getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ); + + const aggResults = { + ...getLatencyAggregation(latencyAggregationType, field), + transaction_count: { value_count: { field } }, + [EVENT_OUTCOME]: { + filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } }, + aggs: { transaction_count: { value_count: { field } } }, + }, + }; + + const response = await apmEventClient.search({ + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + { range: rangeFilter(start, end) }, + ...esFilter, + ], + }, + }, + aggs: { + transaction_groups: { + terms: { + field: TRANSACTION_NAME, + size: 500, + }, + aggs: aggResults, + }, + }, + }, + }); + + const buckets = response.aggregations?.transaction_groups.buckets ?? []; + + const totalDurationValues = buckets.map((group) => { + const latency = + getLatencyValue({ + latencyAggregationType, + aggregation: group.latency, + }) ?? 0; + const throughput = group.transaction_count.value / deltaAsMinutes; + return latency * throughput; + }); + const minTotalDuration = Math.min(...totalDurationValues); + const maxTotalDuration = Math.max(...totalDurationValues); + + return buckets + .filter((bucket) => transactionNames.includes(bucket.key as string)) + .reduce((acc, bucket) => { + const throughput = bucket.transaction_count.value / deltaAsMinutes; + const latency = + getLatencyValue({ + latencyAggregationType, + aggregation: bucket.latency, + }) ?? 0; + + const impact = + ((latency * throughput - minTotalDuration) / + (maxTotalDuration - minTotalDuration)) * + 100; + + return { ...acc, [bucket.key]: impact }; + }, {}); +} + +export async function getServiceTransactionGroupsAggResults({ serviceName, transactionNames, setup, @@ -48,6 +144,7 @@ export async function getServiceTransactionGroupsMetrics({ latency: Coordinate[]; throughput: Coordinate[]; errorRate: Coordinate[]; + impact: number; } > > { @@ -113,6 +210,15 @@ export async function getServiceTransactionGroupsMetrics({ }, }); + const transactionGroupsImpact = await getTransacionGroupsImpact({ + serviceName, + transactionNames, + setup, + searchAggregatedTransactions, + transactionType, + latencyAggregationType, + }); + const buckets = response.aggregations?.transaction_groups.buckets ?? []; return buckets.reduce((acc, bucket) => { @@ -148,7 +254,12 @@ export async function getServiceTransactionGroupsMetrics({ return { ...acc, - [transactionName]: { latency, throughput, errorRate }, + [transactionName]: { + latency, + throughput, + errorRate, + impact: transactionGroupsImpact[transactionName], + }, }; }, {}); } diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index d4de99865ed33..898fdc946d5c2 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -62,7 +62,7 @@ import { transactionGroupsOverviewRoute, transactionLatencyChatsRoute, transactionThroughputChatsRoute, - transactionGroupsMetricsRoute, + transactionGroupsAggResultsRoute, } from './transactions'; import { errorGroupsLocalFiltersRoute, @@ -173,7 +173,7 @@ const createApmApi = () => { .add(transactionGroupsOverviewRoute) .add(transactionLatencyChatsRoute) .add(transactionThroughputChatsRoute) - .add(transactionGroupsMetricsRoute) + .add(transactionGroupsAggResultsRoute) // UI filters .add(errorGroupsLocalFiltersRoute) diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index db259c1ca7656..b22713e9b5bc5 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -23,7 +23,7 @@ import { LatencyAggregationType, latencyAggregationTypeRt, } from '../../common/latency_aggregation_types'; -import { getServiceTransactionGroupsMetrics } from '../lib/services/get_service_transaction_groups_metrics'; +import { getServiceTransactionGroupsAggResults } from '../lib/services/get_service_transaction_groups_agg_results'; /** * Returns a list of transactions grouped by name @@ -101,8 +101,9 @@ export const transactionGroupsOverviewRoute = createRoute({ }, }); -export const transactionGroupsMetricsRoute = createRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/transactions/groups/metrics', +export const transactionGroupsAggResultsRoute = createRoute({ + endpoint: + 'GET /api/apm/services/{serviceName}/transactions/groups/agg_results', params: t.type({ path: t.type({ serviceName: t.string }), query: t.intersection([ @@ -136,7 +137,7 @@ export const transactionGroupsMetricsRoute = createRoute({ }, } = context.params; - return getServiceTransactionGroupsMetrics({ + return getServiceTransactionGroupsAggResults({ setup, serviceName, transactionNames: transactionNames.split(','), diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_metrics.ts b/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_metrics.ts deleted file mode 100644 index 87aa24d2aea67..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_metrics.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import url from 'url'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - const { start, end } = archives[archiveName]; - - const transactionNames = [ - 'DispatcherServlet#doGet', - 'APIRestController#customers', - 'APIRestController#order', - 'APIRestController#stats', - 'APIRestController#customerWhoBought', - 'APIRestController#customer', - 'APIRestController#topProducts', - 'APIRestController#orders', - 'APIRestController#product', - 'ResourceHttpRequestHandler', - 'APIRestController#products', - 'DispatcherServlet#doPost', - ].join(); - - describe('Transactions groups Metrics', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/metrics`, - query: { - start, - end, - uiFilters: '{}', - numBuckets: 20, - latencyAggregationType: 'avg', - transactionType: 'request', - transactionNames, - }, - }) - ); - - expect(response.status).to.be(200); - expect(response.body).to.eql({}); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the correct data', async () => { - const response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/metrics`, - query: { - start, - end, - uiFilters: '{}', - numBuckets: 20, - latencyAggregationType: 'avg', - transactionType: 'request', - transactionNames, - }, - }) - ); - - expect(response.status).to.be(200); - - expectSnapshot(Object.keys(response.body)).toMatchInline(` - Array [ - "DispatcherServlet#doGet", - "APIRestController#customerWhoBought", - "APIRestController#order", - "APIRestController#customer", - "ResourceHttpRequestHandler", - "APIRestController#customers", - "APIRestController#stats", - "APIRestController#topProducts", - "APIRestController#orders", - "APIRestController#product", - "APIRestController#products", - "DispatcherServlet#doPost", - ] - `); - - const firstItem = response.body['DispatcherServlet#doGet']; - - expectSnapshot(firstItem.latency.filter(({ y }: any) => y > 0).length).toMatchInline(`9`); - - expectSnapshot(firstItem.throughput.filter(({ y }: any) => y > 0).length).toMatchInline( - `9` - ); - - expectSnapshot(firstItem.errorRate.filter(({ y }: any) => y > 0).length).toMatchInline(`1`); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts index 1cbcb63bbb01b..ed6828a06e830 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; -import { pick, sortBy } from 'lodash'; +import { pick } from 'lodash'; import url from 'url'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import archives from '../../common/fixtures/es_archiver/archives_metadata'; @@ -36,10 +36,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); expect(response.status).to.be(200); - expect(response.body).to.eql({ - transactionGroups: [], - isAggregationAccurate: true, - }); + expect(response.body.transactionGroups).to.empty(); + expect(response.body.isAggregationAccurate).to.be(true); + expect(response.body.requestId).to.not.empty(); }); } ); @@ -113,48 +112,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { } `); }); - - it('sorts items in the correct order', async () => { - const descendingResponse = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/overview`, - query: { - start, - end, - uiFilters: '{}', - transactionType: 'request', - latencyAggregationType: 'avg', - }, - }) - ); - - expect(descendingResponse.status).to.be(200); - - const descendingOccurrences = descendingResponse.body.transactionGroups.map( - (item: any) => item.impact - ); - - expect(descendingOccurrences).to.eql(sortBy(descendingOccurrences.concat()).reverse()); - - const ascendingResponse = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/overview`, - query: { - start, - end, - uiFilters: '{}', - transactionType: 'request', - latencyAggregationType: 'avg', - }, - }) - ); - - const ascendingOccurrences = ascendingResponse.body.transactionGroups.map( - (item: any) => item.impact - ); - - expect(ascendingOccurrences).to.eql(sortBy(ascendingOccurrences.concat()).reverse()); - }); } ); } From ad4f99d2fbb1a8f07325c30aa5888de11c3e3191 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 1 Feb 2021 13:57:29 +0100 Subject: [PATCH 11/31] adding API test --- .../test/apm_api_integration/tests/index.ts | 1 + .../transactions_groups_agg_results.ts | 299 ++++++++++++++++++ 2 files changed, 300 insertions(+) create mode 100644 x-pack/test/apm_api_integration/tests/transactions/transactions_groups_agg_results.ts diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index eef82c714b2d0..c33abe7200d93 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -62,6 +62,7 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte loadTestFile(require.resolve('./transactions/throughput')); loadTestFile(require.resolve('./transactions/top_transaction_groups')); loadTestFile(require.resolve('./transactions/transactions_groups_overview')); + loadTestFile(require.resolve('./transactions/transactions_groups_agg_results')); loadTestFile(require.resolve('./feature_controls')); diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_agg_results.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_agg_results.ts new file mode 100644 index 0000000000000..15d2872e28427 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_agg_results.ts @@ -0,0 +1,299 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import url from 'url'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + const archiveName = 'apm_8.0.0'; + const { start, end } = archives[archiveName]; + + registry.when( + 'Transaction groups agg results 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-java/transactions/groups/agg_results`, + query: { + start, + end, + uiFilters: '{}', + numBuckets: 20, + latencyAggregationType: 'avg', + transactionType: 'request', + transactionNames: 'DispatcherServlet#doGet,APIRestController#customers', + }, + }) + ); + + expect(response.status).to.be(200); + expect(response.body).to.empty(); + }); + } + ); + + registry.when( + 'Transaction groups agg results when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + it('returns the correct data', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/transactions/groups/agg_results`, + query: { + start, + end, + uiFilters: '{}', + numBuckets: 20, + transactionType: 'request', + latencyAggregationType: 'avg', + transactionNames: 'DispatcherServlet#doGet,APIRestController#customers', + }, + }) + ); + + expect(response.status).to.be(200); + + expectSnapshot(Object.keys(response.body)).toMatchInline(` + Array [ + "DispatcherServlet#doGet", + "APIRestController#customers", + ] + `); + + expectSnapshot(Object.values(response.body).map((group: any) => group.impact)) + .toMatchInline(` + Array [ + 100, + 1.43059146953109, + ] + `); + + const item = response.body['DispatcherServlet#doGet']; + function removeEmptyCoordinates(coordinates: Array<{ x: number; y?: number }>) { + return coordinates.filter(({ y }) => y !== null && y !== undefined); + } + expectSnapshot(removeEmptyCoordinates(item.latency)).toMatchInline(` + Array [ + Object { + "x": 1607435880000, + "y": 69429, + }, + Object { + "x": 1607435940000, + "y": 8071285, + }, + Object { + "x": 1607436000000, + "y": 31949, + }, + Object { + "x": 1607436120000, + "y": 47755, + }, + Object { + "x": 1607436240000, + "y": 35403, + }, + Object { + "x": 1607436480000, + "y": 48137, + }, + Object { + "x": 1607436600000, + "y": 35457, + }, + Object { + "x": 1607436960000, + "y": 30501, + }, + Object { + "x": 1607437200000, + "y": 46937.5, + }, + ] + `); + + expectSnapshot(removeEmptyCoordinates(item.throughput)).toMatchInline(` + Array [ + Object { + "x": 1607435820000, + "y": 0, + }, + Object { + "x": 1607435880000, + "y": 0.0333333333333333, + }, + Object { + "x": 1607435940000, + "y": 0.0666666666666667, + }, + Object { + "x": 1607436000000, + "y": 0.0333333333333333, + }, + Object { + "x": 1607436060000, + "y": 0, + }, + Object { + "x": 1607436120000, + "y": 0.0333333333333333, + }, + Object { + "x": 1607436180000, + "y": 0, + }, + Object { + "x": 1607436240000, + "y": 0.133333333333333, + }, + Object { + "x": 1607436300000, + "y": 0, + }, + Object { + "x": 1607436360000, + "y": 0, + }, + Object { + "x": 1607436420000, + "y": 0, + }, + Object { + "x": 1607436480000, + "y": 0.0666666666666667, + }, + Object { + "x": 1607436540000, + "y": 0, + }, + Object { + "x": 1607436600000, + "y": 0.0333333333333333, + }, + Object { + "x": 1607436660000, + "y": 0, + }, + Object { + "x": 1607436720000, + "y": 0, + }, + Object { + "x": 1607436780000, + "y": 0, + }, + Object { + "x": 1607436840000, + "y": 0, + }, + Object { + "x": 1607436900000, + "y": 0, + }, + Object { + "x": 1607436960000, + "y": 0.0666666666666667, + }, + Object { + "x": 1607437020000, + "y": 0, + }, + Object { + "x": 1607437080000, + "y": 0, + }, + Object { + "x": 1607437140000, + "y": 0, + }, + Object { + "x": 1607437200000, + "y": 0.0666666666666667, + }, + Object { + "x": 1607437260000, + "y": 0, + }, + Object { + "x": 1607437320000, + "y": 0, + }, + Object { + "x": 1607437380000, + "y": 0, + }, + Object { + "x": 1607437440000, + "y": 0, + }, + Object { + "x": 1607437500000, + "y": 0, + }, + Object { + "x": 1607437560000, + "y": 0, + }, + Object { + "x": 1607437620000, + "y": 0, + }, + ] + `); + + expectSnapshot(removeEmptyCoordinates(item.errorRate)).toMatchInline(` + Array [ + Object { + "x": 1607435880000, + "y": 0, + }, + Object { + "x": 1607435940000, + "y": 0, + }, + Object { + "x": 1607436000000, + "y": 0, + }, + Object { + "x": 1607436120000, + "y": 0, + }, + Object { + "x": 1607436240000, + "y": 0, + }, + Object { + "x": 1607436480000, + "y": 0, + }, + Object { + "x": 1607436600000, + "y": 0, + }, + Object { + "x": 1607436960000, + "y": 0, + }, + Object { + "x": 1607437200000, + "y": 0.5, + }, + ] + `); + }); + } + ); +} From bf9647f483962f47a4bb9d74bdc74425a4f950ce Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 1 Feb 2021 15:34:16 +0100 Subject: [PATCH 12/31] changing how to calculate impact --- .../index.tsx | 6 +- .../get_service_transaction_groups.ts | 33 ++-- ..._service_transaction_groups_agg_results.ts | 167 +++++------------- .../plugins/apm/server/routes/transactions.ts | 2 +- .../transactions_groups_agg_results.ts | 14 +- .../transactions_groups_overview.ts | 34 ++-- 6 files changed, 87 insertions(+), 169 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 29c46e0341471..653017febe3d8 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 @@ -100,9 +100,9 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { sort.direction ).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE); - const transactionNames = currentPageTransactionGroups - .map(({ name }) => name) - .join(); + const transactionNames = JSON.stringify( + currentPageTransactionGroups.map(({ name }) => name) + ); const { data: transactionGroupsAggResults, 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 dcebd45b209a3..6b6011634b4b9 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 @@ -9,6 +9,7 @@ import uuid from 'uuid'; import { EVENT_OUTCOME, SERVICE_NAME, + TRANSACTION_DURATION, TRANSACTION_NAME, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; @@ -66,6 +67,7 @@ export async function getServiceTransactionGroups({ }, }, aggs: { + total_duration: { sum: { field: TRANSACTION_DURATION } }, transaction_groups: { terms: { field: TRANSACTION_NAME, @@ -73,6 +75,9 @@ export async function getServiceTransactionGroups({ order: { _count: 'desc' }, }, aggs: { + transaction_group_total_duration: { + sum: { field: TRANSACTION_DURATION }, + }, ...getLatencyAggregation(latencyAggregationType, field), transaction_count: { value_count: { field } }, [EVENT_OUTCOME]: { @@ -85,6 +90,8 @@ export async function getServiceTransactionGroups({ }, }); + const totalDuration = response.aggregations?.total_duration.value; + const transactionGroups = response.aggregations?.transaction_groups.buckets.map((bucket) => { const errorRate = @@ -93,6 +100,9 @@ export async function getServiceTransactionGroups({ bucket.transaction_count.value : null; + const transactionGroupTotalDuration = + bucket.transaction_group_total_duration.value || 0; + return { name: bucket.key as string, latency: getLatencyValue({ @@ -101,29 +111,14 @@ export async function getServiceTransactionGroups({ }), throughput: bucket.transaction_count.value / deltaAsMinutes, errorRate, + impact: totalDuration + ? (transactionGroupTotalDuration * 100) / totalDuration + : 0, }; }) ?? []; - const totalDurationValues = transactionGroups.map( - (group) => (group.latency ?? 0) * group.throughput - ); - - const minTotalDuration = Math.min(...totalDurationValues); - const maxTotalDuration = Math.max(...totalDurationValues); - - const transactionGroupsWithImpact = transactionGroups.map((group) => ({ - ...group, - impact: - (((group.latency ?? 0) * group.throughput - minTotalDuration) / - (maxTotalDuration - minTotalDuration)) * - 100, - })); - // By default sorts transactions by impact - const sortedTransactionGroups = sortBy( - transactionGroupsWithImpact, - 'impact' - ).reverse(); + const sortedTransactionGroups = sortBy(transactionGroups, 'impact').reverse(); return { requestId: uuid(), diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts index fddc2385c6d79..4f49e45482543 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts @@ -7,6 +7,7 @@ import { EVENT_OUTCOME, SERVICE_NAME, + TRANSACTION_DURATION, TRANSACTION_NAME, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; @@ -25,102 +26,6 @@ import { } from '../helpers/latency_aggregation_type'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; -async function getTransacionGroupsImpact({ - serviceName, - transactionNames, - setup, - searchAggregatedTransactions, - transactionType, - latencyAggregationType, -}: { - serviceName: string; - transactionNames: string[]; - setup: Setup & SetupTimeRange; - searchAggregatedTransactions: boolean; - transactionType: string; - latencyAggregationType: LatencyAggregationType; -}): Promise> { - const { apmEventClient, start, end, esFilter } = setup; - const deltaAsMinutes = (end - start) / 1000 / 60; - - const field = getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ); - - const aggResults = { - ...getLatencyAggregation(latencyAggregationType, field), - transaction_count: { value_count: { field } }, - [EVENT_OUTCOME]: { - filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } }, - aggs: { transaction_count: { value_count: { field } } }, - }, - }; - - const response = await apmEventClient.search({ - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, - ...esFilter, - ], - }, - }, - aggs: { - transaction_groups: { - terms: { - field: TRANSACTION_NAME, - size: 500, - }, - aggs: aggResults, - }, - }, - }, - }); - - const buckets = response.aggregations?.transaction_groups.buckets ?? []; - - const totalDurationValues = buckets.map((group) => { - const latency = - getLatencyValue({ - latencyAggregationType, - aggregation: group.latency, - }) ?? 0; - const throughput = group.transaction_count.value / deltaAsMinutes; - return latency * throughput; - }); - const minTotalDuration = Math.min(...totalDurationValues); - const maxTotalDuration = Math.max(...totalDurationValues); - - return buckets - .filter((bucket) => transactionNames.includes(bucket.key as string)) - .reduce((acc, bucket) => { - const throughput = bucket.transaction_count.value / deltaAsMinutes; - const latency = - getLatencyValue({ - latencyAggregationType, - aggregation: bucket.latency, - }) ?? 0; - - const impact = - ((latency * throughput - minTotalDuration) / - (maxTotalDuration - minTotalDuration)) * - 100; - - return { ...acc, [bucket.key]: impact }; - }, {}); -} - export async function getServiceTransactionGroupsAggResults({ serviceName, transactionNames, @@ -170,7 +75,6 @@ export async function getServiceTransactionGroupsAggResults({ query: { bool: { filter: [ - { terms: { [TRANSACTION_NAME]: transactionNames } }, { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, { range: rangeFilter(start, end) }, @@ -179,28 +83,39 @@ export async function getServiceTransactionGroupsAggResults({ }, }, aggs: { - transaction_groups: { - terms: { - field: TRANSACTION_NAME, - size: transactionNames.length, - }, + total_duration: { sum: { field: TRANSACTION_DURATION } }, + transaction_name_filter: { + filter: { terms: { [TRANSACTION_NAME]: transactionNames } }, aggs: { - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, - min_doc_count: 0, - extended_bounds: { - min: start, - max: end, - }, + transaction_groups: { + terms: { + field: TRANSACTION_NAME, + size: transactionNames.length, }, aggs: { - ...getLatencyAggregation(latencyAggregationType, field), - transaction_count: { value_count: { field } }, - [EVENT_OUTCOME]: { - filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } }, - aggs: { transaction_count: { value_count: { field } } }, + transaction_group_total_duration: { + sum: { field: TRANSACTION_DURATION }, + }, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { + min: start, + max: end, + }, + }, + aggs: { + ...getLatencyAggregation(latencyAggregationType, field), + transaction_count: { value_count: { field } }, + [EVENT_OUTCOME]: { + filter: { + term: { [EVENT_OUTCOME]: EventOutcome.failure }, + }, + aggs: { transaction_count: { value_count: { field } } }, + }, + }, }, }, }, @@ -210,16 +125,11 @@ export async function getServiceTransactionGroupsAggResults({ }, }); - const transactionGroupsImpact = await getTransacionGroupsImpact({ - serviceName, - transactionNames, - setup, - searchAggregatedTransactions, - transactionType, - latencyAggregationType, - }); + const buckets = + response.aggregations?.transaction_name_filter.transaction_groups.buckets ?? + []; - const buckets = response.aggregations?.transaction_groups.buckets ?? []; + const totalDuration = response.aggregations?.total_duration.value; return buckets.reduce((acc, bucket) => { const transactionName = bucket.key; @@ -252,13 +162,18 @@ export async function getServiceTransactionGroupsAggResults({ }) ); + const transactionGroupTotalDuration = + bucket.transaction_group_total_duration.value || 0; + return { ...acc, [transactionName]: { latency, throughput, errorRate, - impact: transactionGroupsImpact[transactionName], + impact: totalDuration + ? (transactionGroupTotalDuration * 100) / totalDuration + : 0, }, }; }, {}); diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index b22713e9b5bc5..0347416470930 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -140,7 +140,7 @@ export const transactionGroupsAggResultsRoute = createRoute({ return getServiceTransactionGroupsAggResults({ setup, serviceName, - transactionNames: transactionNames.split(','), + transactionNames: JSON.parse(transactionNames), searchAggregatedTransactions, transactionType, numBuckets, diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_agg_results.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_agg_results.ts index 15d2872e28427..ce379c8fa20c6 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_agg_results.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_agg_results.ts @@ -31,7 +31,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { numBuckets: 20, latencyAggregationType: 'avg', transactionType: 'request', - transactionNames: 'DispatcherServlet#doGet,APIRestController#customers', + transactionNames: JSON.stringify([ + 'DispatcherServlet#doGet', + 'APIRestController#customers', + ]), }, }) ); @@ -57,7 +60,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { numBuckets: 20, transactionType: 'request', latencyAggregationType: 'avg', - transactionNames: 'DispatcherServlet#doGet,APIRestController#customers', + transactionNames: JSON.stringify([ + 'DispatcherServlet#doGet', + 'APIRestController#customers', + ]), }, }) ); @@ -74,8 +80,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(Object.values(response.body).map((group: any) => group.impact)) .toMatchInline(` Array [ - 100, - 1.43059146953109, + 93.9295870910491, + 1.35334507158962, ] `); diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts index ed6828a06e830..d5307178cdcb5 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; -import { pick } from 'lodash'; +import { pick, sum } from 'lodash'; import url from 'url'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import archives from '../../common/fixtures/es_archiver/archives_metadata'; @@ -81,31 +81,33 @@ export default function ApiTest({ getService }: FtrProviderContext) { ] `); - expectSnapshot(response.body.transactionGroups.map((group: any) => group.impact)) - .toMatchInline(` + const impacts = response.body.transactionGroups.map((group: any) => group.impact); + expectSnapshot(impacts).toMatchInline(` Array [ - 100, - 1.43059146953109, - 0.953769516915408, - 0.905498741191481, - 0.894989230293471, - 0.734894148230161, - 0.496596820588832, - 0.465199881087606, - 0.269203783423923, - 0.142856373806016, - 0.0557715877137418, - 0, + 93.9295870910491, + 1.35334507158962, + 0.905514602241759, + 0.860178761411346, + 0.850308244392878, + 0.699947181217412, + 0.476138685202191, + 0.446650726277923, + 0.262571482598846, + 0.143906183235671, + 0.062116281544223, + 0.00973568923904662, ] `); + expect(sum(impacts)).to.eql(100); + const firstItem = response.body.transactionGroups[0]; expectSnapshot(pick(firstItem, 'name', 'latency', 'throughput', 'errorRate', 'impact')) .toMatchInline(` Object { "errorRate": 0.0625, - "impact": 100, + "impact": 93.9295870910491, "latency": 1044995.1875, "name": "DispatcherServlet#doGet", "throughput": 0.533333333333333, From ca17de758e5fbe79ed0b42df3409c4c38398b997 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 1 Feb 2021 15:58:53 +0100 Subject: [PATCH 13/31] fixing conflicts --- .../lib/services/get_service_transaction_groups_agg_results.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts index a1c8df9f12df3..d4a3ee431f713 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts @@ -158,7 +158,8 @@ export async function getServiceTransactionGroupsAggResults({ x: timeseriesBucket.key, y: timeseriesBucket.doc_count > 0 - ? timeseriesBucket[EVENT_OUTCOME].doc_count / bucket.doc_count + ? timeseriesBucket[EVENT_OUTCOME].doc_count / + timeseriesBucket.doc_count : null, }) ); From 502f709a8a9394e062389e32bf0a08f441f42caf Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 1 Feb 2021 16:11:46 +0100 Subject: [PATCH 14/31] caching result --- .../service_overview_transactions_table/index.tsx | 1 + 1 file changed, 1 insertion(+) 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 653017febe3d8..22d80a82c6b89 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 @@ -132,6 +132,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { transactionNames, }, }, + isCachable: true, }); return { [requestId]: metrics }; From fa2ad695adc3a62f2c0ef761af0166cf1de0ab2f Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 3 Feb 2021 14:31:16 +0100 Subject: [PATCH 15/31] addressing PR comments --- .../get_columns.tsx | 27 +- .../index.tsx | 136 ++++---- .../__snapshots__/ManagedTable.test.js.snap | 2 - .../components/shared/ManagedTable/index.tsx | 3 - .../get_service_transaction_groups.ts | 2 - ..._service_transaction_groups_agg_results.ts | 96 +++--- .../apm/server/routes/create_apm_api.ts | 4 +- .../plugins/apm/server/routes/transactions.ts | 29 +- .../test/apm_api_integration/tests/index.ts | 2 +- .../transactions_groups_agg_results.ts | 305 ------------------ .../transactions_groups_overview.ts | 1 - .../transactions_groups_statistics.ts | 134 ++++++++ 12 files changed, 276 insertions(+), 465 deletions(-) delete mode 100644 x-pack/test/apm_api_integration/tests/transactions/transactions_groups_agg_results.ts create mode 100644 x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx index 06268b9652c96..a3b74ee1e5393 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx @@ -18,12 +18,13 @@ import { SparkPlot } from '../../../shared/charts/spark_plot'; import { ImpactBar } from '../../../shared/ImpactBar'; import { TransactionDetailLink } from '../../../shared/Links/apm/transaction_detail_link'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; -import { TransactionGroupsOverview } from './'; -export type ServiceTransactionGroupItem = ValuesType< +type TransactionGroupsOverview = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/overview'>; + +type ServiceTransactionGroupItem = ValuesType< TransactionGroupsOverview['transactionGroups'] >; -type TransactionGroupMetrics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/agg_results'>; +type TransactionGroupsStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/statistics'>; function getLatencyAggregationTypeLabel(latencyAggregationType?: string) { switch (latencyAggregationType) { @@ -50,11 +51,11 @@ function getLatencyAggregationTypeLabel(latencyAggregationType?: string) { export function getColumns({ serviceName, latencyAggregationType, - transactionGroupsAggResults, + transactionGroupsStatistics, }: { serviceName: string; latencyAggregationType?: string; - transactionGroupsAggResults?: TransactionGroupMetrics; + transactionGroupsStatistics?: TransactionGroupsStatistics; }): Array> { return [ { @@ -88,8 +89,8 @@ export function getColumns({ name: getLatencyAggregationTypeLabel(latencyAggregationType), width: px(unit * 10), render: (_, { latency, name }) => { - const timeseries = transactionGroupsAggResults - ? transactionGroupsAggResults[name]?.latency + const timeseries = transactionGroupsStatistics + ? transactionGroupsStatistics[name]?.latency : undefined; return ( { - const timeseries = transactionGroupsAggResults - ? transactionGroupsAggResults[name]?.throughput + const timeseries = transactionGroupsStatistics + ? transactionGroupsStatistics[name]?.throughput : undefined; return ( { - const timeseries = transactionGroupsAggResults - ? transactionGroupsAggResults[name]?.errorRate + const timeseries = transactionGroupsStatistics + ? transactionGroupsStatistics[name]?.errorRate : undefined; return ( { - const impact = transactionGroupsAggResults - ? transactionGroupsAggResults[name]?.impact + const impact = transactionGroupsStatistics + ? transactionGroupsStatistics[name]?.impact : 0; return ; }, 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 22d80a82c6b89..dff0036d7d9e7 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 @@ -13,26 +13,21 @@ import { import { i18n } from '@kbn/i18n'; import { isEmpty, orderBy } from 'lodash'; import React, { useState } from 'react'; +import uuid from 'uuid'; import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; 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 { - APIReturnType, - callApmApi, -} from '../../../../services/rest/createCallApmApi'; import { TransactionOverviewLink } from '../../../shared/Links/apm/transaction_overview_link'; import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; import { getColumns } from './get_columns'; -export type TransactionGroupsOverview = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/overview'>; - interface Props { serviceName: string; } -const INITIAL_STATE: TransactionGroupsOverview = { +const INITIAL_STATE = { transactionGroups: [], isAggregationAccurate: true, requestId: '', @@ -66,32 +61,40 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { urlParams: { start, end, latencyAggregationType }, } = useUrlParams(); - const { data = INITIAL_STATE, status } = useFetcher(() => { - if (!start || !end || !latencyAggregationType || !transactionType) { - return; - } - return callApmApi({ - endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/overview', - params: { - path: { serviceName }, - query: { - start, - end, - uiFilters: JSON.stringify(uiFilters), - transactionType, - latencyAggregationType: latencyAggregationType as LatencyAggregationType, + const { data = INITIAL_STATE, status } = useFetcher( + (callApmApi) => { + if (!start || !end || !latencyAggregationType || !transactionType) { + return; + } + return callApmApi({ + endpoint: + 'GET /api/apm/services/{serviceName}/transactions/groups/overview', + params: { + path: { serviceName }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + transactionType, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, + }, }, - }, - }); - }, [ - serviceName, - start, - end, - uiFilters, - transactionType, - latencyAggregationType, - ]); + }).then((response) => { + return { + requestId: uuid(), + ...response, + }; + }); + }, + [ + serviceName, + start, + end, + uiFilters, + transactionType, + latencyAggregationType, + ] + ); const { transactionGroups, requestId } = data; const currentPageTransactionGroups = orderBy( @@ -101,46 +104,43 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { ).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE); const transactionNames = JSON.stringify( - currentPageTransactionGroups.map(({ name }) => name) + currentPageTransactionGroups.map(({ name }) => name).sort() ); const { - data: transactionGroupsAggResults, - status: transactionGroupsAggResultsStatus, + data: transactionGroupsStatistics, + status: transactionGroupsStatisticsStatus, } = useFetcher( - () => { - async function fetchAggResults() { - if ( - !isEmpty(requestId) && - transactionNames && - start && - end && - transactionType - ) { - const metrics = await callApmApi({ - endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/agg_results', - params: { - path: { serviceName }, - query: { - start, - end, - uiFilters: JSON.stringify(uiFilters), - numBuckets: 20, - transactionType, - latencyAggregationType: latencyAggregationType as LatencyAggregationType, - transactionNames, - }, + (callApmApi) => { + if ( + !isEmpty(requestId) && + currentPageTransactionGroups.length && + start && + end && + transactionType + ) { + return callApmApi({ + endpoint: + 'GET /api/apm/services/{serviceName}/transactions/groups/statistics', + params: { + path: { serviceName }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + numBuckets: 20, + transactionType, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, + transactionNames, }, - isCachable: true, - }); - - return { [requestId]: metrics }; - } + }, + isCachable: true, + }).then((result) => { + return { [requestId]: result }; + }); } - return fetchAggResults(); }, - // only fetches metrics when requestId changes or transaction names changes + // only fetches statistics when requestId changes or transaction names changes // eslint-disable-next-line react-hooks/exhaustive-deps [requestId, transactionNames] ); @@ -148,14 +148,14 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { const columns = getColumns({ serviceName, latencyAggregationType, - transactionGroupsAggResults: transactionGroupsAggResults - ? transactionGroupsAggResults[requestId] + transactionGroupsStatistics: transactionGroupsStatistics + ? transactionGroupsStatistics[requestId] : undefined, }); const isLoading = status === FETCH_STATUS.LOADING || - transactionGroupsAggResultsStatus === FETCH_STATUS.LOADING; + transactionGroupsStatisticsStatus === FETCH_STATUS.LOADING; const pagination = { pageIndex, diff --git a/x-pack/plugins/apm/public/components/shared/ManagedTable/__snapshots__/ManagedTable.test.js.snap b/x-pack/plugins/apm/public/components/shared/ManagedTable/__snapshots__/ManagedTable.test.js.snap index f077ca301d5f9..655fc5a25b9ef 100644 --- a/x-pack/plugins/apm/public/components/shared/ManagedTable/__snapshots__/ManagedTable.test.js.snap +++ b/x-pack/plugins/apm/public/components/shared/ManagedTable/__snapshots__/ManagedTable.test.js.snap @@ -33,7 +33,6 @@ exports[`ManagedTable component should render a page-full of items, with default }, ] } - loading={false} noItemsMessage="No items found" onChange={[Function]} pagination={ @@ -82,7 +81,6 @@ exports[`ManagedTable component should render when specifying initial values 1`] }, ] } - loading={false} noItemsMessage="No items found" onChange={[Function]} pagination={ diff --git a/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx b/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx index 6d462a20924ce..8033b6415319e 100644 --- a/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx @@ -39,7 +39,6 @@ interface Props { sortDirection: 'asc' | 'desc' ) => T[]; pagination?: boolean; - isLoading?: boolean; } function defaultSortFn( @@ -64,7 +63,6 @@ function UnoptimizedManagedTable(props: Props) { sortItems = true, sortFn = defaultSortFn, pagination = true, - isLoading = false, } = props; const { @@ -126,7 +124,6 @@ function UnoptimizedManagedTable(props: Props) { return ( >} // EuiBasicTableColumn is stricter than ITableColumn 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 09ce34830bc31..7857437bc0012 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 @@ -5,7 +5,6 @@ */ import { sortBy } from 'lodash'; -import uuid from 'uuid'; import { EVENT_OUTCOME, SERVICE_NAME, @@ -133,7 +132,6 @@ export async function getServiceTransactionGroups({ const sortedTransactionGroups = sortBy(transactionGroups, 'impact').reverse(); return { - requestId: uuid(), transactionGroups: sortedTransactionGroups.map((transactionGroup) => ({ ...transactionGroup, transactionType, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts index d4a3ee431f713..5e490f84210a4 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts @@ -88,35 +88,31 @@ export async function getServiceTransactionGroupsAggResults({ }, aggs: { total_duration: { sum: { field: TRANSACTION_DURATION } }, - transaction_name_filter: { - filter: { terms: { [TRANSACTION_NAME]: transactionNames } }, + transaction_groups: { + terms: { + field: TRANSACTION_NAME, + include: transactionNames, + size: transactionNames.length, + }, aggs: { - transaction_groups: { - terms: { - field: TRANSACTION_NAME, - size: transactionNames.length, + transaction_group_total_duration: { + sum: { field: TRANSACTION_DURATION }, + }, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { + min: start, + max: end, + }, }, aggs: { - transaction_group_total_duration: { - sum: { field: TRANSACTION_DURATION }, - }, - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, - min_doc_count: 0, - extended_bounds: { - min: start, - max: end, - }, - }, - aggs: { - ...getLatencyAggregation(latencyAggregationType, field), - [EVENT_OUTCOME]: { - filter: { - term: { [EVENT_OUTCOME]: EventOutcome.failure }, - }, - }, + ...getLatencyAggregation(latencyAggregationType, field), + [EVENT_OUTCOME]: { + filter: { + term: { [EVENT_OUTCOME]: EventOutcome.failure }, }, }, }, @@ -127,42 +123,34 @@ export async function getServiceTransactionGroupsAggResults({ }, }); - const buckets = - response.aggregations?.transaction_name_filter.transaction_groups.buckets ?? - []; + const buckets = response.aggregations?.transaction_groups.buckets ?? []; const totalDuration = response.aggregations?.total_duration.value; return buckets.reduce((acc, bucket) => { const transactionName = bucket.key; - const latency: Coordinate[] = bucket.timeseries.buckets.map( - (timeseriesBucket) => ({ - x: timeseriesBucket.key, - y: getLatencyValue({ - latencyAggregationType, - aggregation: timeseriesBucket.latency, - }), - }) - ); + const latency = bucket.timeseries.buckets.map((timeseriesBucket) => ({ + x: timeseriesBucket.key, + y: getLatencyValue({ + latencyAggregationType, + aggregation: timeseriesBucket.latency, + }), + })); - const throughput: Coordinate[] = bucket.timeseries.buckets.map( - (timeseriesBucket) => ({ - x: timeseriesBucket.key, - y: timeseriesBucket.doc_count / deltaAsMinutes, - }) - ); + const throughput = bucket.timeseries.buckets.map((timeseriesBucket) => ({ + x: timeseriesBucket.key, + y: timeseriesBucket.doc_count / deltaAsMinutes, + })); - const errorRate: Coordinate[] = bucket.timeseries.buckets.map( - (timeseriesBucket) => ({ - x: timeseriesBucket.key, - y: - timeseriesBucket.doc_count > 0 - ? timeseriesBucket[EVENT_OUTCOME].doc_count / - timeseriesBucket.doc_count - : null, - }) - ); + const errorRate = bucket.timeseries.buckets.map((timeseriesBucket) => ({ + x: timeseriesBucket.key, + y: + timeseriesBucket.doc_count > 0 + ? timeseriesBucket[EVENT_OUTCOME].doc_count / + timeseriesBucket.doc_count + : null, + })); const transactionGroupTotalDuration = bucket.transaction_group_total_duration.value || 0; diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index 898fdc946d5c2..0f4047363fc42 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -62,7 +62,7 @@ import { transactionGroupsOverviewRoute, transactionLatencyChatsRoute, transactionThroughputChatsRoute, - transactionGroupsAggResultsRoute, + transactionGroupsStatisticsRoute, } from './transactions'; import { errorGroupsLocalFiltersRoute, @@ -173,7 +173,7 @@ const createApmApi = () => { .add(transactionGroupsOverviewRoute) .add(transactionLatencyChatsRoute) .add(transactionThroughputChatsRoute) - .add(transactionGroupsAggResultsRoute) + .add(transactionGroupsStatisticsRoute) // UI filters .add(errorGroupsLocalFiltersRoute) diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index 0347416470930..0c2a45632adfe 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -6,24 +6,25 @@ import Boom from '@hapi/boom'; import * as t from 'io-ts'; -import { createRoute } from './create_route'; -import { rangeRt, uiFiltersRt } from './default_api_types'; +import { + LatencyAggregationType, + latencyAggregationTypeRt, +} from '../../common/latency_aggregation_types'; +import { jsonRt } from '../../common/runtime_types/json_rt'; import { toNumberRt } from '../../common/runtime_types/to_number_rt'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; import { setupRequest } from '../lib/helpers/setup_request'; import { getServiceTransactionGroups } from '../lib/services/get_service_transaction_groups'; +import { getServiceTransactionGroupsAggResults } from '../lib/services/get_service_transaction_groups_agg_results'; import { getTransactionBreakdown } from '../lib/transactions/breakdown'; -import { getAnomalySeries } from '../lib/transactions/get_anomaly_data'; import { getTransactionDistribution } from '../lib/transactions/distribution'; -import { getTransactionGroupList } from '../lib/transaction_groups'; -import { getErrorRate } from '../lib/transaction_groups/get_error_rate'; +import { getAnomalySeries } from '../lib/transactions/get_anomaly_data'; import { getLatencyTimeseries } from '../lib/transactions/get_latency_charts'; import { getThroughputCharts } from '../lib/transactions/get_throughput_charts'; -import { - LatencyAggregationType, - latencyAggregationTypeRt, -} from '../../common/latency_aggregation_types'; -import { getServiceTransactionGroupsAggResults } from '../lib/services/get_service_transaction_groups_agg_results'; +import { getTransactionGroupList } from '../lib/transaction_groups'; +import { getErrorRate } from '../lib/transaction_groups/get_error_rate'; +import { createRoute } from './create_route'; +import { rangeRt, uiFiltersRt } from './default_api_types'; /** * Returns a list of transactions grouped by name @@ -101,16 +102,16 @@ export const transactionGroupsOverviewRoute = createRoute({ }, }); -export const transactionGroupsAggResultsRoute = createRoute({ +export const transactionGroupsStatisticsRoute = createRoute({ endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/agg_results', + 'GET /api/apm/services/{serviceName}/transactions/groups/statistics', params: t.type({ path: t.type({ serviceName: t.string }), query: t.intersection([ rangeRt, uiFiltersRt, t.type({ - transactionNames: t.string, + transactionNames: t.string.pipe(jsonRt), numBuckets: toNumberRt, transactionType: t.string, latencyAggregationType: latencyAggregationTypeRt, @@ -140,7 +141,7 @@ export const transactionGroupsAggResultsRoute = createRoute({ return getServiceTransactionGroupsAggResults({ setup, serviceName, - transactionNames: JSON.parse(transactionNames), + transactionNames, searchAggregatedTransactions, transactionType, numBuckets, diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index c33abe7200d93..266a430a1093e 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -62,7 +62,7 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte loadTestFile(require.resolve('./transactions/throughput')); loadTestFile(require.resolve('./transactions/top_transaction_groups')); loadTestFile(require.resolve('./transactions/transactions_groups_overview')); - loadTestFile(require.resolve('./transactions/transactions_groups_agg_results')); + loadTestFile(require.resolve('./transactions/transactions_groups_statistics')); loadTestFile(require.resolve('./feature_controls')); diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_agg_results.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_agg_results.ts deleted file mode 100644 index ce379c8fa20c6..0000000000000 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_agg_results.ts +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import url from 'url'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import archives from '../../common/fixtures/es_archiver/archives_metadata'; -import { registry } from '../../common/registry'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - const archiveName = 'apm_8.0.0'; - const { start, end } = archives[archiveName]; - - registry.when( - 'Transaction groups agg results 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-java/transactions/groups/agg_results`, - query: { - start, - end, - uiFilters: '{}', - numBuckets: 20, - latencyAggregationType: 'avg', - transactionType: 'request', - transactionNames: JSON.stringify([ - 'DispatcherServlet#doGet', - 'APIRestController#customers', - ]), - }, - }) - ); - - expect(response.status).to.be(200); - expect(response.body).to.empty(); - }); - } - ); - - registry.when( - 'Transaction groups agg results when data is loaded', - { config: 'basic', archives: [archiveName] }, - () => { - it('returns the correct data', async () => { - const response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/agg_results`, - query: { - start, - end, - uiFilters: '{}', - numBuckets: 20, - transactionType: 'request', - latencyAggregationType: 'avg', - transactionNames: JSON.stringify([ - 'DispatcherServlet#doGet', - 'APIRestController#customers', - ]), - }, - }) - ); - - expect(response.status).to.be(200); - - expectSnapshot(Object.keys(response.body)).toMatchInline(` - Array [ - "DispatcherServlet#doGet", - "APIRestController#customers", - ] - `); - - expectSnapshot(Object.values(response.body).map((group: any) => group.impact)) - .toMatchInline(` - Array [ - 93.9295870910491, - 1.35334507158962, - ] - `); - - const item = response.body['DispatcherServlet#doGet']; - function removeEmptyCoordinates(coordinates: Array<{ x: number; y?: number }>) { - return coordinates.filter(({ y }) => y !== null && y !== undefined); - } - expectSnapshot(removeEmptyCoordinates(item.latency)).toMatchInline(` - Array [ - Object { - "x": 1607435880000, - "y": 69429, - }, - Object { - "x": 1607435940000, - "y": 8071285, - }, - Object { - "x": 1607436000000, - "y": 31949, - }, - Object { - "x": 1607436120000, - "y": 47755, - }, - Object { - "x": 1607436240000, - "y": 35403, - }, - Object { - "x": 1607436480000, - "y": 48137, - }, - Object { - "x": 1607436600000, - "y": 35457, - }, - Object { - "x": 1607436960000, - "y": 30501, - }, - Object { - "x": 1607437200000, - "y": 46937.5, - }, - ] - `); - - expectSnapshot(removeEmptyCoordinates(item.throughput)).toMatchInline(` - Array [ - Object { - "x": 1607435820000, - "y": 0, - }, - Object { - "x": 1607435880000, - "y": 0.0333333333333333, - }, - Object { - "x": 1607435940000, - "y": 0.0666666666666667, - }, - Object { - "x": 1607436000000, - "y": 0.0333333333333333, - }, - Object { - "x": 1607436060000, - "y": 0, - }, - Object { - "x": 1607436120000, - "y": 0.0333333333333333, - }, - Object { - "x": 1607436180000, - "y": 0, - }, - Object { - "x": 1607436240000, - "y": 0.133333333333333, - }, - Object { - "x": 1607436300000, - "y": 0, - }, - Object { - "x": 1607436360000, - "y": 0, - }, - Object { - "x": 1607436420000, - "y": 0, - }, - Object { - "x": 1607436480000, - "y": 0.0666666666666667, - }, - Object { - "x": 1607436540000, - "y": 0, - }, - Object { - "x": 1607436600000, - "y": 0.0333333333333333, - }, - Object { - "x": 1607436660000, - "y": 0, - }, - Object { - "x": 1607436720000, - "y": 0, - }, - Object { - "x": 1607436780000, - "y": 0, - }, - Object { - "x": 1607436840000, - "y": 0, - }, - Object { - "x": 1607436900000, - "y": 0, - }, - Object { - "x": 1607436960000, - "y": 0.0666666666666667, - }, - Object { - "x": 1607437020000, - "y": 0, - }, - Object { - "x": 1607437080000, - "y": 0, - }, - Object { - "x": 1607437140000, - "y": 0, - }, - Object { - "x": 1607437200000, - "y": 0.0666666666666667, - }, - Object { - "x": 1607437260000, - "y": 0, - }, - Object { - "x": 1607437320000, - "y": 0, - }, - Object { - "x": 1607437380000, - "y": 0, - }, - Object { - "x": 1607437440000, - "y": 0, - }, - Object { - "x": 1607437500000, - "y": 0, - }, - Object { - "x": 1607437560000, - "y": 0, - }, - Object { - "x": 1607437620000, - "y": 0, - }, - ] - `); - - expectSnapshot(removeEmptyCoordinates(item.errorRate)).toMatchInline(` - Array [ - Object { - "x": 1607435880000, - "y": 0, - }, - Object { - "x": 1607435940000, - "y": 0, - }, - Object { - "x": 1607436000000, - "y": 0, - }, - Object { - "x": 1607436120000, - "y": 0, - }, - Object { - "x": 1607436240000, - "y": 0, - }, - Object { - "x": 1607436480000, - "y": 0, - }, - Object { - "x": 1607436600000, - "y": 0, - }, - Object { - "x": 1607436960000, - "y": 0, - }, - Object { - "x": 1607437200000, - "y": 0.5, - }, - ] - `); - }); - } - ); -} diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts index d5307178cdcb5..310f46a717d01 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts @@ -38,7 +38,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expect(response.body.transactionGroups).to.empty(); expect(response.body.isAggregationAccurate).to.be(true); - expect(response.body.requestId).to.not.empty(); }); } ); diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts new file mode 100644 index 0000000000000..50b3225e2e1f0 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import url from 'url'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + const archiveName = 'apm_8.0.0'; + const { start, end } = archives[archiveName]; + const transactionNames = ['DispatcherServlet#doGet', 'APIRestController#customers']; + + registry.when( + 'Transaction groups agg results 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-java/transactions/groups/statistics`, + query: { + start, + end, + uiFilters: '{}', + numBuckets: 20, + latencyAggregationType: 'avg', + transactionType: 'request', + transactionNames: JSON.stringify(transactionNames), + }, + }) + ); + + expect(response.status).to.be(200); + expect(response.body).to.empty(); + }); + } + ); + + registry.when( + 'Transaction groups agg results when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + it('returns the correct data', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/transactions/groups/statistics`, + query: { + start, + end, + uiFilters: '{}', + numBuckets: 20, + transactionType: 'request', + latencyAggregationType: 'avg', + transactionNames: JSON.stringify(transactionNames), + }, + }) + ); + + expect(response.status).to.be(200); + + expect(Object.keys(response.body).length).to.be(transactionNames.length); + expectSnapshot(Object.keys(response.body)).toMatchInline(` + Array [ + "DispatcherServlet#doGet", + "APIRestController#customers", + ] + `); + + expect(Object.values(response.body).map((group: any) => group.impact).length).to.be(2); + expectSnapshot(Object.values(response.body).map((group: any) => group.impact)) + .toMatchInline(` + Array [ + 93.9295870910491, + 1.35334507158962, + ] + `); + + const item = response.body[transactionNames[0]]; + + function removeEmptyCoordinates(coordinates: Array<{ x: number; y?: number }>) { + return coordinates.filter(({ y }) => y !== null && y !== undefined); + } + + expect(item.latency.length).to.be.greaterThan(0); + expectSnapshot(removeEmptyCoordinates(item.latency)[0]).toMatchInline(` + Object { + "x": 1607435880000, + "y": 69429, + } + `); + expect(item.throughput.length).to.be.greaterThan(0); + expectSnapshot(removeEmptyCoordinates(item.throughput)[0]).toMatchInline(` + Object { + "x": 1607435820000, + "y": 0, + } + `); + expect(item.errorRate.length).to.be.greaterThan(0); + expectSnapshot(removeEmptyCoordinates(item.errorRate).pop()).toMatchInline(` + Object { + "x": 1607437200000, + "y": 0.5, + } + `); + }); + it('returns empty when transaction name is not found', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/transactions/groups/statistics`, + query: { + start, + end, + uiFilters: '{}', + numBuckets: 20, + transactionType: 'request', + latencyAggregationType: 'avg', + transactionNames: JSON.stringify(['foo']), + }, + }) + ); + + expect(response.status).to.be(200); + expect(response.body).to.empty(); + }); + } + ); +} From 9b4e84ccd14f0e3dd3ce29c7327a8e2cc20b3c02 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 4 Feb 2021 09:48:07 +0100 Subject: [PATCH 16/31] fixing license --- .../service_overview_transactions_table/get_columns.tsx | 6 ++++-- .../services/get_service_transaction_groups_agg_results.ts | 5 +++-- .../tests/transactions/transactions_groups_statistics.ts | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx index a3b74ee1e5393..e6cc657585c77 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx @@ -1,8 +1,10 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * 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 { EuiBasicTableColumn } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts index 5e490f84210a4..20fd34b704980 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts @@ -1,7 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * 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 { diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts index 50b3225e2e1f0..0053ef97bc785 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts @@ -1,7 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * 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'; From c725a6e59d793c327f7a789bf42d203bb04e70b8 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 4 Feb 2021 11:30:07 +0100 Subject: [PATCH 17/31] renaming --- ...esults.ts => get_service_transaction_groups_statistics.ts} | 2 +- x-pack/plugins/apm/server/routes/transactions.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename x-pack/plugins/apm/server/lib/services/{get_service_transaction_groups_agg_results.ts => get_service_transaction_groups_statistics.ts} (98%) diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts similarity index 98% rename from x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts rename to x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts index 20fd34b704980..52101547628d3 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_agg_results.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts @@ -28,7 +28,7 @@ import { } from '../helpers/latency_aggregation_type'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; -export async function getServiceTransactionGroupsAggResults({ +export async function getServiceTransactionGroupsStatistics({ serviceName, transactionNames, setup, diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index b98721866f05b..687b22cfc147a 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -16,7 +16,7 @@ import { toNumberRt } from '../../common/runtime_types/to_number_rt'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; import { setupRequest } from '../lib/helpers/setup_request'; import { getServiceTransactionGroups } from '../lib/services/get_service_transaction_groups'; -import { getServiceTransactionGroupsAggResults } from '../lib/services/get_service_transaction_groups_agg_results'; +import { getServiceTransactionGroupsStatistics } from '../lib/services/get_service_transaction_groups_agg_results'; import { getTransactionBreakdown } from '../lib/transactions/breakdown'; import { getTransactionDistribution } from '../lib/transactions/distribution'; import { getAnomalySeries } from '../lib/transactions/get_anomaly_data'; @@ -139,7 +139,7 @@ export const transactionGroupsStatisticsRoute = createRoute({ }, } = context.params; - return getServiceTransactionGroupsAggResults({ + return getServiceTransactionGroupsStatistics({ setup, serviceName, transactionNames, From 51c66d97ab55dd1dacd63fa77c7acbb0905c619e Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 4 Feb 2021 13:12:27 +0100 Subject: [PATCH 18/31] using rate agg to calculate throughput --- ...t_service_transaction_groups_statistics.ts | 12 +- .../plugins/apm/server/routes/transactions.ts | 2 +- .../transactions_groups_statistics.ts | 230 ++++++++++++++++-- .../typings/elasticsearch/aggregations.d.ts | 8 + 4 files changed, 230 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts index 52101547628d3..9169a992cf227 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts @@ -110,6 +110,13 @@ export async function getServiceTransactionGroupsStatistics({ }, }, aggs: { + throughput_rate: { + rate: { + field: TRANSACTION_DURATION, + unit: 'minute', + mode: 'value_count', + }, + }, ...getLatencyAggregation(latencyAggregationType, field), [EVENT_OUTCOME]: { filter: { @@ -141,7 +148,10 @@ export async function getServiceTransactionGroupsStatistics({ const throughput = bucket.timeseries.buckets.map((timeseriesBucket) => ({ x: timeseriesBucket.key, - y: timeseriesBucket.doc_count / deltaAsMinutes, + y: + timeseriesBucket.throughput_rate.value !== null + ? timeseriesBucket.throughput_rate.value / 100 + : null, })); const errorRate = bucket.timeseries.buckets.map((timeseriesBucket) => ({ diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index 687b22cfc147a..e842b370263d7 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -16,7 +16,7 @@ import { toNumberRt } from '../../common/runtime_types/to_number_rt'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; import { setupRequest } from '../lib/helpers/setup_request'; import { getServiceTransactionGroups } from '../lib/services/get_service_transaction_groups'; -import { getServiceTransactionGroupsStatistics } from '../lib/services/get_service_transaction_groups_agg_results'; +import { getServiceTransactionGroupsStatistics } from '../lib/services/get_service_transaction_groups_statistics'; import { getTransactionBreakdown } from '../lib/transactions/breakdown'; import { getTransactionDistribution } from '../lib/transactions/distribution'; import { getAnomalySeries } from '../lib/transactions/get_anomaly_data'; diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts index 0053ef97bc785..81181cd93f389 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts @@ -68,11 +68,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(Object.keys(response.body).length).to.be(transactionNames.length); expectSnapshot(Object.keys(response.body)).toMatchInline(` - Array [ - "DispatcherServlet#doGet", - "APIRestController#customers", - ] - `); + Array [ + "DispatcherServlet#doGet", + "APIRestController#customers", + ] + `); expect(Object.values(response.body).map((group: any) => group.impact).length).to.be(2); expectSnapshot(Object.values(response.body).map((group: any) => group.impact)) @@ -90,25 +90,215 @@ export default function ApiTest({ getService }: FtrProviderContext) { } expect(item.latency.length).to.be.greaterThan(0); - expectSnapshot(removeEmptyCoordinates(item.latency)[0]).toMatchInline(` - Object { - "x": 1607435880000, - "y": 69429, - } + expectSnapshot(removeEmptyCoordinates(item.latency)).toMatchInline(` + Array [ + Object { + "x": 1607435880000, + "y": 69429, + }, + Object { + "x": 1607435940000, + "y": 8071285, + }, + Object { + "x": 1607436000000, + "y": 31949, + }, + Object { + "x": 1607436120000, + "y": 47755, + }, + Object { + "x": 1607436240000, + "y": 35403, + }, + Object { + "x": 1607436480000, + "y": 48137, + }, + Object { + "x": 1607436600000, + "y": 35457, + }, + Object { + "x": 1607436960000, + "y": 30501, + }, + Object { + "x": 1607437200000, + "y": 46937.5, + }, + ] `); expect(item.throughput.length).to.be.greaterThan(0); - expectSnapshot(removeEmptyCoordinates(item.throughput)[0]).toMatchInline(` - Object { - "x": 1607435820000, - "y": 0, - } + expectSnapshot(removeEmptyCoordinates(item.throughput)).toMatchInline(` + Array [ + Object { + "x": 1607435820000, + "y": 0, + }, + Object { + "x": 1607435880000, + "y": 0.01, + }, + Object { + "x": 1607435940000, + "y": 0.02, + }, + Object { + "x": 1607436000000, + "y": 0.01, + }, + Object { + "x": 1607436060000, + "y": 0, + }, + Object { + "x": 1607436120000, + "y": 0.01, + }, + Object { + "x": 1607436180000, + "y": 0, + }, + Object { + "x": 1607436240000, + "y": 0.04, + }, + Object { + "x": 1607436300000, + "y": 0, + }, + Object { + "x": 1607436360000, + "y": 0, + }, + Object { + "x": 1607436420000, + "y": 0, + }, + Object { + "x": 1607436480000, + "y": 0.02, + }, + Object { + "x": 1607436540000, + "y": 0, + }, + Object { + "x": 1607436600000, + "y": 0.01, + }, + Object { + "x": 1607436660000, + "y": 0, + }, + Object { + "x": 1607436720000, + "y": 0, + }, + Object { + "x": 1607436780000, + "y": 0, + }, + Object { + "x": 1607436840000, + "y": 0, + }, + Object { + "x": 1607436900000, + "y": 0, + }, + Object { + "x": 1607436960000, + "y": 0.02, + }, + Object { + "x": 1607437020000, + "y": 0, + }, + Object { + "x": 1607437080000, + "y": 0, + }, + Object { + "x": 1607437140000, + "y": 0, + }, + Object { + "x": 1607437200000, + "y": 0.02, + }, + Object { + "x": 1607437260000, + "y": 0, + }, + Object { + "x": 1607437320000, + "y": 0, + }, + Object { + "x": 1607437380000, + "y": 0, + }, + Object { + "x": 1607437440000, + "y": 0, + }, + Object { + "x": 1607437500000, + "y": 0, + }, + Object { + "x": 1607437560000, + "y": 0, + }, + Object { + "x": 1607437620000, + "y": 0, + }, + ] `); expect(item.errorRate.length).to.be.greaterThan(0); - expectSnapshot(removeEmptyCoordinates(item.errorRate).pop()).toMatchInline(` - Object { - "x": 1607437200000, - "y": 0.5, - } + expectSnapshot(removeEmptyCoordinates(item.errorRate)).toMatchInline(` + Array [ + Object { + "x": 1607435880000, + "y": 0, + }, + Object { + "x": 1607435940000, + "y": 0, + }, + Object { + "x": 1607436000000, + "y": 0, + }, + Object { + "x": 1607436120000, + "y": 0, + }, + Object { + "x": 1607436240000, + "y": 0, + }, + Object { + "x": 1607436480000, + "y": 0, + }, + Object { + "x": 1607436600000, + "y": 0, + }, + Object { + "x": 1607436960000, + "y": 0, + }, + Object { + "x": 1607437200000, + "y": 0.5, + }, + ] `); }); it('returns empty when transaction name is not found', async () => { diff --git a/x-pack/typings/elasticsearch/aggregations.d.ts b/x-pack/typings/elasticsearch/aggregations.d.ts index 795205e82aa6b..fa4e087f2106a 100644 --- a/x-pack/typings/elasticsearch/aggregations.d.ts +++ b/x-pack/typings/elasticsearch/aggregations.d.ts @@ -190,6 +190,11 @@ export interface AggregationOptionsByType { gap_policy?: 'skip' | 'insert_zeros'; format?: string; }; + rate: { + field?: string; + unit: 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'; + mode?: 'sum' | 'value_count'; + }; } type AggregationType = keyof AggregationOptionsByType; @@ -409,6 +414,9 @@ interface AggregationResponsePart = TFieldName extends string From a37637d6251f85a74caf74a2e0728ad82d759555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Fri, 5 Feb 2021 09:20:16 +0100 Subject: [PATCH 19/31] Removing unused variable --- .../lib/services/get_service_transaction_groups_statistics.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts index 9169a992cf227..539e68e973070 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts @@ -56,8 +56,6 @@ export async function getServiceTransactionGroupsStatistics({ > > { const { apmEventClient, start, end, esFilter } = setup; - const deltaAsMinutes = (end - start) / 1000 / 60; - const { intervalString } = getBucketSize({ start, end, numBuckets }); const field = getTransactionDurationFieldForAggregatedTransactions( From 80f12b80175547764cc26700e8562d802055f1fb Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 5 Feb 2021 17:49:40 +0100 Subject: [PATCH 20/31] addressing PR comments --- .../service_overview_transactions_table/index.tsx | 4 +--- .../server/lib/services/get_service_transaction_groups.ts | 5 +---- .../services/get_service_transaction_groups_statistics.ts | 2 +- 3 files changed, 3 insertions(+), 8 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 d173d8290d882..ee64e87e53521 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 @@ -149,9 +149,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { const columns = getColumns({ serviceName, latencyAggregationType, - transactionGroupsStatistics: transactionGroupsStatistics - ? transactionGroupsStatistics[requestId] - : undefined, + transactionGroupsStatistics: transactionGroupsStatistics?.[requestId], }); const isLoading = 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 4f21c0433ff34..3b4360677aaa5 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 @@ -129,11 +129,8 @@ export async function getServiceTransactionGroups({ }; }) ?? []; - // By default sorts transactions by impact - const sortedTransactionGroups = sortBy(transactionGroups, 'impact').reverse(); - return { - transactionGroups: sortedTransactionGroups.map((transactionGroup) => ({ + transactionGroups: transactionGroups.map((transactionGroup) => ({ ...transactionGroup, transactionType, })), diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts index 9169a992cf227..7d9260edcfa51 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts @@ -150,7 +150,7 @@ export async function getServiceTransactionGroupsStatistics({ x: timeseriesBucket.key, y: timeseriesBucket.throughput_rate.value !== null - ? timeseriesBucket.throughput_rate.value / 100 + ? timeseriesBucket.throughput_rate.value : null, })); From 19b395fc61e79d96b5b7b073ea34e600bdf5d8cf Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Sat, 6 Feb 2021 11:39:56 +0100 Subject: [PATCH 21/31] addressing PR comments --- .../service_overview.test.tsx | 2 +- .../get_columns.tsx | 4 +- .../index.tsx | 4 +- .../get_service_transaction_groups.ts | 1 - ...t_service_transaction_groups_statistics.ts | 72 ++- .../apm/server/routes/create_apm_api.ts | 8 +- .../plugins/apm/server/routes/transactions.ts | 11 +- .../test/apm_api_integration/tests/index.ts | 4 +- .../service_overview/dependencies/index.ts | 43 +- ...sactions_groups_comparison_statistics.snap | 517 ++++++++++++++++++ ...ansactions_groups_comparison_statistics.ts | 158 ++++++ ...transactions_groups_primary_statistics.ts} | 67 ++- .../transactions_groups_statistics.ts | 325 ----------- x-pack/test/apm_api_integration/utils.ts | 18 + 14 files changed, 813 insertions(+), 421 deletions(-) create mode 100644 x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_groups_comparison_statistics.snap create mode 100644 x-pack/test/apm_api_integration/tests/transactions/transactions_groups_comparison_statistics.ts rename x-pack/test/apm_api_integration/tests/transactions/{transactions_groups_overview.ts => transactions_groups_primary_statistics.ts} (62%) delete mode 100644 x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts create mode 100644 x-pack/test/apm_api_integration/utils.ts diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx index cb77c0fa1346f..1787dd81065be 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx @@ -86,7 +86,7 @@ describe('ServiceOverview', () => { error_groups: [], total_error_groups: 0, }, - 'GET /api/apm/services/{serviceName}/transactions/groups/overview': { + 'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics': { transactionGroups: [], totalTransactionGroups: 0, isAggregationAccurate: true, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx index e6cc657585c77..dd22ccf05c516 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx @@ -21,12 +21,12 @@ import { ImpactBar } from '../../../shared/ImpactBar'; import { TransactionDetailLink } from '../../../shared/Links/apm/transaction_detail_link'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; -type TransactionGroupsOverview = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/overview'>; +type TransactionGroupsOverview = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics'>; type ServiceTransactionGroupItem = ValuesType< TransactionGroupsOverview['transactionGroups'] >; -type TransactionGroupsStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/statistics'>; +type TransactionGroupsStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/comparison_statistics'>; function getLatencyAggregationTypeLabel(latencyAggregationType?: string) { switch (latencyAggregationType) { 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 ee64e87e53521..ba890165dc4d1 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 @@ -69,7 +69,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { } return callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/overview', + 'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics', params: { path: { serviceName }, query: { @@ -122,7 +122,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { ) { return callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/statistics', + 'GET /api/apm/services/{serviceName}/transactions/groups/comparison_statistics', params: { path: { serviceName }, query: { 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 3b4360677aaa5..9dd677beaa576 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 @@ -5,7 +5,6 @@ * 2.0. */ -import { sortBy } from 'lodash'; import { EVENT_OUTCOME, SERVICE_NAME, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts index 10db26138e0b6..1c355b4b034a9 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { keyBy } from 'lodash'; import { EVENT_OUTCOME, SERVICE_NAME, @@ -132,48 +133,43 @@ export async function getServiceTransactionGroupsStatistics({ const buckets = response.aggregations?.transaction_groups.buckets ?? []; const totalDuration = response.aggregations?.total_duration.value; - - return buckets.reduce((acc, bucket) => { - const transactionName = bucket.key; - - const latency = bucket.timeseries.buckets.map((timeseriesBucket) => ({ - x: timeseriesBucket.key, - y: getLatencyValue({ - latencyAggregationType, - aggregation: timeseriesBucket.latency, - }), - })); - - const throughput = bucket.timeseries.buckets.map((timeseriesBucket) => ({ - x: timeseriesBucket.key, - y: - timeseriesBucket.throughput_rate.value !== null - ? timeseriesBucket.throughput_rate.value - : null, - })); - - const errorRate = bucket.timeseries.buckets.map((timeseriesBucket) => ({ - x: timeseriesBucket.key, - y: - timeseriesBucket.doc_count > 0 - ? timeseriesBucket[EVENT_OUTCOME].doc_count / - timeseriesBucket.doc_count - : null, - })); - - const transactionGroupTotalDuration = - bucket.transaction_group_total_duration.value || 0; - - return { - ...acc, - [transactionName]: { + return keyBy( + buckets.map((bucket) => { + const transactionName = bucket.key; + const latency = bucket.timeseries.buckets.map((timeseriesBucket) => ({ + x: timeseriesBucket.key, + y: getLatencyValue({ + latencyAggregationType, + aggregation: timeseriesBucket.latency, + }), + })); + const throughput = bucket.timeseries.buckets.map((timeseriesBucket) => ({ + x: timeseriesBucket.key, + y: + timeseriesBucket.throughput_rate.value !== null + ? timeseriesBucket.throughput_rate.value + : null, + })); + const errorRate = bucket.timeseries.buckets.map((timeseriesBucket) => ({ + x: timeseriesBucket.key, + y: + timeseriesBucket.doc_count > 0 + ? timeseriesBucket[EVENT_OUTCOME].doc_count / + timeseriesBucket.doc_count + : null, + })); + const transactionGroupTotalDuration = + bucket.transaction_group_total_duration.value || 0; + return { + transactionName, latency, throughput, errorRate, impact: totalDuration ? (transactionGroupTotalDuration * 100) / totalDuration : 0, - }, - }; - }, {}); + }; + }), + 'transactionName' + ); } diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index bdeb713c9fc78..49bd7d94c31c2 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -61,10 +61,10 @@ import { transactionChartsDistributionRoute, transactionChartsErrorRateRoute, transactionGroupsRoute, - transactionGroupsOverviewRoute, + transactionGroupsPrimaryStatisticsRoute, transactionLatencyChatsRoute, transactionThroughputChatsRoute, - transactionGroupsStatisticsRoute, + transactionGroupsComparisonStatisticsRoute, } from './transactions'; import { errorGroupsLocalFiltersRoute, @@ -172,10 +172,10 @@ const createApmApi = () => { .add(transactionChartsDistributionRoute) .add(transactionChartsErrorRateRoute) .add(transactionGroupsRoute) - .add(transactionGroupsOverviewRoute) + .add(transactionGroupsPrimaryStatisticsRoute) .add(transactionLatencyChatsRoute) .add(transactionThroughputChatsRoute) - .add(transactionGroupsStatisticsRoute) + .add(transactionGroupsComparisonStatisticsRoute) // UI filters .add(errorGroupsLocalFiltersRoute) diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index e842b370263d7..aa56dd9f1ba38 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -29,7 +29,7 @@ import { rangeRt, uiFiltersRt } 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/overview/ + * //TODO: delete this once we moved away from the old table in the transaction overview page. It should be replaced by /transactions/groups/primary_statistics/ */ export const transactionGroupsRoute = createRoute({ endpoint: 'GET /api/apm/services/{serviceName}/transactions/groups', @@ -65,8 +65,9 @@ export const transactionGroupsRoute = createRoute({ }, }); -export const transactionGroupsOverviewRoute = createRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/transactions/groups/overview', +export const transactionGroupsPrimaryStatisticsRoute = createRoute({ + endpoint: + 'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics', params: t.type({ path: t.type({ serviceName: t.string }), query: t.intersection([ @@ -103,9 +104,9 @@ export const transactionGroupsOverviewRoute = createRoute({ }, }); -export const transactionGroupsStatisticsRoute = createRoute({ +export const transactionGroupsComparisonStatisticsRoute = createRoute({ endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/statistics', + 'GET /api/apm/services/{serviceName}/transactions/groups/comparison_statistics', params: t.type({ path: t.type({ serviceName: t.string }), query: t.intersection([ diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index d278fdaba1bb1..72ca22ae749ca 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -62,8 +62,8 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte loadTestFile(require.resolve('./transactions/latency')); loadTestFile(require.resolve('./transactions/throughput')); loadTestFile(require.resolve('./transactions/top_transaction_groups')); - loadTestFile(require.resolve('./transactions/transactions_groups_overview')); - loadTestFile(require.resolve('./transactions/transactions_groups_statistics')); + loadTestFile(require.resolve('./transactions/transactions_groups_primary_statistics')); + loadTestFile(require.resolve('./transactions/transactions_groups_comparison_statistics')); loadTestFile(require.resolve('./feature_controls')); diff --git a/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts index 321ef0c4a7638..fde1210551816 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts @@ -6,20 +6,17 @@ */ import expect from '@kbn/expect'; +import { last, omit, pick, sortBy } from 'lodash'; import url from 'url'; -import { sortBy, pick, last, omit } from 'lodash'; import { ValuesType } from 'utility-types'; -import { registry } from '../../../common/registry'; -import { Maybe } from '../../../../../plugins/apm/typings/common'; -import { isFiniteNumber } from '../../../../../plugins/apm/common/utils/is_finite_number'; -import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { roundNumber } from '../../../utils'; import { ENVIRONMENT_ALL } from '../../../../../plugins/apm/common/environment_filter_values'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { registry } from '../../../common/registry'; import { apmDependenciesMapping, createServiceDependencyDocs } from './es_utils'; -const round = (num: Maybe): string => (isFiniteNumber(num) ? num.toPrecision(4) : ''); - export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const es = getService('es'); @@ -235,9 +232,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(opbeansNode !== undefined).to.be(true); const values = { - latency: round(opbeansNode?.latency.value), - throughput: round(opbeansNode?.throughput.value), - errorRate: round(opbeansNode?.errorRate.value), + latency: roundNumber(opbeansNode?.latency.value), + throughput: roundNumber(opbeansNode?.throughput.value), + errorRate: roundNumber(opbeansNode?.errorRate.value), ...pick(opbeansNode, 'serviceName', 'type', 'agentName', 'environment', 'impact'), }; @@ -250,16 +247,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { environment: '', serviceName: 'opbeans-node', type: 'service', - errorRate: round(errors / count), - latency: round(sum / count), - throughput: round(count / ((endTime - startTime) / 1000 / 60)), + errorRate: roundNumber(errors / count), + latency: roundNumber(sum / count), + throughput: roundNumber(count / ((endTime - startTime) / 1000 / 60)), impact: 100, }); - const firstValue = round(opbeansNode?.latency.timeseries[0].y); - const lastValue = round(last(opbeansNode?.latency.timeseries)?.y); + const firstValue = roundNumber(opbeansNode?.latency.timeseries[0].y); + const lastValue = roundNumber(last(opbeansNode?.latency.timeseries)?.y); - expect(firstValue).to.be(round(20 / 3)); + expect(firstValue).to.be(roundNumber(20 / 3)); expect(lastValue).to.be('1.000'); }); @@ -271,9 +268,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(postgres !== undefined).to.be(true); const values = { - latency: round(postgres?.latency.value), - throughput: round(postgres?.throughput.value), - errorRate: round(postgres?.errorRate.value), + latency: roundNumber(postgres?.latency.value), + throughput: roundNumber(postgres?.throughput.value), + errorRate: roundNumber(postgres?.errorRate.value), ...pick(postgres, 'spanType', 'spanSubtype', 'name', 'impact', 'type'), }; @@ -286,9 +283,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { spanSubtype: 'http', name: 'postgres', type: 'external', - errorRate: round(errors / count), - latency: round(sum / count), - throughput: round(count / ((endTime - startTime) / 1000 / 60)), + errorRate: roundNumber(errors / count), + latency: roundNumber(sum / count), + throughput: roundNumber(count / ((endTime - startTime) / 1000 / 60)), impact: 0, }); }); diff --git a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_groups_comparison_statistics.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_groups_comparison_statistics.snap new file mode 100644 index 0000000000000..360f3de8e8598 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_groups_comparison_statistics.snap @@ -0,0 +1,517 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`APM API tests basic apm_8.0.0 Transaction groups agg results when data is loaded returns the correct data 1`] = ` +Array [ + Object { + "x": 1607435820000, + "y": null, + }, + Object { + "x": 1607435880000, + "y": 69429, + }, + Object { + "x": 1607435940000, + "y": 8071285, + }, + Object { + "x": 1607436000000, + "y": 31949, + }, + Object { + "x": 1607436060000, + "y": null, + }, + Object { + "x": 1607436120000, + "y": 47755, + }, + Object { + "x": 1607436180000, + "y": null, + }, + Object { + "x": 1607436240000, + "y": 35403, + }, + Object { + "x": 1607436300000, + "y": null, + }, + Object { + "x": 1607436360000, + "y": null, + }, + Object { + "x": 1607436420000, + "y": null, + }, + Object { + "x": 1607436480000, + "y": 48137, + }, + Object { + "x": 1607436540000, + "y": null, + }, + Object { + "x": 1607436600000, + "y": 35457, + }, + Object { + "x": 1607436660000, + "y": null, + }, + Object { + "x": 1607436720000, + "y": null, + }, + Object { + "x": 1607436780000, + "y": null, + }, + Object { + "x": 1607436840000, + "y": null, + }, + Object { + "x": 1607436900000, + "y": null, + }, + Object { + "x": 1607436960000, + "y": 30501, + }, + Object { + "x": 1607437020000, + "y": null, + }, + Object { + "x": 1607437080000, + "y": null, + }, + Object { + "x": 1607437140000, + "y": null, + }, + Object { + "x": 1607437200000, + "y": 46937.5, + }, + Object { + "x": 1607437260000, + "y": null, + }, + Object { + "x": 1607437320000, + "y": null, + }, + Object { + "x": 1607437380000, + "y": null, + }, + Object { + "x": 1607437440000, + "y": null, + }, + Object { + "x": 1607437500000, + "y": null, + }, + Object { + "x": 1607437560000, + "y": null, + }, + Object { + "x": 1607437620000, + "y": null, + }, +] +`; + +exports[`APM API tests basic apm_8.0.0 Transaction groups agg results when data is loaded returns the correct data 2`] = ` +Array [ + Object { + "x": 1607435820000, + "y": 0, + }, + Object { + "x": 1607435880000, + "y": 1, + }, + Object { + "x": 1607435940000, + "y": 2, + }, + Object { + "x": 1607436000000, + "y": 1, + }, + Object { + "x": 1607436060000, + "y": 0, + }, + Object { + "x": 1607436120000, + "y": 1, + }, + Object { + "x": 1607436180000, + "y": 0, + }, + Object { + "x": 1607436240000, + "y": 4, + }, + Object { + "x": 1607436300000, + "y": 0, + }, + Object { + "x": 1607436360000, + "y": 0, + }, + Object { + "x": 1607436420000, + "y": 0, + }, + Object { + "x": 1607436480000, + "y": 2, + }, + Object { + "x": 1607436540000, + "y": 0, + }, + Object { + "x": 1607436600000, + "y": 1, + }, + Object { + "x": 1607436660000, + "y": 0, + }, + Object { + "x": 1607436720000, + "y": 0, + }, + Object { + "x": 1607436780000, + "y": 0, + }, + Object { + "x": 1607436840000, + "y": 0, + }, + Object { + "x": 1607436900000, + "y": 0, + }, + Object { + "x": 1607436960000, + "y": 2, + }, + Object { + "x": 1607437020000, + "y": 0, + }, + Object { + "x": 1607437080000, + "y": 0, + }, + Object { + "x": 1607437140000, + "y": 0, + }, + Object { + "x": 1607437200000, + "y": 2, + }, + Object { + "x": 1607437260000, + "y": 0, + }, + Object { + "x": 1607437320000, + "y": 0, + }, + Object { + "x": 1607437380000, + "y": 0, + }, + Object { + "x": 1607437440000, + "y": 0, + }, + Object { + "x": 1607437500000, + "y": 0, + }, + Object { + "x": 1607437560000, + "y": 0, + }, + Object { + "x": 1607437620000, + "y": 0, + }, +] +`; + +exports[`APM API tests basic apm_8.0.0 Transaction groups agg results when data is loaded returns the correct data 3`] = ` +Array [ + Object { + "x": 1607435820000, + "y": null, + }, + Object { + "x": 1607435880000, + "y": 0, + }, + Object { + "x": 1607435940000, + "y": 0, + }, + Object { + "x": 1607436000000, + "y": 0, + }, + Object { + "x": 1607436060000, + "y": null, + }, + Object { + "x": 1607436120000, + "y": 0, + }, + Object { + "x": 1607436180000, + "y": null, + }, + Object { + "x": 1607436240000, + "y": 0, + }, + Object { + "x": 1607436300000, + "y": null, + }, + Object { + "x": 1607436360000, + "y": null, + }, + Object { + "x": 1607436420000, + "y": null, + }, + Object { + "x": 1607436480000, + "y": 0, + }, + Object { + "x": 1607436540000, + "y": null, + }, + Object { + "x": 1607436600000, + "y": 0, + }, + Object { + "x": 1607436660000, + "y": null, + }, + Object { + "x": 1607436720000, + "y": null, + }, + Object { + "x": 1607436780000, + "y": null, + }, + Object { + "x": 1607436840000, + "y": null, + }, + Object { + "x": 1607436900000, + "y": null, + }, + Object { + "x": 1607436960000, + "y": 0, + }, + Object { + "x": 1607437020000, + "y": null, + }, + Object { + "x": 1607437080000, + "y": null, + }, + Object { + "x": 1607437140000, + "y": null, + }, + Object { + "x": 1607437200000, + "y": 0.5, + }, + Object { + "x": 1607437260000, + "y": null, + }, + Object { + "x": 1607437320000, + "y": null, + }, + Object { + "x": 1607437380000, + "y": null, + }, + Object { + "x": 1607437440000, + "y": null, + }, + Object { + "x": 1607437500000, + "y": null, + }, + Object { + "x": 1607437560000, + "y": null, + }, + Object { + "x": 1607437620000, + "y": null, + }, +] +`; + +exports[`APM API tests basic apm_8.0.0 Transaction groups agg results when data is loaded returns the correct for latency aggregation 99th percentile 1`] = ` +Array [ + Object { + "x": 1607435820000, + "y": null, + }, + Object { + "x": 1607435880000, + "y": 69429, + }, + Object { + "x": 1607435940000, + "y": 8198285, + }, + Object { + "x": 1607436000000, + "y": 31949, + }, + Object { + "x": 1607436060000, + "y": null, + }, + Object { + "x": 1607436120000, + "y": 47755, + }, + Object { + "x": 1607436180000, + "y": null, + }, + Object { + "x": 1607436240000, + "y": 73411, + }, + Object { + "x": 1607436300000, + "y": null, + }, + Object { + "x": 1607436360000, + "y": null, + }, + Object { + "x": 1607436420000, + "y": null, + }, + Object { + "x": 1607436480000, + "y": 55116, + }, + Object { + "x": 1607436540000, + "y": null, + }, + Object { + "x": 1607436600000, + "y": 35457, + }, + Object { + "x": 1607436660000, + "y": null, + }, + Object { + "x": 1607436720000, + "y": null, + }, + Object { + "x": 1607436780000, + "y": null, + }, + Object { + "x": 1607436840000, + "y": null, + }, + Object { + "x": 1607436900000, + "y": null, + }, + Object { + "x": 1607436960000, + "y": 46040, + }, + Object { + "x": 1607437020000, + "y": null, + }, + Object { + "x": 1607437080000, + "y": null, + }, + Object { + "x": 1607437140000, + "y": null, + }, + Object { + "x": 1607437200000, + "y": 82486, + }, + Object { + "x": 1607437260000, + "y": null, + }, + Object { + "x": 1607437320000, + "y": null, + }, + Object { + "x": 1607437380000, + "y": null, + }, + Object { + "x": 1607437440000, + "y": null, + }, + Object { + "x": 1607437500000, + "y": null, + }, + Object { + "x": 1607437560000, + "y": null, + }, + Object { + "x": 1607437620000, + "y": null, + }, +] +`; diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_comparison_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_comparison_statistics.ts new file mode 100644 index 0000000000000..7f471039ae0bb --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_comparison_statistics.ts @@ -0,0 +1,158 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; +import { removeEmptyCoordinates, roundNumber } from '../../utils'; + +type TransactionsGroupsComparisonStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/comparison_statistics'>; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + const archiveName = 'apm_8.0.0'; + const { start, end } = archives[archiveName]; + const transactionNames = ['DispatcherServlet#doGet', 'APIRestController#customers']; + + registry.when( + 'Transaction groups agg results 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-java/transactions/groups/comparison_statistics`, + query: { + start, + end, + uiFilters: '{}', + numBuckets: 20, + latencyAggregationType: 'avg', + transactionType: 'request', + transactionNames: JSON.stringify(transactionNames), + }, + }) + ); + + expect(response.status).to.be(200); + expect(response.body).to.empty(); + }); + } + ); + + registry.when( + 'Transaction groups agg results when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + it('returns the correct data', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/transactions/groups/comparison_statistics`, + query: { + start, + end, + uiFilters: '{}', + numBuckets: 20, + transactionType: 'request', + latencyAggregationType: 'avg', + transactionNames: JSON.stringify(transactionNames), + }, + }) + ); + + expect(response.status).to.be(200); + + const transactionsGroupsComparisonStatistics = response.body as TransactionsGroupsComparisonStatistics; + + expect(Object.keys(transactionsGroupsComparisonStatistics).length).to.be.eql( + transactionNames.length + ); + + transactionNames.map((transactionName) => { + expect(transactionsGroupsComparisonStatistics[transactionName]).not.to.be.empty(); + }); + + const { latency, throughput, errorRate, impact } = transactionsGroupsComparisonStatistics[ + transactionNames[0] + ]; + + expect(removeEmptyCoordinates(latency).length).to.be.greaterThan(0); + expectSnapshot(latency).toMatch(); + + expect(removeEmptyCoordinates(throughput).length).to.be.greaterThan(0); + expectSnapshot(throughput).toMatch(); + + expect(removeEmptyCoordinates(errorRate).length).to.be.greaterThan(0); + expectSnapshot(errorRate).toMatch(); + + expectSnapshot(roundNumber(impact)).toMatchInline(`"93.93"`); + }); + + it('returns the correct for latency aggregation 99th percentile', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/transactions/groups/comparison_statistics`, + query: { + start, + end, + uiFilters: '{}', + numBuckets: 20, + transactionType: 'request', + latencyAggregationType: 'p99', + transactionNames: JSON.stringify(transactionNames), + }, + }) + ); + + expect(response.status).to.be(200); + + const transactionsGroupsComparisonStatistics = response.body as TransactionsGroupsComparisonStatistics; + + expect(Object.keys(transactionsGroupsComparisonStatistics).length).to.be.eql( + transactionNames.length + ); + + transactionNames.map((transactionName) => { + expect(transactionsGroupsComparisonStatistics[transactionName]).not.to.be.empty(); + }); + + const { latency, throughput, errorRate } = transactionsGroupsComparisonStatistics[ + transactionNames[0] + ]; + expect(removeEmptyCoordinates(latency).length).to.be.greaterThan(0); + expectSnapshot(latency).toMatch(); + + expect(removeEmptyCoordinates(throughput).length).to.be.greaterThan(0); + expect(removeEmptyCoordinates(errorRate).length).to.be.greaterThan(0); + }); + + it('returns empty when transaction name is not found', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/transactions/groups/comparison_statistics`, + query: { + start, + end, + uiFilters: '{}', + numBuckets: 20, + transactionType: 'request', + latencyAggregationType: 'avg', + transactionNames: JSON.stringify(['foo']), + }, + }) + ); + + expect(response.status).to.be(200); + expect(response.body).to.empty(); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_primary_statistics.ts similarity index 62% rename from x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts rename to x-pack/test/apm_api_integration/tests/transactions/transactions_groups_primary_statistics.ts index da32bad052e65..b2fdc930e742a 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_primary_statistics.ts @@ -8,10 +8,13 @@ import expect from '@kbn/expect'; import { pick, sum } from 'lodash'; import url from 'url'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import archives from '../../common/fixtures/es_archiver/archives_metadata'; import { registry } from '../../common/registry'; +type TransactionsGroupsPrimaryStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics'>; + export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -19,13 +22,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { const { start, end } = archives[archiveName]; registry.when( - 'Transaction groups overview when data is not loaded', + 'Transaction groups primary statistics 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-java/transactions/groups/overview`, + pathname: `/api/apm/services/opbeans-java/transactions/groups/primary_statistics`, query: { start, end, @@ -37,8 +40,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); expect(response.status).to.be(200); - expect(response.body.transactionGroups).to.empty(); - expect(response.body.isAggregationAccurate).to.be(true); + const transctionsGroupsPrimaryStatistics = response.body as TransactionsGroupsPrimaryStatistics; + expect(transctionsGroupsPrimaryStatistics.transactionGroups).to.empty(); + expect(transctionsGroupsPrimaryStatistics.isAggregationAccurate).to.be(true); }); } ); @@ -50,7 +54,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the correct data', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/overview`, + pathname: `/api/apm/services/opbeans-java/transactions/groups/primary_statistics`, query: { start, end, @@ -63,45 +67,50 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); - expectSnapshot(response.body.transactionGroups.map((group: any) => group.name)) - .toMatchInline(` + const transctionsGroupsPrimaryStatistics = response.body as TransactionsGroupsPrimaryStatistics; + + expectSnapshot( + transctionsGroupsPrimaryStatistics.transactionGroups.map((group: any) => group.name) + ).toMatchInline(` Array [ "DispatcherServlet#doGet", - "APIRestController#customers", - "APIRestController#order", - "APIRestController#stats", "APIRestController#customerWhoBought", + "APIRestController#order", "APIRestController#customer", + "ResourceHttpRequestHandler", + "APIRestController#customers", + "APIRestController#stats", "APIRestController#topProducts", "APIRestController#orders", "APIRestController#product", - "ResourceHttpRequestHandler", "APIRestController#products", "DispatcherServlet#doPost", ] `); - const impacts = response.body.transactionGroups.map((group: any) => group.impact); + const impacts = transctionsGroupsPrimaryStatistics.transactionGroups.map( + (group: any) => group.impact + ); expectSnapshot(impacts).toMatchInline(` Array [ 93.9295870910491, - 1.35334507158962, - 0.905514602241759, - 0.860178761411346, 0.850308244392878, + 0.905514602241759, 0.699947181217412, + 0.143906183235671, + 1.35334507158962, + 0.860178761411346, 0.476138685202191, 0.446650726277923, 0.262571482598846, - 0.143906183235671, 0.062116281544223, 0.00973568923904662, ] `); - expect(sum(impacts)).to.eql(100); + expect(Math.round(sum(impacts))).to.eql(100); - const firstItem = response.body.transactionGroups[0]; + const firstItem = transctionsGroupsPrimaryStatistics.transactionGroups[0]; expectSnapshot(pick(firstItem, 'name', 'latency', 'throughput', 'errorRate', 'impact')) .toMatchInline(` @@ -114,6 +123,28 @@ export default function ApiTest({ getService }: FtrProviderContext) { } `); }); + + it('returns the correct data for latency aggregation 99th percentile', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/transactions/groups/primary_statistics`, + query: { + start, + end, + uiFilters: '{}', + transactionType: 'request', + latencyAggregationType: 'p99', + }, + }) + ); + + expect(response.status).to.be(200); + + const transctionsGroupsPrimaryStatistics = response.body as TransactionsGroupsPrimaryStatistics; + + const firstItem = transctionsGroupsPrimaryStatistics.transactionGroups[0]; + expectSnapshot(firstItem.latency).toMatchInline(`8198285`); + }); } ); } diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts deleted file mode 100644 index 81181cd93f389..0000000000000 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_statistics.ts +++ /dev/null @@ -1,325 +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 { FtrProviderContext } from '../../common/ftr_provider_context'; -import archives from '../../common/fixtures/es_archiver/archives_metadata'; -import { registry } from '../../common/registry'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - const archiveName = 'apm_8.0.0'; - const { start, end } = archives[archiveName]; - const transactionNames = ['DispatcherServlet#doGet', 'APIRestController#customers']; - - registry.when( - 'Transaction groups agg results 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-java/transactions/groups/statistics`, - query: { - start, - end, - uiFilters: '{}', - numBuckets: 20, - latencyAggregationType: 'avg', - transactionType: 'request', - transactionNames: JSON.stringify(transactionNames), - }, - }) - ); - - expect(response.status).to.be(200); - expect(response.body).to.empty(); - }); - } - ); - - registry.when( - 'Transaction groups agg results when data is loaded', - { config: 'basic', archives: [archiveName] }, - () => { - it('returns the correct data', async () => { - const response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/statistics`, - query: { - start, - end, - uiFilters: '{}', - numBuckets: 20, - transactionType: 'request', - latencyAggregationType: 'avg', - transactionNames: JSON.stringify(transactionNames), - }, - }) - ); - - expect(response.status).to.be(200); - - expect(Object.keys(response.body).length).to.be(transactionNames.length); - expectSnapshot(Object.keys(response.body)).toMatchInline(` - Array [ - "DispatcherServlet#doGet", - "APIRestController#customers", - ] - `); - - expect(Object.values(response.body).map((group: any) => group.impact).length).to.be(2); - expectSnapshot(Object.values(response.body).map((group: any) => group.impact)) - .toMatchInline(` - Array [ - 93.9295870910491, - 1.35334507158962, - ] - `); - - const item = response.body[transactionNames[0]]; - - function removeEmptyCoordinates(coordinates: Array<{ x: number; y?: number }>) { - return coordinates.filter(({ y }) => y !== null && y !== undefined); - } - - expect(item.latency.length).to.be.greaterThan(0); - expectSnapshot(removeEmptyCoordinates(item.latency)).toMatchInline(` - Array [ - Object { - "x": 1607435880000, - "y": 69429, - }, - Object { - "x": 1607435940000, - "y": 8071285, - }, - Object { - "x": 1607436000000, - "y": 31949, - }, - Object { - "x": 1607436120000, - "y": 47755, - }, - Object { - "x": 1607436240000, - "y": 35403, - }, - Object { - "x": 1607436480000, - "y": 48137, - }, - Object { - "x": 1607436600000, - "y": 35457, - }, - Object { - "x": 1607436960000, - "y": 30501, - }, - Object { - "x": 1607437200000, - "y": 46937.5, - }, - ] - `); - expect(item.throughput.length).to.be.greaterThan(0); - expectSnapshot(removeEmptyCoordinates(item.throughput)).toMatchInline(` - Array [ - Object { - "x": 1607435820000, - "y": 0, - }, - Object { - "x": 1607435880000, - "y": 0.01, - }, - Object { - "x": 1607435940000, - "y": 0.02, - }, - Object { - "x": 1607436000000, - "y": 0.01, - }, - Object { - "x": 1607436060000, - "y": 0, - }, - Object { - "x": 1607436120000, - "y": 0.01, - }, - Object { - "x": 1607436180000, - "y": 0, - }, - Object { - "x": 1607436240000, - "y": 0.04, - }, - Object { - "x": 1607436300000, - "y": 0, - }, - Object { - "x": 1607436360000, - "y": 0, - }, - Object { - "x": 1607436420000, - "y": 0, - }, - Object { - "x": 1607436480000, - "y": 0.02, - }, - Object { - "x": 1607436540000, - "y": 0, - }, - Object { - "x": 1607436600000, - "y": 0.01, - }, - Object { - "x": 1607436660000, - "y": 0, - }, - Object { - "x": 1607436720000, - "y": 0, - }, - Object { - "x": 1607436780000, - "y": 0, - }, - Object { - "x": 1607436840000, - "y": 0, - }, - Object { - "x": 1607436900000, - "y": 0, - }, - Object { - "x": 1607436960000, - "y": 0.02, - }, - Object { - "x": 1607437020000, - "y": 0, - }, - Object { - "x": 1607437080000, - "y": 0, - }, - Object { - "x": 1607437140000, - "y": 0, - }, - Object { - "x": 1607437200000, - "y": 0.02, - }, - Object { - "x": 1607437260000, - "y": 0, - }, - Object { - "x": 1607437320000, - "y": 0, - }, - Object { - "x": 1607437380000, - "y": 0, - }, - Object { - "x": 1607437440000, - "y": 0, - }, - Object { - "x": 1607437500000, - "y": 0, - }, - Object { - "x": 1607437560000, - "y": 0, - }, - Object { - "x": 1607437620000, - "y": 0, - }, - ] - `); - expect(item.errorRate.length).to.be.greaterThan(0); - expectSnapshot(removeEmptyCoordinates(item.errorRate)).toMatchInline(` - Array [ - Object { - "x": 1607435880000, - "y": 0, - }, - Object { - "x": 1607435940000, - "y": 0, - }, - Object { - "x": 1607436000000, - "y": 0, - }, - Object { - "x": 1607436120000, - "y": 0, - }, - Object { - "x": 1607436240000, - "y": 0, - }, - Object { - "x": 1607436480000, - "y": 0, - }, - Object { - "x": 1607436600000, - "y": 0, - }, - Object { - "x": 1607436960000, - "y": 0, - }, - Object { - "x": 1607437200000, - "y": 0.5, - }, - ] - `); - }); - it('returns empty when transaction name is not found', async () => { - const response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/statistics`, - query: { - start, - end, - uiFilters: '{}', - numBuckets: 20, - transactionType: 'request', - latencyAggregationType: 'avg', - transactionNames: JSON.stringify(['foo']), - }, - }) - ); - - expect(response.status).to.be(200); - expect(response.body).to.empty(); - }); - } - ); -} diff --git a/x-pack/test/apm_api_integration/utils.ts b/x-pack/test/apm_api_integration/utils.ts new file mode 100644 index 0000000000000..a02f32adee1a7 --- /dev/null +++ b/x-pack/test/apm_api_integration/utils.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Coordinate } from '../../plugins/apm/typings/timeseries'; +import { isFiniteNumber } from '../../plugins/apm/common/utils/is_finite_number'; +import { Maybe } from '../../plugins/apm/typings/common'; + +export function roundNumber(num: Maybe) { + return isFiniteNumber(num) ? num.toPrecision(4) : ''; +} + +export function removeEmptyCoordinates(coordinates: Coordinate[]) { + return coordinates.filter(({ y }) => y !== null && y !== undefined); +} From 2e7c224751f9075049d5455d4f9a80c5b36a842f Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Sat, 6 Feb 2021 12:02:57 +0100 Subject: [PATCH 22/31] fixing error rate --- .../lib/services/get_service_transaction_groups.ts | 13 ++++++++----- .../get_service_transaction_groups_statistics.ts | 12 +++++------- 2 files changed, 13 insertions(+), 12 deletions(-) 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 9dd677beaa576..26127473dbc45 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 @@ -26,6 +26,7 @@ import { getLatencyValue, } from '../helpers/latency_aggregation_type'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { calculateTransactionErrorPercentage } from '../helpers/transaction_error_rate'; export type ServiceOverviewTransactionGroupSortField = | 'name' @@ -90,7 +91,10 @@ export async function getServiceTransactionGroups({ }, ...getLatencyAggregation(latencyAggregationType, field), [EVENT_OUTCOME]: { - filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } }, + terms: { + field: EVENT_OUTCOME, + include: [EventOutcome.failure, EventOutcome.success], + }, }, }, }, @@ -102,10 +106,9 @@ export async function getServiceTransactionGroups({ const transactionGroups = response.aggregations?.transaction_groups.buckets.map((bucket) => { - const errorRate = - bucket.doc_count > 0 - ? bucket[EVENT_OUTCOME].doc_count / bucket.doc_count - : null; + const errorRate = calculateTransactionErrorPercentage( + bucket[EVENT_OUTCOME] + ); const transactionGroupTotalDuration = bucket.transaction_group_total_duration.value || 0; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts index 1c355b4b034a9..1c356eb274f5d 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts @@ -28,6 +28,7 @@ import { getLatencyValue, } from '../helpers/latency_aggregation_type'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { calculateTransactionErrorPercentage } from '../helpers/transaction_error_rate'; export async function getServiceTransactionGroupsStatistics({ serviceName, @@ -118,8 +119,9 @@ export async function getServiceTransactionGroupsStatistics({ }, ...getLatencyAggregation(latencyAggregationType, field), [EVENT_OUTCOME]: { - filter: { - term: { [EVENT_OUTCOME]: EventOutcome.failure }, + terms: { + field: EVENT_OUTCOME, + include: [EventOutcome.failure, EventOutcome.success], }, }, }, @@ -152,11 +154,7 @@ export async function getServiceTransactionGroupsStatistics({ })); const errorRate = bucket.timeseries.buckets.map((timeseriesBucket) => ({ x: timeseriesBucket.key, - y: - timeseriesBucket.doc_count > 0 - ? timeseriesBucket[EVENT_OUTCOME].doc_count / - timeseriesBucket.doc_count - : null, + y: calculateTransactionErrorPercentage(timeseriesBucket[EVENT_OUTCOME]), })); const transactionGroupTotalDuration = bucket.transaction_group_total_duration.value || 0; From 8d33fb3b2167112a335af961687ef670d587b530 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Sun, 7 Feb 2021 10:50:04 +0100 Subject: [PATCH 23/31] fixing eslint --- .../components/app/service_overview/service_overview.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx index 1787dd81065be..999718e754c61 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx @@ -80,8 +80,8 @@ describe('ServiceOverview', () => { status: FETCH_STATUS.SUCCESS, }); + /* eslint-disable @typescript-eslint/naming-convention */ const calls = { - // eslint-disable-next-line @typescript-eslint/naming-convention 'GET /api/apm/services/{serviceName}/error_groups': { error_groups: [], total_error_groups: 0, @@ -92,9 +92,9 @@ describe('ServiceOverview', () => { isAggregationAccurate: true, }, 'GET /api/apm/services/{serviceName}/dependencies': [], - // eslint-disable-next-line @typescript-eslint/naming-convention 'GET /api/apm/services/{serviceName}/service_overview_instances': [], }; + /* eslint-enable @typescript-eslint/naming-convention */ jest .spyOn(callApmApiModule, 'createCallApmApi') From 85e4b94965c79ea379947c504bf607e2892698ea Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 8 Feb 2021 16:23:46 +0100 Subject: [PATCH 24/31] addressing PR comments --- .../get_columns.tsx | 29 ++++++++----------- .../index.tsx | 12 ++++---- ...ransaction_group_comparison_statistics.ts} | 7 ++--- .../plugins/apm/server/routes/transactions.ts | 6 ++-- ...sactions_groups_comparison_statistics.snap | 8 ++--- ...ansactions_groups_comparison_statistics.ts | 4 +-- .../transactions_groups_primary_statistics.ts | 2 +- x-pack/test/apm_api_integration/utils.ts | 2 +- 8 files changed, 31 insertions(+), 39 deletions(-) rename x-pack/plugins/apm/server/lib/services/{get_service_transaction_groups_statistics.ts => get_service_transaction_group_comparison_statistics.ts} (96%) diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx index dd22ccf05c516..cf1c9046769c9 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx @@ -21,12 +21,12 @@ import { ImpactBar } from '../../../shared/ImpactBar'; import { TransactionDetailLink } from '../../../shared/Links/apm/transaction_detail_link'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; -type TransactionGroupsOverview = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics'>; +type TransactionGroupPrimaryStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics'>; type ServiceTransactionGroupItem = ValuesType< - TransactionGroupsOverview['transactionGroups'] + TransactionGroupPrimaryStatistics['transactionGroups'] >; -type TransactionGroupsStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/comparison_statistics'>; +type TransactionGroupComparisonStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/comparison_statistics'>; function getLatencyAggregationTypeLabel(latencyAggregationType?: string) { switch (latencyAggregationType) { @@ -53,11 +53,11 @@ function getLatencyAggregationTypeLabel(latencyAggregationType?: string) { export function getColumns({ serviceName, latencyAggregationType, - transactionGroupsStatistics, + transactionGroupComparisonStatistics, }: { serviceName: string; latencyAggregationType?: string; - transactionGroupsStatistics?: TransactionGroupsStatistics; + transactionGroupComparisonStatistics?: TransactionGroupComparisonStatistics; }): Array> { return [ { @@ -91,9 +91,8 @@ export function getColumns({ name: getLatencyAggregationTypeLabel(latencyAggregationType), width: px(unit * 10), render: (_, { latency, name }) => { - const timeseries = transactionGroupsStatistics - ? transactionGroupsStatistics[name]?.latency - : undefined; + const timeseries = + transactionGroupComparisonStatistics?.[name]?.latency; return ( { - const timeseries = transactionGroupsStatistics - ? transactionGroupsStatistics[name]?.throughput - : undefined; + const timeseries = + transactionGroupComparisonStatistics?.[name]?.throughput; return ( { - const timeseries = transactionGroupsStatistics - ? transactionGroupsStatistics[name]?.errorRate - : undefined; + const timeseries = + transactionGroupComparisonStatistics?.[name]?.errorRate; return ( { - const impact = transactionGroupsStatistics - ? transactionGroupsStatistics[name]?.impact - : 0; + const impact = transactionGroupComparisonStatistics?.[name]?.impact; return ; }, }, 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 ba890165dc4d1..bd39e759d2d78 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 @@ -12,7 +12,7 @@ import { EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isEmpty, orderBy } from 'lodash'; +import { orderBy } from 'lodash'; import React, { useState } from 'react'; import uuid from 'uuid'; import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; @@ -109,12 +109,11 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { ); const { - data: transactionGroupsStatistics, - status: transactionGroupsStatisticsStatus, + data: transactionGroupComparisonStatistics, + status: transactionGroupComparisonStatisticsStatus, } = useFetcher( (callApmApi) => { if ( - !isEmpty(requestId) && currentPageTransactionGroups.length && start && end && @@ -149,12 +148,13 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { const columns = getColumns({ serviceName, latencyAggregationType, - transactionGroupsStatistics: transactionGroupsStatistics?.[requestId], + transactionGroupComparisonStatistics: + transactionGroupComparisonStatistics?.[requestId], }); const isLoading = status === FETCH_STATUS.LOADING || - transactionGroupsStatisticsStatus === FETCH_STATUS.LOADING; + transactionGroupComparisonStatisticsStatus === FETCH_STATUS.LOADING; const pagination = { pageIndex, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts similarity index 96% rename from x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts rename to x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts index 1c356eb274f5d..7fa3b2f79bc01 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts @@ -30,7 +30,7 @@ import { import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { calculateTransactionErrorPercentage } from '../helpers/transaction_error_rate'; -export async function getServiceTransactionGroupsStatistics({ +export async function getServiceTransactionGroupComparisonStatistics({ serviceName, transactionNames, setup, @@ -147,10 +147,7 @@ export async function getServiceTransactionGroupsStatistics({ })); const throughput = bucket.timeseries.buckets.map((timeseriesBucket) => ({ x: timeseriesBucket.key, - y: - timeseriesBucket.throughput_rate.value !== null - ? timeseriesBucket.throughput_rate.value - : null, + y: timeseriesBucket.throughput_rate.value, })); const errorRate = bucket.timeseries.buckets.map((timeseriesBucket) => ({ x: timeseriesBucket.key, diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index aa56dd9f1ba38..bef96cb7f0767 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -16,7 +16,7 @@ import { toNumberRt } from '../../common/runtime_types/to_number_rt'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; import { setupRequest } from '../lib/helpers/setup_request'; import { getServiceTransactionGroups } from '../lib/services/get_service_transaction_groups'; -import { getServiceTransactionGroupsStatistics } from '../lib/services/get_service_transaction_groups_statistics'; +import { getServiceTransactionGroupComparisonStatistics } from '../lib/services/get_service_transaction_group_comparison_statistics'; import { getTransactionBreakdown } from '../lib/transactions/breakdown'; import { getTransactionDistribution } from '../lib/transactions/distribution'; import { getAnomalySeries } from '../lib/transactions/get_anomaly_data'; @@ -113,7 +113,7 @@ export const transactionGroupsComparisonStatisticsRoute = createRoute({ rangeRt, uiFiltersRt, t.type({ - transactionNames: t.string.pipe(jsonRt), + transactionNames: jsonRt, numBuckets: toNumberRt, transactionType: t.string, latencyAggregationType: latencyAggregationTypeRt, @@ -140,7 +140,7 @@ export const transactionGroupsComparisonStatisticsRoute = createRoute({ }, } = context.params; - return getServiceTransactionGroupsStatistics({ + return getServiceTransactionGroupComparisonStatistics({ setup, serviceName, transactionNames, diff --git a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_groups_comparison_statistics.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_groups_comparison_statistics.snap index 360f3de8e8598..739ff5a080d76 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_groups_comparison_statistics.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_groups_comparison_statistics.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM API tests basic apm_8.0.0 Transaction groups agg results when data is loaded returns the correct data 1`] = ` +exports[`APM API tests basic apm_8.0.0 Transaction groups comparison statistics when data is loaded returns the correct data 1`] = ` Array [ Object { "x": 1607435820000, @@ -129,7 +129,7 @@ Array [ ] `; -exports[`APM API tests basic apm_8.0.0 Transaction groups agg results when data is loaded returns the correct data 2`] = ` +exports[`APM API tests basic apm_8.0.0 Transaction groups comparison statistics when data is loaded returns the correct data 2`] = ` Array [ Object { "x": 1607435820000, @@ -258,7 +258,7 @@ Array [ ] `; -exports[`APM API tests basic apm_8.0.0 Transaction groups agg results when data is loaded returns the correct data 3`] = ` +exports[`APM API tests basic apm_8.0.0 Transaction groups comparison statistics when data is loaded returns the correct data 3`] = ` Array [ Object { "x": 1607435820000, @@ -387,7 +387,7 @@ Array [ ] `; -exports[`APM API tests basic apm_8.0.0 Transaction groups agg results when data is loaded returns the correct for latency aggregation 99th percentile 1`] = ` +exports[`APM API tests basic apm_8.0.0 Transaction groups comparison statistics when data is loaded returns the correct for latency aggregation 99th percentile 1`] = ` Array [ Object { "x": 1607435820000, diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_comparison_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_comparison_statistics.ts index 7f471039ae0bb..414e2189a63fe 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_comparison_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_comparison_statistics.ts @@ -23,7 +23,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const transactionNames = ['DispatcherServlet#doGet', 'APIRestController#customers']; registry.when( - 'Transaction groups agg results when data is not loaded', + 'Transaction groups comparison statistics when data is not loaded', { config: 'basic', archives: [] }, () => { it('handles the empty state', async () => { @@ -49,7 +49,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); registry.when( - 'Transaction groups agg results when data is loaded', + 'Transaction groups comparison statistics when data is loaded', { config: 'basic', archives: [archiveName] }, () => { it('returns the correct data', async () => { diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_primary_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_primary_statistics.ts index b2fdc930e742a..7d8417bc5bf63 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_primary_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_primary_statistics.ts @@ -48,7 +48,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); registry.when( - 'Top transaction groups when data is loaded', + 'Transaction groups primary statistics when data is loaded', { config: 'basic', archives: [archiveName] }, () => { it('returns the correct data', async () => { diff --git a/x-pack/test/apm_api_integration/utils.ts b/x-pack/test/apm_api_integration/utils.ts index a02f32adee1a7..0fb99e2aa3c7c 100644 --- a/x-pack/test/apm_api_integration/utils.ts +++ b/x-pack/test/apm_api_integration/utils.ts @@ -14,5 +14,5 @@ export function roundNumber(num: Maybe) { } export function removeEmptyCoordinates(coordinates: Coordinate[]) { - return coordinates.filter(({ y }) => y !== null && y !== undefined); + return coordinates.filter(({ y }) => isFiniteNumber(y)); } From a67632ff9d125377bb074666fd9733b5198e4f94 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 8 Feb 2021 17:13:03 +0100 Subject: [PATCH 25/31] fixig TS issue --- .../service_overview_transactions_table/get_columns.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx index cf1c9046769c9..2ffc0fc9c93a3 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx @@ -154,7 +154,8 @@ export function getColumns({ ), width: px(unit * 5), render: (_, { name }) => { - const impact = transactionGroupComparisonStatistics?.[name]?.impact; + const impact = + transactionGroupComparisonStatistics?.[name]?.impact ?? 0; return ; }, }, From daf88fe86497582044aafea09b96424fa6a8161a Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 9 Feb 2021 18:45:07 +0100 Subject: [PATCH 26/31] removing requestId --- .../index.tsx | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 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 bd39e759d2d78..42da402cc6553 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 @@ -14,7 +14,6 @@ import { import { i18n } from '@kbn/i18n'; import { orderBy } from 'lodash'; import React, { useState } from 'react'; -import uuid from 'uuid'; import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; @@ -31,7 +30,6 @@ interface Props { const INITIAL_STATE = { transactionGroups: [], isAggregationAccurate: true, - requestId: '', }; type SortField = 'name' | 'latency' | 'throughput' | 'errorRate' | 'impact'; @@ -80,11 +78,6 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { latencyAggregationType: latencyAggregationType as LatencyAggregationType, }, }, - }).then((response) => { - return { - requestId: uuid(), - ...response, - }; }); }, [ @@ -97,7 +90,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { ] ); - const { transactionGroups, requestId } = data; + const { transactionGroups } = data; const currentPageTransactionGroups = orderBy( transactionGroups, sort.field, @@ -134,22 +127,19 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { transactionNames, }, }, - isCachable: true, - }).then((result) => { - return { [requestId]: result }; }); } }, - // only fetches statistics when requestId changes or transaction names changes + // only fetches statistics when transaction names changes // eslint-disable-next-line react-hooks/exhaustive-deps - [requestId, transactionNames] + [transactionNames], + { preservePreviousData: false } ); const columns = getColumns({ serviceName, latencyAggregationType, - transactionGroupComparisonStatistics: - transactionGroupComparisonStatistics?.[requestId], + transactionGroupComparisonStatistics, }); const isLoading = From 6d10b2f99146399ba8a4211b17e6134451899887 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 9 Feb 2021 18:59:41 +0100 Subject: [PATCH 27/31] addressing PR comments --- .../service_overview_transactions_table/index.tsx | 8 ++++---- .../context/url_params_context/resolve_url_params.ts | 2 +- .../apm/public/context/url_params_context/types.ts | 3 ++- .../public/hooks/use_transaction_latency_chart_fetcher.ts | 3 +-- 4 files changed, 8 insertions(+), 8 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 42da402cc6553..7575cdcea4aca 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 @@ -14,7 +14,6 @@ import { import { i18n } from '@kbn/i18n'; import { orderBy } from 'lodash'; import React, { useState } from 'react'; -import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; 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'; @@ -75,7 +74,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { end, uiFilters: JSON.stringify(uiFilters), transactionType, - latencyAggregationType: latencyAggregationType as LatencyAggregationType, + latencyAggregationType, }, }, }); @@ -110,7 +109,8 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { currentPageTransactionGroups.length && start && end && - transactionType + transactionType && + latencyAggregationType ) { return callApmApi({ endpoint: @@ -123,7 +123,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { uiFilters: JSON.stringify(uiFilters), numBuckets: 20, transactionType, - latencyAggregationType: latencyAggregationType as LatencyAggregationType, + latencyAggregationType, transactionNames, }, }, diff --git a/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts index 03ad24a580612..5b72a50e8dbd8 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts +++ b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts @@ -80,7 +80,7 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) { transactionType, searchTerm: toString(searchTerm), percentile: toNumber(percentile), - latencyAggregationType, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, comparisonEnabled: comparisonEnabled ? toBoolean(comparisonEnabled) : undefined, diff --git a/x-pack/plugins/apm/public/context/url_params_context/types.ts b/x-pack/plugins/apm/public/context/url_params_context/types.ts index be66f6fdd02f6..723fca4487237 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/types.ts +++ b/x-pack/plugins/apm/public/context/url_params_context/types.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { LatencyAggregationType } from '../../../common/latency_aggregation_types'; import { LocalUIFilterName } from '../../../common/ui_filter'; export type IUrlParams = { @@ -29,7 +30,7 @@ export type IUrlParams = { pageSize?: number; searchTerm?: string; percentile?: number; - latencyAggregationType?: string; + latencyAggregationType?: LatencyAggregationType; comparisonEnabled?: boolean; comparisonType?: string; } & Partial>; diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts index 5522562fbeab7..d5974ee3543a7 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts @@ -12,7 +12,6 @@ import { useUrlParams } from '../context/url_params_context/use_url_params'; import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; import { getLatencyChartSelector } from '../selectors/latency_chart_selectors'; import { useTheme } from './use_theme'; -import { LatencyAggregationType } from '../../common/latency_aggregation_types'; export function useTransactionLatencyChartsFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); @@ -43,7 +42,7 @@ export function useTransactionLatencyChartsFetcher() { transactionType, transactionName, uiFilters: JSON.stringify(uiFilters), - latencyAggregationType: latencyAggregationType as LatencyAggregationType, + latencyAggregationType, }, }, }); From e90fa6cba98fd4254922557f6dad500fb7de4548 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 10 Feb 2021 07:30:57 +0100 Subject: [PATCH 28/31] adding requestId --- .../action_menu/anomaly_detection_setup_link.test.tsx | 1 + .../URLFilter/URLSearch/SelectableUrlList.test.tsx | 1 + .../app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx | 1 + .../public/components/app/ServiceMap/index.test.tsx | 1 + .../AgentConfigurationCreateEdit/index.stories.tsx | 1 + .../components/app/Settings/ApmIndices/index.test.tsx | 1 + .../app/Settings/CustomizeUI/CustomLink/index.test.tsx | 4 ++++ .../components/app/TraceLink/trace_link.test.tsx | 2 ++ .../app/service_details/service_icons/index.test.tsx | 5 +++++ .../service_overview_transactions_table/index.tsx | 6 +++--- .../CustomLinkMenuSection/index.test.tsx | 6 ++++++ .../TransactionActionMenu.test.tsx | 1 + x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx | 10 ++++++++++ x-pack/plugins/apm/public/hooks/use_fetcher.tsx | 7 +++++++ 14 files changed, 44 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx index a4be2bb8d773d..3623aee43c7d1 100644 --- a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx @@ -19,6 +19,7 @@ async function renderTooltipAnchor({ }) { // mock api response jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: { jobs }, status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx index 03c826b50a06b..b6311e9135695 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx @@ -14,6 +14,7 @@ import { render } from '../../utils/test_helper'; describe('SelectableUrlList', () => { it('it uses search term value from url', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: {}, status: fetcherHook.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx index e1253b41a45f5..d7edc0402d9be 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx @@ -13,6 +13,7 @@ import { KeyUXMetrics } from './KeyUXMetrics'; describe('KeyUXMetrics', () => { it('renders metrics with correct formats', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: { noOfLongTasks: 3.0009765625, sumOfLongTasks: 520.4375, diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx index e8384de1d15ba..a18e88238ce39 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx @@ -90,6 +90,7 @@ describe('ServiceMap', () => { describe('with an empty response', () => { it('renders the empty banner', async () => { jest.spyOn(useFetcherModule, 'useFetcher').mockReturnValueOnce({ + requestId: 'foo', data: { elements: [] }, refetch: () => {}, status: useFetcherModule.FETCH_STATUS.SUCCESS, diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx index 4d2754a677bf7..c7844eaaeb6e4 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx @@ -53,6 +53,7 @@ storiesOf( { it('should not get stuck in infinite loop', () => { const spy = jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: undefined, status: hooks.FETCH_STATUS.LOADING, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx index 0dbc8f6235342..ff15a3069ba8b 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx @@ -69,6 +69,7 @@ describe('CustomLink', () => { describe('empty prompt', () => { beforeAll(() => { jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: [], status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -93,6 +94,7 @@ describe('CustomLink', () => { describe('overview', () => { beforeAll(() => { jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data, status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -172,6 +174,7 @@ describe('CustomLink', () => { beforeAll(() => { saveCustomLinkSpy = jest.spyOn(saveCustomLink, 'saveCustomLink'); jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data, status: hooks.FETCH_STATUS.SUCCESS, refetch, @@ -290,6 +293,7 @@ describe('CustomLink', () => { describe('invalid license', () => { beforeAll(() => { jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: [], status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx index be23c19e0664d..93e51345d9204 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx @@ -71,6 +71,7 @@ describe('TraceLink', () => { }, }); jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: { transaction: undefined }, status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -111,6 +112,7 @@ describe('TraceLink', () => { trace: { id: 123 }, }; jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: { transaction }, status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/service_details/service_icons/index.test.tsx b/x-pack/plugins/apm/public/components/app/service_details/service_icons/index.test.tsx index dbf4b65deb3b3..0b6d14b0f23dd 100644 --- a/x-pack/plugins/apm/public/components/app/service_details/service_icons/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_details/service_icons/index.test.tsx @@ -54,6 +54,7 @@ describe('ServiceIcons', () => { describe('icons', () => { it('Shows loading spinner while fetching data', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: undefined, status: fetcherHook.FETCH_STATUS.LOADING, refetch: jest.fn(), @@ -70,6 +71,7 @@ describe('ServiceIcons', () => { }); it("doesn't show any icons", () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: {}, status: fetcherHook.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -87,6 +89,7 @@ describe('ServiceIcons', () => { }); it('shows service icon', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: { agentName: 'java', }, @@ -106,6 +109,7 @@ describe('ServiceIcons', () => { }); it('shows service and container icons', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: { agentName: 'java', containerType: 'Kubernetes', @@ -126,6 +130,7 @@ describe('ServiceIcons', () => { }); it('shows service, container and cloud icons', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: { agentName: 'java', containerType: 'Kubernetes', 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 7575cdcea4aca..379b37ed959b2 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 @@ -59,7 +59,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { urlParams: { start, end, latencyAggregationType }, } = useUrlParams(); - const { data = INITIAL_STATE, status } = useFetcher( + const { data = INITIAL_STATE, status, requestId } = useFetcher( (callApmApi) => { if (!start || !end || !latencyAggregationType || !transactionType) { return; @@ -130,9 +130,9 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { }); } }, - // only fetches statistics when transaction names changes + // only fetches statistics when transaction names or requestId changes // eslint-disable-next-line react-hooks/exhaustive-deps - [transactionNames], + [requestId, transactionNames], { preservePreviousData: false } ); diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx index 6f2910a2a5ef7..7f2a5edf50ca3 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx @@ -40,6 +40,7 @@ const transaction = ({ describe('Custom links', () => { it('shows empty message when no custom link is available', () => { jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: [], status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -58,6 +59,7 @@ describe('Custom links', () => { it('shows loading while custom links are fetched', () => { jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: [], status: useFetcher.FETCH_STATUS.LOADING, refetch: jest.fn(), @@ -79,6 +81,7 @@ describe('Custom links', () => { ] as CustomLinkType[]; jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: customLinks, status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -101,6 +104,7 @@ describe('Custom links', () => { ] as CustomLinkType[]; jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: customLinks, status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -125,6 +129,7 @@ describe('Custom links', () => { describe('create custom link buttons', () => { it('shows create button below empty message', () => { jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: [], status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -148,6 +153,7 @@ describe('Custom links', () => { ] as CustomLinkType[]; jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: customLinks, status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx index 0b0d5c8b1b8a2..7fc2907525663 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx @@ -61,6 +61,7 @@ const renderTransaction = async (transaction: Record) => { describe('TransactionActionMenu component', () => { beforeAll(() => { jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: [], status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx index 546b039f7ba8a..856fc0c3830f6 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx @@ -33,6 +33,7 @@ describe('useFetcher', () => { it('should have loading spinner initally', async () => { expect(hook.result.current).toEqual({ + requestId: '', data: undefined, error: undefined, refetch: expect.any(Function), @@ -44,6 +45,7 @@ describe('useFetcher', () => { jest.advanceTimersByTime(100); expect(hook.result.current).toEqual({ + requestId: '', data: undefined, error: undefined, refetch: expect.any(Function), @@ -56,6 +58,7 @@ describe('useFetcher', () => { await hook.waitForNextUpdate(); expect(hook.result.current).toEqual({ + requestId: expect.any(String), data: 'response from hook', error: undefined, refetch: expect.any(Function), @@ -82,6 +85,7 @@ describe('useFetcher', () => { it('should have loading spinner initally', async () => { expect(hook.result.current).toEqual({ + requestId: '', data: undefined, error: undefined, refetch: expect.any(Function), @@ -93,6 +97,7 @@ describe('useFetcher', () => { jest.advanceTimersByTime(100); expect(hook.result.current).toEqual({ + requestId: '', data: undefined, error: undefined, refetch: expect.any(Function), @@ -105,6 +110,7 @@ describe('useFetcher', () => { await hook.waitForNextUpdate(); expect(hook.result.current).toEqual({ + requestId: expect.any(String), data: undefined, error: expect.any(Error), refetch: expect.any(Function), @@ -129,6 +135,7 @@ describe('useFetcher', () => { } ); expect(hook.result.current).toEqual({ + requestId: expect.any(String), data: undefined, error: undefined, refetch: expect.any(Function), @@ -139,6 +146,7 @@ describe('useFetcher', () => { // assert: first response has loaded and should be rendered expect(hook.result.current).toEqual({ + requestId: expect.any(String), data: 'first response', error: undefined, refetch: expect.any(Function), @@ -158,6 +166,7 @@ describe('useFetcher', () => { // assert: while loading new data the previous data should still be rendered expect(hook.result.current).toEqual({ + requestId: expect.any(String), data: 'first response', error: undefined, refetch: expect.any(Function), @@ -169,6 +178,7 @@ describe('useFetcher', () => { // assert: "second response" has loaded and should be rendered expect(hook.result.current).toEqual({ + requestId: expect.any(String), data: 'second response', error: undefined, refetch: expect.any(Function), diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx index e427c5719aeb8..1770001d57538 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import React, { useEffect, useMemo, useState } from 'react'; import { IHttpFetchError } from 'src/core/public'; +import uuid from 'uuid'; import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; import { callApmApi, @@ -24,6 +25,7 @@ export enum FETCH_STATUS { } export interface FetcherResult { + requestId: string; data?: Data; status: FETCH_STATUS; error?: IHttpFetchError; @@ -75,6 +77,7 @@ export function useFetcher( >({ data: undefined, status: FETCH_STATUS.NOT_INITIATED, + requestId: '', }); const [counter, setCounter] = useState(0); const { rangeId } = useUrlParams(); @@ -83,6 +86,7 @@ export function useFetcher( let controller: AbortController = new AbortController(); async function doFetch() { + const requestId = uuid(); controller.abort(); controller = new AbortController(); @@ -98,6 +102,7 @@ export function useFetcher( } setResult((prevResult) => ({ + requestId: prevResult.requestId, data: preservePreviousData ? prevResult.data : undefined, // preserve data from previous state while loading next state status: FETCH_STATUS.LOADING, error: undefined, @@ -111,6 +116,7 @@ export function useFetcher( // aborted before updating the result. if (!signal.aborted) { setResult({ + requestId, data, status: FETCH_STATUS.SUCCESS, error: undefined, @@ -143,6 +149,7 @@ export function useFetcher( }); } setResult({ + requestId, data: undefined, status: FETCH_STATUS.FAILURE, error: e, From 4370be1a11a7414c48dc1d7e8330859fe6fbf2bc Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Fri, 12 Feb 2021 08:40:21 +0100 Subject: [PATCH 29/31] Revert "adding requestId" This reverts commit e90fa6cba98fd4254922557f6dad500fb7de4548. --- .../action_menu/anomaly_detection_setup_link.test.tsx | 1 - .../URLFilter/URLSearch/SelectableUrlList.test.tsx | 1 - .../app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx | 1 - .../public/components/app/ServiceMap/index.test.tsx | 1 - .../AgentConfigurationCreateEdit/index.stories.tsx | 1 - .../components/app/Settings/ApmIndices/index.test.tsx | 1 - .../app/Settings/CustomizeUI/CustomLink/index.test.tsx | 4 ---- .../components/app/TraceLink/trace_link.test.tsx | 2 -- .../app/service_details/service_icons/index.test.tsx | 5 ----- .../service_overview_transactions_table/index.tsx | 6 +++--- .../CustomLinkMenuSection/index.test.tsx | 6 ------ .../TransactionActionMenu.test.tsx | 1 - x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx | 10 ---------- x-pack/plugins/apm/public/hooks/use_fetcher.tsx | 7 ------- 14 files changed, 3 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx index 3623aee43c7d1..a4be2bb8d773d 100644 --- a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx @@ -19,7 +19,6 @@ async function renderTooltipAnchor({ }) { // mock api response jest.spyOn(hooks, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: { jobs }, status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx index b6311e9135695..03c826b50a06b 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx @@ -14,7 +14,6 @@ import { render } from '../../utils/test_helper'; describe('SelectableUrlList', () => { it('it uses search term value from url', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: {}, status: fetcherHook.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx index d7edc0402d9be..e1253b41a45f5 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx @@ -13,7 +13,6 @@ import { KeyUXMetrics } from './KeyUXMetrics'; describe('KeyUXMetrics', () => { it('renders metrics with correct formats', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: { noOfLongTasks: 3.0009765625, sumOfLongTasks: 520.4375, diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx index a18e88238ce39..e8384de1d15ba 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx @@ -90,7 +90,6 @@ describe('ServiceMap', () => { describe('with an empty response', () => { it('renders the empty banner', async () => { jest.spyOn(useFetcherModule, 'useFetcher').mockReturnValueOnce({ - requestId: 'foo', data: { elements: [] }, refetch: () => {}, status: useFetcherModule.FETCH_STATUS.SUCCESS, diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx index c7844eaaeb6e4..4d2754a677bf7 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx @@ -53,7 +53,6 @@ storiesOf( { it('should not get stuck in infinite loop', () => { const spy = jest.spyOn(hooks, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: undefined, status: hooks.FETCH_STATUS.LOADING, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx index ff15a3069ba8b..0dbc8f6235342 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx @@ -69,7 +69,6 @@ describe('CustomLink', () => { describe('empty prompt', () => { beforeAll(() => { jest.spyOn(hooks, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: [], status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -94,7 +93,6 @@ describe('CustomLink', () => { describe('overview', () => { beforeAll(() => { jest.spyOn(hooks, 'useFetcher').mockReturnValue({ - requestId: 'foo', data, status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -174,7 +172,6 @@ describe('CustomLink', () => { beforeAll(() => { saveCustomLinkSpy = jest.spyOn(saveCustomLink, 'saveCustomLink'); jest.spyOn(hooks, 'useFetcher').mockReturnValue({ - requestId: 'foo', data, status: hooks.FETCH_STATUS.SUCCESS, refetch, @@ -293,7 +290,6 @@ describe('CustomLink', () => { describe('invalid license', () => { beforeAll(() => { jest.spyOn(hooks, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: [], status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx index 93e51345d9204..be23c19e0664d 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx @@ -71,7 +71,6 @@ describe('TraceLink', () => { }, }); jest.spyOn(hooks, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: { transaction: undefined }, status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -112,7 +111,6 @@ describe('TraceLink', () => { trace: { id: 123 }, }; jest.spyOn(hooks, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: { transaction }, status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/service_details/service_icons/index.test.tsx b/x-pack/plugins/apm/public/components/app/service_details/service_icons/index.test.tsx index 0b6d14b0f23dd..dbf4b65deb3b3 100644 --- a/x-pack/plugins/apm/public/components/app/service_details/service_icons/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_details/service_icons/index.test.tsx @@ -54,7 +54,6 @@ describe('ServiceIcons', () => { describe('icons', () => { it('Shows loading spinner while fetching data', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: undefined, status: fetcherHook.FETCH_STATUS.LOADING, refetch: jest.fn(), @@ -71,7 +70,6 @@ describe('ServiceIcons', () => { }); it("doesn't show any icons", () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: {}, status: fetcherHook.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -89,7 +87,6 @@ describe('ServiceIcons', () => { }); it('shows service icon', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: { agentName: 'java', }, @@ -109,7 +106,6 @@ describe('ServiceIcons', () => { }); it('shows service and container icons', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: { agentName: 'java', containerType: 'Kubernetes', @@ -130,7 +126,6 @@ describe('ServiceIcons', () => { }); it('shows service, container and cloud icons', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: { agentName: 'java', containerType: 'Kubernetes', 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 379b37ed959b2..7575cdcea4aca 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 @@ -59,7 +59,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { urlParams: { start, end, latencyAggregationType }, } = useUrlParams(); - const { data = INITIAL_STATE, status, requestId } = useFetcher( + const { data = INITIAL_STATE, status } = useFetcher( (callApmApi) => { if (!start || !end || !latencyAggregationType || !transactionType) { return; @@ -130,9 +130,9 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { }); } }, - // only fetches statistics when transaction names or requestId changes + // only fetches statistics when transaction names changes // eslint-disable-next-line react-hooks/exhaustive-deps - [requestId, transactionNames], + [transactionNames], { preservePreviousData: false } ); diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx index 7f2a5edf50ca3..6f2910a2a5ef7 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx @@ -40,7 +40,6 @@ const transaction = ({ describe('Custom links', () => { it('shows empty message when no custom link is available', () => { jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: [], status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -59,7 +58,6 @@ describe('Custom links', () => { it('shows loading while custom links are fetched', () => { jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: [], status: useFetcher.FETCH_STATUS.LOADING, refetch: jest.fn(), @@ -81,7 +79,6 @@ describe('Custom links', () => { ] as CustomLinkType[]; jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: customLinks, status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -104,7 +101,6 @@ describe('Custom links', () => { ] as CustomLinkType[]; jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: customLinks, status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -129,7 +125,6 @@ describe('Custom links', () => { describe('create custom link buttons', () => { it('shows create button below empty message', () => { jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: [], status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -153,7 +148,6 @@ describe('Custom links', () => { ] as CustomLinkType[]; jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: customLinks, status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx index 7fc2907525663..0b0d5c8b1b8a2 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx @@ -61,7 +61,6 @@ const renderTransaction = async (transaction: Record) => { describe('TransactionActionMenu component', () => { beforeAll(() => { jest.spyOn(hooks, 'useFetcher').mockReturnValue({ - requestId: 'foo', data: [], status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx index 856fc0c3830f6..546b039f7ba8a 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx @@ -33,7 +33,6 @@ describe('useFetcher', () => { it('should have loading spinner initally', async () => { expect(hook.result.current).toEqual({ - requestId: '', data: undefined, error: undefined, refetch: expect.any(Function), @@ -45,7 +44,6 @@ describe('useFetcher', () => { jest.advanceTimersByTime(100); expect(hook.result.current).toEqual({ - requestId: '', data: undefined, error: undefined, refetch: expect.any(Function), @@ -58,7 +56,6 @@ describe('useFetcher', () => { await hook.waitForNextUpdate(); expect(hook.result.current).toEqual({ - requestId: expect.any(String), data: 'response from hook', error: undefined, refetch: expect.any(Function), @@ -85,7 +82,6 @@ describe('useFetcher', () => { it('should have loading spinner initally', async () => { expect(hook.result.current).toEqual({ - requestId: '', data: undefined, error: undefined, refetch: expect.any(Function), @@ -97,7 +93,6 @@ describe('useFetcher', () => { jest.advanceTimersByTime(100); expect(hook.result.current).toEqual({ - requestId: '', data: undefined, error: undefined, refetch: expect.any(Function), @@ -110,7 +105,6 @@ describe('useFetcher', () => { await hook.waitForNextUpdate(); expect(hook.result.current).toEqual({ - requestId: expect.any(String), data: undefined, error: expect.any(Error), refetch: expect.any(Function), @@ -135,7 +129,6 @@ describe('useFetcher', () => { } ); expect(hook.result.current).toEqual({ - requestId: expect.any(String), data: undefined, error: undefined, refetch: expect.any(Function), @@ -146,7 +139,6 @@ describe('useFetcher', () => { // assert: first response has loaded and should be rendered expect(hook.result.current).toEqual({ - requestId: expect.any(String), data: 'first response', error: undefined, refetch: expect.any(Function), @@ -166,7 +158,6 @@ describe('useFetcher', () => { // assert: while loading new data the previous data should still be rendered expect(hook.result.current).toEqual({ - requestId: expect.any(String), data: 'first response', error: undefined, refetch: expect.any(Function), @@ -178,7 +169,6 @@ describe('useFetcher', () => { // assert: "second response" has loaded and should be rendered expect(hook.result.current).toEqual({ - requestId: expect.any(String), data: 'second response', error: undefined, refetch: expect.any(Function), diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx index 1770001d57538..e427c5719aeb8 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx @@ -8,7 +8,6 @@ import { i18n } from '@kbn/i18n'; import React, { useEffect, useMemo, useState } from 'react'; import { IHttpFetchError } from 'src/core/public'; -import uuid from 'uuid'; import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; import { callApmApi, @@ -25,7 +24,6 @@ export enum FETCH_STATUS { } export interface FetcherResult { - requestId: string; data?: Data; status: FETCH_STATUS; error?: IHttpFetchError; @@ -77,7 +75,6 @@ export function useFetcher( >({ data: undefined, status: FETCH_STATUS.NOT_INITIATED, - requestId: '', }); const [counter, setCounter] = useState(0); const { rangeId } = useUrlParams(); @@ -86,7 +83,6 @@ export function useFetcher( let controller: AbortController = new AbortController(); async function doFetch() { - const requestId = uuid(); controller.abort(); controller = new AbortController(); @@ -102,7 +98,6 @@ export function useFetcher( } setResult((prevResult) => ({ - requestId: prevResult.requestId, data: preservePreviousData ? prevResult.data : undefined, // preserve data from previous state while loading next state status: FETCH_STATUS.LOADING, error: undefined, @@ -116,7 +111,6 @@ export function useFetcher( // aborted before updating the result. if (!signal.aborted) { setResult({ - requestId, data, status: FETCH_STATUS.SUCCESS, error: undefined, @@ -149,7 +143,6 @@ export function useFetcher( }); } setResult({ - requestId, data: undefined, status: FETCH_STATUS.FAILURE, error: e, From c49006780214b440d8ec5d07df32301d09c57a2c Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Fri, 12 Feb 2021 08:41:35 +0100 Subject: [PATCH 30/31] Revert "removing requestId" This reverts commit daf88fe86497582044aafea09b96424fa6a8161a. --- .../index.tsx | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 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 7575cdcea4aca..af2513e12dc19 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 @@ -14,6 +14,7 @@ import { import { i18n } from '@kbn/i18n'; import { orderBy } from 'lodash'; import React, { useState } from 'react'; +import uuid from 'uuid'; 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'; @@ -29,6 +30,7 @@ interface Props { const INITIAL_STATE = { transactionGroups: [], isAggregationAccurate: true, + requestId: '', }; type SortField = 'name' | 'latency' | 'throughput' | 'errorRate' | 'impact'; @@ -77,6 +79,11 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { latencyAggregationType, }, }, + }).then((response) => { + return { + requestId: uuid(), + ...response, + }; }); }, [ @@ -89,7 +96,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { ] ); - const { transactionGroups } = data; + const { transactionGroups, requestId } = data; const currentPageTransactionGroups = orderBy( transactionGroups, sort.field, @@ -127,19 +134,22 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { transactionNames, }, }, + isCachable: true, + }).then((result) => { + return { [requestId]: result }; }); } }, - // only fetches statistics when transaction names changes + // only fetches statistics when requestId changes or transaction names changes // eslint-disable-next-line react-hooks/exhaustive-deps - [transactionNames], - { preservePreviousData: false } + [requestId, transactionNames] ); const columns = getColumns({ serviceName, latencyAggregationType, - transactionGroupComparisonStatistics, + transactionGroupComparisonStatistics: + transactionGroupComparisonStatistics?.[requestId], }); const isLoading = From f7d2157bdcd3c0c7e60d5afe0b4e6283c6620d30 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Fri, 12 Feb 2021 09:30:29 +0100 Subject: [PATCH 31/31] Support for transaction duration metrics --- .../service_overview_transactions_table/index.tsx | 9 +++------ ..._service_transaction_group_comparison_statistics.ts | 7 ++----- .../lib/services/get_service_transaction_groups.ts | 5 ++--- x-pack/typings/elasticsearch/aggregations.d.ts | 10 +++++++--- 4 files changed, 14 insertions(+), 17 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 af2513e12dc19..a0facb2ddbedf 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 @@ -134,22 +134,19 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { transactionNames, }, }, - isCachable: true, - }).then((result) => { - return { [requestId]: result }; }); } }, // only fetches statistics when requestId changes or transaction names changes // eslint-disable-next-line react-hooks/exhaustive-deps - [requestId, transactionNames] + [requestId, transactionNames], + { preservePreviousData: false } ); const columns = getColumns({ serviceName, latencyAggregationType, - transactionGroupComparisonStatistics: - transactionGroupComparisonStatistics?.[requestId], + transactionGroupComparisonStatistics, }); const isLoading = diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts index 7fa3b2f79bc01..8c21fb65a37e5 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts @@ -9,7 +9,6 @@ import { keyBy } from 'lodash'; import { EVENT_OUTCOME, SERVICE_NAME, - TRANSACTION_DURATION, TRANSACTION_NAME, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; @@ -88,7 +87,7 @@ export async function getServiceTransactionGroupComparisonStatistics({ }, }, aggs: { - total_duration: { sum: { field: TRANSACTION_DURATION } }, + total_duration: { sum: { field } }, transaction_groups: { terms: { field: TRANSACTION_NAME, @@ -97,7 +96,7 @@ export async function getServiceTransactionGroupComparisonStatistics({ }, aggs: { transaction_group_total_duration: { - sum: { field: TRANSACTION_DURATION }, + sum: { field }, }, timeseries: { date_histogram: { @@ -112,9 +111,7 @@ export async function getServiceTransactionGroupComparisonStatistics({ aggs: { throughput_rate: { rate: { - field: TRANSACTION_DURATION, unit: 'minute', - mode: 'value_count', }, }, ...getLatencyAggregation(latencyAggregationType, field), 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 26127473dbc45..67ae37f93606e 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 @@ -8,7 +8,6 @@ import { EVENT_OUTCOME, SERVICE_NAME, - TRANSACTION_DURATION, TRANSACTION_NAME, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; @@ -78,7 +77,7 @@ export async function getServiceTransactionGroups({ }, }, aggs: { - total_duration: { sum: { field: TRANSACTION_DURATION } }, + total_duration: { sum: { field } }, transaction_groups: { terms: { field: TRANSACTION_NAME, @@ -87,7 +86,7 @@ export async function getServiceTransactionGroups({ }, aggs: { transaction_group_total_duration: { - sum: { field: TRANSACTION_DURATION }, + sum: { field }, }, ...getLatencyAggregation(latencyAggregationType, field), [EVENT_OUTCOME]: { diff --git a/x-pack/typings/elasticsearch/aggregations.d.ts b/x-pack/typings/elasticsearch/aggregations.d.ts index fa4e087f2106a..077399c596d54 100644 --- a/x-pack/typings/elasticsearch/aggregations.d.ts +++ b/x-pack/typings/elasticsearch/aggregations.d.ts @@ -191,10 +191,14 @@ export interface AggregationOptionsByType { format?: string; }; rate: { - field?: string; unit: 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'; - mode?: 'sum' | 'value_count'; - }; + } & ( + | { + field: string; + mode: 'sum' | 'value_count'; + } + | {} + ); } type AggregationType = keyof AggregationOptionsByType;