From f428b10a798531e8d2ada77656631063c17f16cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 10 Mar 2021 11:02:50 -0500 Subject: [PATCH] [APM] Adding comparison latency chart (#91339) * adding time comparison to latency chart * adding time comparison to latency chart * fixing TS * fixing api test * addressing PR comments * adding api test * addressing PR comments * fixing api test * rounding date diff * addressing PR comments * fixing api test * refactoring * fixing ts issue * fixing offset function * fixing offset function * addressing PR comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../shared/charts/latency_chart/index.tsx | 27 +- .../shared/time_comparison/index.tsx | 14 +- .../use_transaction_latency_chart_fetcher.ts | 12 + .../selectors/latency_chart_selector.test.ts | 166 ++++++------ .../selectors/latency_chart_selectors.ts | 111 ++++---- ...transaction_group_comparison_statistics.ts | 43 ++-- .../transactions/get_latency_charts/index.ts | 79 +++++- x-pack/plugins/apm/server/routes/services.ts | 13 +- .../plugins/apm/server/routes/transactions.ts | 29 ++- .../offset_previous_period_coordinate.test.ts | 14 +- .../offset_previous_period_coordinate.ts | 13 +- .../transactions/__snapshots__/latency.snap | 183 +++++++++++-- .../tests/transactions/latency.ts | 240 +++++++++++++----- ...ansactions_groups_comparison_statistics.ts | 33 ++- 14 files changed, 690 insertions(+), 287 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx index 3cccf543c3e60..3f61273729e64 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx @@ -13,6 +13,7 @@ import { LatencyAggregationType } from '../../../../../common/latency_aggregatio import { getDurationFormatter } from '../../../../../common/utils/formatters'; import { useLicenseContext } from '../../../../context/license/use_license_context'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useTheme } from '../../../../hooks/use_theme'; import { useTransactionLatencyChartsFetcher } from '../../../../hooks/use_transaction_latency_chart_fetcher'; import { TimeseriesChart } from '../../../shared/charts/timeseries_chart'; import { @@ -21,6 +22,7 @@ import { } from '../../../shared/charts/transaction_charts/helper'; import { MLHeader } from '../../../shared/charts/transaction_charts/ml_header'; import * as urlHelpers from '../../../shared/Links/url_helpers'; +import { getComparisonChartTheme } from '../../time_comparison/get_time_range_comparison'; interface Props { height?: number; @@ -32,10 +34,16 @@ const options: Array<{ value: LatencyAggregationType; text: string }> = [ { value: LatencyAggregationType.p99, text: '99th percentile' }, ]; +function filterNil(value: T | null | undefined): value is T { + return value != null; +} + export function LatencyChart({ height }: Props) { const history = useHistory(); + const theme = useTheme(); + const comparisonChartTheme = getComparisonChartTheme(theme); const { urlParams } = useUrlParams(); - const { latencyAggregationType } = urlParams; + const { latencyAggregationType, comparisonEnabled } = urlParams; const license = useLicenseContext(); const { @@ -43,9 +51,19 @@ export function LatencyChart({ height }: Props) { latencyChartsStatus, } = useTransactionLatencyChartsFetcher(); - const { latencyTimeseries, anomalyTimeseries, mlJobId } = latencyChartsData; + const { + currentPeriod, + previousPeriod, + anomalyTimeseries, + mlJobId, + } = latencyChartsData; + + const timeseries = [ + currentPeriod, + comparisonEnabled ? previousPeriod : undefined, + ].filter(filterNil); - const latencyMaxY = getMaxY(latencyTimeseries); + const latencyMaxY = getMaxY(timeseries); const latencyFormatter = getDurationFormatter(latencyMaxY); return ( @@ -99,7 +117,8 @@ export function LatencyChart({ height }: Props) { height={height} fetchStatus={latencyChartsStatus} id="latencyChart" - timeseries={latencyTimeseries} + customTheme={comparisonChartTheme} + timeseries={timeseries} yLabelFormat={getResponseTimeTickFormatter(latencyFormatter)} anomalyTimeseries={anomalyTimeseries} /> diff --git a/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx b/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx index 0b6c1a2c52a98..1769119593c0e 100644 --- a/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx @@ -84,12 +84,14 @@ function getSelectOptions({ }), }; - const dateDiff = getDateDifference({ - start: momentStart, - end: momentEnd, - unitOfTime: 'days', - precise: true, - }); + const dateDiff = Number( + getDateDifference({ + start: momentStart, + end: momentEnd, + unitOfTime: 'days', + precise: true, + }).toFixed(2) + ); const isRangeToNow = rangeTo === 'now'; 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 b92b812bdd430..16a82b1d4972b 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,6 +12,7 @@ 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 { getTimeRangeComparison } from '../components/shared/time_comparison/get_time_range_comparison'; export function useTransactionLatencyChartsFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); @@ -25,9 +26,16 @@ export function useTransactionLatencyChartsFetcher() { end, transactionName, latencyAggregationType, + comparisonType, }, } = useUrlParams(); + const { comparisonStart, comparisonEnd } = getTimeRangeComparison({ + start, + end, + comparisonType, + }); + const { data, error, status } = useFetcher( (callApmApi) => { if ( @@ -50,6 +58,8 @@ export function useTransactionLatencyChartsFetcher() { transactionType, transactionName, latencyAggregationType, + comparisonStart, + comparisonEnd, }, }, }); @@ -64,6 +74,8 @@ export function useTransactionLatencyChartsFetcher() { transactionName, transactionType, latencyAggregationType, + comparisonStart, + comparisonEnd, ] ); diff --git a/x-pack/plugins/apm/public/selectors/latency_chart_selector.test.ts b/x-pack/plugins/apm/public/selectors/latency_chart_selector.test.ts index d4c9ba0878f28..252ced2be5e0e 100644 --- a/x-pack/plugins/apm/public/selectors/latency_chart_selector.test.ts +++ b/x-pack/plugins/apm/public/selectors/latency_chart_selector.test.ts @@ -18,12 +18,19 @@ const theme = { euiColorVis5: 'red', euiColorVis7: 'black', euiColorVis9: 'yellow', + euiColorLightestShade: 'green', }, } as EuiTheme; const latencyChartData = { - overallAvgDuration: 1, - latencyTimeseries: [{ x: 1, y: 10 }], + currentPeriod: { + overallAvgDuration: 1, + latencyTimeseries: [{ x: 1, y: 10 }], + }, + previousPeriod: { + overallAvgDuration: 1, + latencyTimeseries: [{ x: 1, y: 10 }], + }, anomalyTimeseries: { jobId: '1', anomalyBoundaries: [{ x: 1, y: 2, y0: 1 }], @@ -36,69 +43,84 @@ describe('getLatencyChartSelector', () => { it('returns default values when data is undefined', () => { const latencyChart = getLatencyChartSelector({ theme }); expect(latencyChart).toEqual({ - latencyTimeseries: [], + currentPeriod: undefined, + previousPeriod: undefined, mlJobId: undefined, anomalyTimeseries: undefined, }); }); it('returns average timeseries', () => { - const { anomalyTimeseries, ...latencyWithouAnomaly } = latencyChartData; + const { anomalyTimeseries, ...latencyWithoutAnomaly } = latencyChartData; const latencyTimeseries = getLatencyChartSelector({ - latencyChart: latencyWithouAnomaly as LatencyChartsResponse, + latencyChart: latencyWithoutAnomaly as LatencyChartsResponse, theme, latencyAggregationType: LatencyAggregationType.avg, }); expect(latencyTimeseries).toEqual({ - latencyTimeseries: [ - { - title: 'Average', - data: [{ x: 1, y: 10 }], - legendValue: '1 μs', - type: 'linemark', - color: 'blue', - }, - ], + currentPeriod: { + title: 'Average', + data: [{ x: 1, y: 10 }], + legendValue: '1 μs', + type: 'linemark', + color: 'blue', + }, + + previousPeriod: { + color: 'green', + data: [{ x: 1, y: 10 }], + type: 'area', + title: 'Previous period', + }, }); }); it('returns 95th percentile timeseries', () => { - const { anomalyTimeseries, ...latencyWithouAnomaly } = latencyChartData; + const { anomalyTimeseries, ...latencyWithoutAnomaly } = latencyChartData; const latencyTimeseries = getLatencyChartSelector({ - latencyChart: latencyWithouAnomaly as LatencyChartsResponse, + latencyChart: latencyWithoutAnomaly as LatencyChartsResponse, theme, latencyAggregationType: LatencyAggregationType.p95, }); expect(latencyTimeseries).toEqual({ - latencyTimeseries: [ - { - title: '95th percentile', - data: [{ x: 1, y: 10 }], - titleShort: '95th', - type: 'linemark', - color: 'red', - }, - ], + currentPeriod: { + title: '95th percentile', + titleShort: '95th', + data: [{ x: 1, y: 10 }], + type: 'linemark', + color: 'red', + }, + previousPeriod: { + data: [{ x: 1, y: 10 }], + type: 'area', + color: 'green', + title: 'Previous period', + }, }); }); it('returns 99th percentile timeseries', () => { - const { anomalyTimeseries, ...latencyWithouAnomaly } = latencyChartData; + const { anomalyTimeseries, ...latencyWithoutAnomaly } = latencyChartData; const latencyTimeseries = getLatencyChartSelector({ - latencyChart: latencyWithouAnomaly as LatencyChartsResponse, + latencyChart: latencyWithoutAnomaly as LatencyChartsResponse, theme, latencyAggregationType: LatencyAggregationType.p99, }); + expect(latencyTimeseries).toEqual({ - latencyTimeseries: [ - { - title: '99th percentile', - data: [{ x: 1, y: 10 }], - titleShort: '99th', - type: 'linemark', - color: 'black', - }, - ], + currentPeriod: { + title: '99th percentile', + titleShort: '99th', + data: [{ x: 1, y: 10 }], + type: 'linemark', + color: 'black', + }, + previousPeriod: { + data: [{ x: 1, y: 10 }], + type: 'area', + color: 'green', + title: 'Previous period', + }, }); }); }); @@ -111,76 +133,52 @@ describe('getLatencyChartSelector', () => { latencyAggregationType: LatencyAggregationType.p99, }); expect(latencyTimeseries).toEqual({ + currentPeriod: { + title: '99th percentile', + titleShort: '99th', + data: [{ x: 1, y: 10 }], + type: 'linemark', + color: 'black', + }, + previousPeriod: { + data: [{ x: 1, y: 10 }], + type: 'area', + color: 'green', + title: 'Previous period', + }, + mlJobId: '1', anomalyTimeseries: { boundaries: [ { - color: 'rgba(0,0,0,0)', - areaSeriesStyle: { - point: { - opacity: 0, - }, - }, - data: [ - { - x: 1, - y: 1, - }, - ], + type: 'area', fit: 'lookahead', hideLegend: true, hideTooltipValue: true, stackAccessors: ['y'], + areaSeriesStyle: { point: { opacity: 0 } }, title: 'anomalyBoundariesLower', - type: 'area', + data: [{ x: 1, y: 1 }], + color: 'rgba(0,0,0,0)', }, { - color: 'rgba(0,0,255,0.5)', - areaSeriesStyle: { - point: { - opacity: 0, - }, - }, - data: [ - { - x: 1, - y: 1, - }, - ], + type: 'area', fit: 'lookahead', hideLegend: true, hideTooltipValue: true, stackAccessors: ['y'], + areaSeriesStyle: { point: { opacity: 0 } }, title: 'anomalyBoundariesUpper', - type: 'area', + data: [{ x: 1, y: 1 }], + color: 'rgba(0,0,255,0.5)', }, ], scores: { - color: 'yellow', - data: [ - { - x: 1, - x0: 2, - }, - ], title: 'anomalyScores', type: 'rectAnnotation', + data: [{ x: 1, x0: 2 }], + color: 'yellow', }, }, - latencyTimeseries: [ - { - color: 'black', - data: [ - { - x: 1, - y: 10, - }, - ], - title: '99th percentile', - titleShort: '99th', - type: 'linemark', - }, - ], - mlJobId: '1', }); }); }); diff --git a/x-pack/plugins/apm/public/selectors/latency_chart_selectors.ts b/x-pack/plugins/apm/public/selectors/latency_chart_selectors.ts index 858d44de8bb7a..2ee4a717106eb 100644 --- a/x-pack/plugins/apm/public/selectors/latency_chart_selectors.ts +++ b/x-pack/plugins/apm/public/selectors/latency_chart_selectors.ts @@ -16,7 +16,8 @@ import { APIReturnType } from '../services/rest/createCallApmApi'; export type LatencyChartsResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/latency'>; export interface LatencyChartData { - latencyTimeseries: Array>; + currentPeriod?: APMChartSpec; + previousPeriod?: APMChartSpec; mlJobId?: string; anomalyTimeseries?: { boundaries: APMChartSpec[]; scores: APMChartSpec }; } @@ -29,20 +30,23 @@ export function getLatencyChartSelector({ latencyChart?: LatencyChartsResponse; theme: EuiTheme; latencyAggregationType?: string; -}): LatencyChartData { - if (!latencyChart?.latencyTimeseries || !latencyAggregationType) { - return { - latencyTimeseries: [], - mlJobId: undefined, - anomalyTimeseries: undefined, - }; +}): Partial { + if ( + !latencyChart?.currentPeriod.latencyTimeseries || + !latencyAggregationType + ) { + return {}; } return { - latencyTimeseries: getLatencyTimeseries({ - latencyChart, + currentPeriod: getLatencyTimeseries({ + latencyChart: latencyChart.currentPeriod, theme, latencyAggregationType, }), + previousPeriod: getPreviousPeriodTimeseries({ + previousPeriod: latencyChart.previousPeriod, + theme, + }), mlJobId: latencyChart.anomalyTimeseries?.jobId, anomalyTimeseries: getAnomalyTimeseries({ anomalyTimeseries: latencyChart.anomalyTimeseries, @@ -51,12 +55,30 @@ export function getLatencyChartSelector({ }; } +function getPreviousPeriodTimeseries({ + previousPeriod, + theme, +}: { + previousPeriod: LatencyChartsResponse['previousPeriod']; + theme: EuiTheme; +}) { + return { + data: previousPeriod.latencyTimeseries ?? [], + type: 'area', + color: theme.eui.euiColorLightestShade, + title: i18n.translate( + 'xpack.apm.serviceOverview.latencyChartTitle.previousPeriodLabel', + { defaultMessage: 'Previous period' } + ), + }; +} + function getLatencyTimeseries({ latencyChart, theme, latencyAggregationType, }: { - latencyChart: LatencyChartsResponse; + latencyChart: LatencyChartsResponse['currentPeriod']; theme: EuiTheme; latencyAggregationType: string; }) { @@ -65,49 +87,42 @@ function getLatencyTimeseries({ switch (latencyAggregationType) { case 'avg': { - return [ - { - title: i18n.translate( - 'xpack.apm.transactions.latency.chart.averageLabel', - { defaultMessage: 'Average' } - ), - data: latencyTimeseries, - legendValue: asDuration(overallAvgDuration), - type: 'linemark', - color: theme.eui.euiColorVis1, - }, - ]; + return { + title: i18n.translate( + 'xpack.apm.transactions.latency.chart.averageLabel', + { defaultMessage: 'Average' } + ), + data: latencyTimeseries, + legendValue: asDuration(overallAvgDuration), + type: 'linemark', + color: theme.eui.euiColorVis1, + }; } case 'p95': { - return [ - { - title: i18n.translate( - 'xpack.apm.transactions.latency.chart.95thPercentileLabel', - { defaultMessage: '95th percentile' } - ), - titleShort: '95th', - data: latencyTimeseries, - type: 'linemark', - color: theme.eui.euiColorVis5, - }, - ]; + return { + title: i18n.translate( + 'xpack.apm.transactions.latency.chart.95thPercentileLabel', + { defaultMessage: '95th percentile' } + ), + titleShort: '95th', + data: latencyTimeseries, + type: 'linemark', + color: theme.eui.euiColorVis5, + }; } case 'p99': { - return [ - { - title: i18n.translate( - 'xpack.apm.transactions.latency.chart.99thPercentileLabel', - { defaultMessage: '99th percentile' } - ), - titleShort: '99th', - data: latencyTimeseries, - type: 'linemark', - color: theme.eui.euiColorVis7, - }, - ]; + return { + title: i18n.translate( + 'xpack.apm.transactions.latency.chart.99thPercentileLabel', + { defaultMessage: '99th percentile' } + ), + titleShort: '99th', + data: latencyTimeseries, + type: 'linemark', + color: theme.eui.euiColorVis7, + }; } } - return []; } function getAnomalyTimeseries({ 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 ebdb5fb5bbdc2..54e882d1dd6da 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 @@ -47,7 +47,6 @@ export async function getServiceTransactionGroupComparisonStatistics({ latencyAggregationType, start, end, - getOffsetXCoordinate, }: { environment?: string; kuery?: string; @@ -60,7 +59,6 @@ export async function getServiceTransactionGroupComparisonStatistics({ latencyAggregationType: LatencyAggregationType; start: number; end: number; - getOffsetXCoordinate?: (timeseries: Coordinate[]) => Coordinate[]; }): Promise< Array<{ transactionName: string; @@ -175,15 +173,9 @@ export async function getServiceTransactionGroupComparisonStatistics({ bucket.transaction_group_total_duration.value || 0; return { transactionName, - latency: getOffsetXCoordinate - ? getOffsetXCoordinate(latency) - : latency, - throughput: getOffsetXCoordinate - ? getOffsetXCoordinate(throughput) - : throughput, - errorRate: getOffsetXCoordinate - ? getOffsetXCoordinate(errorRate) - : errorRate, + latency, + throughput, + errorRate, impact: totalDuration ? (transactionGroupTotalDuration * 100) / totalDuration : 0, @@ -244,12 +236,6 @@ export async function getServiceTransactionGroupComparisonStatisticsPeriods({ ...commonProps, start: comparisonStart, end: comparisonEnd, - getOffsetXCoordinate: (timeseries: Coordinate[]) => - offsetPreviousPeriodCoordinates({ - currentPeriodStart: start, - previousPeriodStart: comparisonStart, - previousPeriodTimeseries: timeseries, - }), }) : []; @@ -258,8 +244,29 @@ export async function getServiceTransactionGroupComparisonStatisticsPeriods({ previousPeriodPromise, ]); + const firtCurrentPeriod = currentPeriod.length ? currentPeriod[0] : undefined; + return { currentPeriod: keyBy(currentPeriod, 'transactionName'), - previousPeriod: keyBy(previousPeriod, 'transactionName'), + previousPeriod: keyBy( + previousPeriod.map((data) => { + return { + ...data, + errorRate: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: firtCurrentPeriod?.errorRate, + previousPeriodTimeseries: data.errorRate, + }), + throughput: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: firtCurrentPeriod?.throughput, + previousPeriodTimeseries: data.throughput, + }), + latency: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: firtCurrentPeriod?.latency, + previousPeriodTimeseries: data.latency, + }), + }; + }), + 'transactionName' + ), }; } diff --git a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts index 0be72c95b0a60..31b5c6ff64dfd 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts @@ -25,6 +25,7 @@ import { } from '../../../lib/helpers/aggregated_transactions'; import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../../lib/helpers/setup_request'; +import { offsetPreviousPeriodCoordinates } from '../../../utils/offset_previous_period_coordinate'; import { withApmSpan } from '../../../utils/with_apm_span'; import { getLatencyAggregation, @@ -43,17 +44,21 @@ function searchLatency({ setup, searchAggregatedTransactions, latencyAggregationType, + start, + end, }: { environment?: string; kuery?: string; serviceName: string; transactionType: string | undefined; transactionName: string | undefined; - setup: Setup & SetupTimeRange; + setup: Setup; searchAggregatedTransactions: boolean; latencyAggregationType: LatencyAggregationType; + start: number; + end: number; }) { - const { start, end, apmEventClient } = setup; + const { apmEventClient } = setup; const { intervalString } = getBucketSize({ start, end }); const filter: ESFilter[] = [ @@ -119,15 +124,19 @@ export function getLatencyTimeseries({ setup, searchAggregatedTransactions, latencyAggregationType, + start, + end, }: { environment?: string; kuery?: string; serviceName: string; transactionType: string | undefined; transactionName: string | undefined; - setup: Setup & SetupTimeRange; + setup: Setup; searchAggregatedTransactions: boolean; latencyAggregationType: LatencyAggregationType; + start: number; + end: number; }) { return withApmSpan('get_latency_charts', async () => { const response = await searchLatency({ @@ -139,6 +148,8 @@ export function getLatencyTimeseries({ setup, searchAggregatedTransactions, latencyAggregationType, + start, + end, }); if (!response.aggregations) { @@ -162,3 +173,65 @@ export function getLatencyTimeseries({ }; }); } + +export async function getLatencyPeriods({ + serviceName, + transactionType, + transactionName, + setup, + searchAggregatedTransactions, + latencyAggregationType, + comparisonStart, + comparisonEnd, +}: { + serviceName: string; + transactionType: string | undefined; + transactionName: string | undefined; + setup: Setup & SetupTimeRange; + searchAggregatedTransactions: boolean; + latencyAggregationType: LatencyAggregationType; + comparisonStart?: number; + comparisonEnd?: number; +}) { + const { start, end } = setup; + const options = { + serviceName, + transactionType, + transactionName, + setup, + searchAggregatedTransactions, + }; + + const currentPeriodPromise = getLatencyTimeseries({ + ...options, + start, + end, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, + }); + + const previousPeriodPromise = + comparisonStart && comparisonEnd + ? getLatencyTimeseries({ + ...options, + start: comparisonStart, + end: comparisonEnd, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, + }) + : { latencyTimeseries: [], overallAvgDuration: null }; + + const [currentPeriod, previousPeriod] = await Promise.all([ + currentPeriodPromise, + previousPeriodPromise, + ]); + + return { + currentPeriod, + previousPeriod: { + ...previousPeriod, + latencyTimeseries: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentPeriod.latencyTimeseries, + previousPeriodTimeseries: previousPeriod.latencyTimeseries, + }), + }, + }; +} diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index 58e6f6ccadc0a..a84c8dc274248 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -408,19 +408,16 @@ export const serviceThroughputRoute = createRoute({ ...commonProps, start: comparisonStart, end: comparisonEnd, - }).then((coordinates) => - offsetPreviousPeriodCoordinates({ - currentPeriodStart: start, - previousPeriodStart: comparisonStart, - previousPeriodTimeseries: coordinates, - }) - ) + }) : [], ]); return { currentPeriod, - previousPeriod, + previousPeriod: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentPeriod, + previousPeriodTimeseries: previousPeriod, + }), }; }, }); diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index f7ff93d104647..1571efb373cc9 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -19,14 +19,14 @@ import { getServiceTransactionGroupComparisonStatisticsPeriods } from '../lib/se import { getTransactionBreakdown } from '../lib/transactions/breakdown'; import { getTransactionDistribution } from '../lib/transactions/distribution'; import { getAnomalySeries } from '../lib/transactions/get_anomaly_data'; -import { getLatencyTimeseries } from '../lib/transactions/get_latency_charts'; +import { getLatencyPeriods } from '../lib/transactions/get_latency_charts'; import { getThroughputCharts } from '../lib/transactions/get_throughput_charts'; import { getTransactionGroupList } from '../lib/transaction_groups'; import { getErrorRate } from '../lib/transaction_groups/get_error_rate'; import { createRoute } from './create_route'; import { - environmentRt, comparisonRangeRt, + environmentRt, rangeRt, kueryRt, } from './default_api_types'; @@ -179,16 +179,12 @@ export const transactionLatencyChartsRoute = createRoute({ serviceName: t.string, }), query: t.intersection([ - t.partial({ - transactionName: t.string, - }), t.type({ transactionType: t.string, latencyAggregationType: latencyAggregationTypeRt, }), - environmentRt, - kueryRt, - rangeRt, + t.partial({ transactionName: t.string }), + t.intersection([environmentRt, kueryRt, rangeRt, comparisonRangeRt]), ]), }), options: { tags: ['access:apm'] }, @@ -202,6 +198,8 @@ export const transactionLatencyChartsRoute = createRoute({ transactionType, transactionName, latencyAggregationType, + comparisonStart, + comparisonEnd, } = context.params.query; const searchAggregatedTransactions = await getSearchAggregatedTransactions( @@ -219,10 +217,15 @@ export const transactionLatencyChartsRoute = createRoute({ logger, }; - const [latencyData, anomalyTimeseries] = await Promise.all([ - getLatencyTimeseries({ + const [ + { currentPeriod, previousPeriod }, + anomalyTimeseries, + ] = await Promise.all([ + getLatencyPeriods({ ...options, latencyAggregationType: latencyAggregationType as LatencyAggregationType, + comparisonStart, + comparisonEnd, }), getAnomalySeries(options).catch((error) => { logger.warn(`Unable to retrieve anomalies for latency charts.`); @@ -231,11 +234,9 @@ export const transactionLatencyChartsRoute = createRoute({ }), ]); - const { latencyTimeseries, overallAvgDuration } = latencyData; - return { - latencyTimeseries, - overallAvgDuration, + currentPeriod, + previousPeriod, anomalyTimeseries, }; }, diff --git a/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.test.ts b/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.test.ts index 6436c7c5193ec..f965751333838 100644 --- a/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.test.ts +++ b/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.test.ts @@ -7,16 +7,16 @@ import { Coordinate } from '../../typings/timeseries'; import { offsetPreviousPeriodCoordinates } from './offset_previous_period_coordinate'; -const previousPeriodStart = new Date('2021-01-27T14:45:00.000Z').valueOf(); -const currentPeriodStart = new Date('2021-01-28T14:45:00.000Z').valueOf(); +const currentPeriodTimeseries: Coordinate[] = [ + { x: new Date('2021-01-28T14:45:00.000Z').valueOf(), y: 0 }, +]; describe('mergePeriodsTimeseries', () => { describe('returns empty array', () => { it('when previous timeseries is not defined', () => { expect( offsetPreviousPeriodCoordinates({ - currentPeriodStart, - previousPeriodStart, + currentPeriodTimeseries, previousPeriodTimeseries: undefined, }) ).toEqual([]); @@ -25,8 +25,7 @@ describe('mergePeriodsTimeseries', () => { it('when previous timeseries is empty', () => { expect( offsetPreviousPeriodCoordinates({ - currentPeriodStart, - previousPeriodStart, + currentPeriodTimeseries, previousPeriodTimeseries: [], }) ).toEqual([]); @@ -43,8 +42,7 @@ describe('mergePeriodsTimeseries', () => { expect( offsetPreviousPeriodCoordinates({ - currentPeriodStart, - previousPeriodStart, + currentPeriodTimeseries, previousPeriodTimeseries, }) ).toEqual([ diff --git a/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.ts b/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.ts index 7ca153ec6181f..4d095a79394a1 100644 --- a/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.ts +++ b/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.ts @@ -9,19 +9,20 @@ import moment from 'moment'; import { Coordinate } from '../../typings/timeseries'; export function offsetPreviousPeriodCoordinates({ - currentPeriodStart, - previousPeriodStart, + currentPeriodTimeseries, previousPeriodTimeseries, }: { - currentPeriodStart: number; - previousPeriodStart: number; + currentPeriodTimeseries?: Coordinate[]; previousPeriodTimeseries?: Coordinate[]; }) { - if (!previousPeriodTimeseries) { + if (!previousPeriodTimeseries?.length) { return []; } + const currentPeriodStart = currentPeriodTimeseries?.length + ? currentPeriodTimeseries[0].x + : 0; - const dateDiff = currentPeriodStart - previousPeriodStart; + const dateDiff = currentPeriodStart - previousPeriodTimeseries[0].x; return previousPeriodTimeseries.map(({ x, y }) => { const offsetX = moment(x).add(dateDiff).valueOf(); diff --git a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap index 11c557fd02e38..468776bf692f4 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap @@ -1,31 +1,182 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded when not defined environment is selected should return the correct anomaly boundaries 1`] = ` +exports[`APM API tests basic apm_8.0.0 Latency with a basic license when data is loaded time comparison returns some data 1`] = ` Array [ Object { - "x": 1607436000000, - "y": 0, - "y0": 0, + "x": 1607436780000, + "y": 51029, }, Object { - "x": 1607436900000, - "y": 0, - "y0": 0, + "x": 1607436820000, + "y": 38124, + }, + Object { + "x": 1607436860000, + "y": 16327, + }, + Object { + "x": 1607436980000, + "y": 35617.5, + }, + Object { + "x": 1607436990000, + "y": 34599.75, + }, + Object { + "x": 1607437100000, + "y": 26980, + }, + Object { + "x": 1607437110000, + "y": 42808, + }, + Object { + "x": 1607437130000, + "y": 22230.5, + }, + Object { + "x": 1607437220000, + "y": 34973, + }, + Object { + "x": 1607437230000, + "y": 19284.2, + }, + Object { + "x": 1607437240000, + "y": 9280, + }, + Object { + "x": 1607437250000, + "y": 42777, + }, + Object { + "x": 1607437260000, + "y": 10702, + }, + Object { + "x": 1607437340000, + "y": 22452, + }, + Object { + "x": 1607437470000, + "y": 14495.5, + }, + Object { + "x": 1607437480000, + "y": 11644.5714285714, + }, + Object { + "x": 1607437570000, + "y": 17359.6666666667, + }, + Object { + "x": 1607437590000, + "y": 11394.2, + }, +] +`; + +exports[`APM API tests basic apm_8.0.0 Latency with a basic license when data is loaded time comparison returns some data 2`] = ` +Array [ + Object { + "x": 1607436800000, + "y": 23448.25, + }, + Object { + "x": 1607436820000, + "y": 25181, + }, + Object { + "x": 1607436840000, + "y": 16834, + }, + Object { + "x": 1607436910000, + "y": 21582, + }, + Object { + "x": 1607437040000, + "y": 31800, + }, + Object { + "x": 1607437050000, + "y": 21341, + }, + Object { + "x": 1607437060000, + "y": 21108.5, + }, + Object { + "x": 1607437150000, + "y": 12147.3333333333, + }, + Object { + "x": 1607437160000, + "y": 23941.5, + }, + Object { + "x": 1607437180000, + "y": 18244, + }, + Object { + "x": 1607437240000, + "y": 24359.5, + }, + Object { + "x": 1607437280000, + "y": 27767, + }, + Object { + "x": 1607437290000, + "y": 21909.6666666667, + }, + Object { + "x": 1607437390000, + "y": 31521, + }, + Object { + "x": 1607437410000, + "y": 20227.5, + }, + Object { + "x": 1607437420000, + "y": 18664, + }, + Object { + "x": 1607437510000, + "y": 14197.5, + }, + Object { + "x": 1607437520000, + "y": 19199.8571428571, + }, + Object { + "x": 1607437540000, + "y": 63745.75, + }, + Object { + "x": 1607437640000, + "y": 63220, + }, + Object { + "x": 1607437660000, + "y": 20040, }, ] `; -exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded with environment selected and empty kuery filter should return a non-empty anomaly series 1`] = ` +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded when not defined environments is seleted should return the correct anomaly boundaries 1`] = ` Array [ Object { "x": 1607436000000, - "y": 1625128.56211579, - "y0": 7533.02707532227, + "y": 0, + "y0": 0, }, Object { "x": 1607436900000, - "y": 1660982.24115757, - "y0": 5732.00699123528, + "y": 0, + "y0": 0, }, ] `; @@ -34,13 +185,13 @@ exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license Array [ Object { "x": 1607436000000, - "y": 1625128.56211579, - "y0": 7533.02707532227, + "y": 136610.507897203, + "y0": 22581.5157631454, }, Object { "x": 1607436900000, - "y": 1660982.24115757, - "y0": 5732.00699123528, + "y": 136610.507897203, + "y0": 22581.5157631454, }, ] `; diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/tests/transactions/latency.ts index 4b9409ce6f16b..cb72f7f9844bc 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/latency.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/latency.ts @@ -6,21 +6,22 @@ */ import expect from '@kbn/expect'; +import url from 'url'; +import moment from 'moment'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; import { registry } from '../../common/registry'; +type LatencyChartReturnType = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/latency'>; + export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const archiveName = 'apm_8.0.0'; - const range = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(range.start); - const end = encodeURIComponent(range.end); + const { start, end } = archives_metadata[archiveName]; registry.when( 'Latency with a basic license when data is not loaded ', @@ -28,7 +29,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { it('returns 400 when latencyAggregationType is not informed', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=request` + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + query: { + start, + end, + transactionType: 'request', + environment: 'testing', + }, + }) ); expect(response.status).to.be(400); @@ -36,7 +45,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns 400 when transactionType is not informed', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&latencyAggregationType=avg` + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'avg', + environment: 'testing', + }, + }) ); expect(response.status).to.be(400); @@ -44,13 +61,25 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles the empty state', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&latencyAggregationType=avg&transactionType=request` + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'avg', + transactionType: 'request', + environment: 'testing', + }, + }) ); expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).to.be(null); - expect(response.body.latencyTimeseries.length).to.be(0); + const latencyChartReturn = response.body as LatencyChartReturnType; + + expect(latencyChartReturn.currentPeriod.overallAvgDuration).to.be(null); + expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be(0); + expect(latencyChartReturn.previousPeriod.latencyTimeseries.length).to.be(0); }); } ); @@ -64,42 +93,113 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('average latency type', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=request&latencyAggregationType=avg` + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'avg', + transactionType: 'request', + environment: 'testing', + }, + }) ); }); it('returns average duration and timeseries', async () => { expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn.currentPeriod.overallAvgDuration).not.to.be(null); + expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be.eql(61); }); }); describe('95th percentile latency type', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=request&latencyAggregationType=p95` + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'p95', + transactionType: 'request', + environment: 'testing', + }, + }) ); }); it('returns average duration and timeseries', async () => { expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn.currentPeriod.overallAvgDuration).not.to.be(null); + expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be.eql(61); }); }); describe('99th percentile latency type', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=request&latencyAggregationType=p99` + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'p99', + transactionType: 'request', + environment: 'testing', + }, + }) ); }); it('returns average duration and timeseries', async () => { expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn.currentPeriod.overallAvgDuration).not.to.be(null); + expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be.eql(61); + }); + }); + + describe('time comparison', () => { + before(async () => { + response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + query: { + latencyAggregationType: 'avg', + transactionType: 'request', + start: moment(end).subtract(15, 'minutes').toISOString(), + end, + comparisonStart: start, + comparisonEnd: moment(start).add(15, 'minutes').toISOString(), + }, + }) + ); + }); + + it('returns some data', async () => { + expect(response.status).to.be(200); + const latencyChartReturn = response.body as LatencyChartReturnType; + const currentPeriodNonNullDataPoints = latencyChartReturn.currentPeriod.latencyTimeseries.filter( + ({ y }) => y !== null + ); + expect(currentPeriodNonNullDataPoints.length).to.be.greaterThan(0); + const previousPeriodNonNullDataPoints = latencyChartReturn.previousPeriod.latencyTimeseries.filter( + ({ y }) => y !== null + ); + expect(previousPeriodNonNullDataPoints.length).to.be.greaterThan(0); + + expectSnapshot(currentPeriodNonNullDataPoints).toMatch(); + expectSnapshot(previousPeriodNonNullDataPoints).toMatch(); + }); + + it('matches x-axis on current period and previous period', () => { + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn.currentPeriod.latencyTimeseries.map(({ x }) => x)).to.be.eql( + latencyChartReturn.previousPeriod.latencyTimeseries.map(({ x }) => x) + ); }); }); } @@ -116,7 +216,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('without an environment', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` + url.format({ + pathname: `/api/apm/services/opbeans-java/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'avg', + transactionType, + }, + }) ); }); @@ -128,7 +236,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('with environment selected', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?environment=production&start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` + url.format({ + pathname: `/api/apm/services/opbeans-python/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'avg', + transactionType, + environment: 'production', + }, + }) ); }); @@ -137,24 +254,37 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('should return the ML job id for anomalies of the selected environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn).to.have.property('anomalyTimeseries'); + expect(latencyChartReturn.anomalyTimeseries).to.have.property('jobId'); + expectSnapshot(latencyChartReturn.anomalyTimeseries?.jobId).toMatchInline( `"apm-production-1369-high_mean_transaction_duration"` ); }); it('should return a non-empty anomaly series', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn).to.have.property('anomalyTimeseries'); + expect(latencyChartReturn.anomalyTimeseries?.anomalyBoundaries?.length).to.be.greaterThan( + 0 + ); + expectSnapshot(latencyChartReturn.anomalyTimeseries?.anomalyBoundaries).toMatch(); }); }); - describe('when not defined environment is selected', () => { + describe('when not defined environments is seleted', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-python/transactions/charts/latency?environment=ENVIRONMENT_NOT_DEFINED&start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` + url.format({ + pathname: `/api/apm/services/opbeans-python/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'avg', + transactionType, + environment: 'ENVIRONMENT_NOT_DEFINED', + }, + }) ); }); @@ -163,23 +293,34 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('should return the ML job id for anomalies with no defined environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn).to.have.property('anomalyTimeseries'); + expect(latencyChartReturn.anomalyTimeseries).to.have.property('jobId'); + expectSnapshot(latencyChartReturn.anomalyTimeseries?.jobId).toMatchInline( `"apm-environment_not_defined-5626-high_mean_transaction_duration"` ); }); it('should return the correct anomaly boundaries', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn).to.have.property('anomalyTimeseries'); + expectSnapshot(latencyChartReturn.anomalyTimeseries?.anomalyBoundaries).toMatch(); }); }); describe('with all environments selected', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?environment=ENVIRONMENT_ALL&start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` + url.format({ + pathname: `/api/apm/services/opbeans-java/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'avg', + transactionType, + environment: 'ENVIRONMENT_ALL', + }, + }) ); }); @@ -188,33 +329,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('should not return anomaly timeseries data', () => { - expect(response.body).to.not.have.property('anomalyTimeseries'); - }); - }); - - describe('with environment selected and empty kuery filter', () => { - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?environment=production&start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` - ); - }); - - it('should have a successful response', () => { - expect(response.status).to.eql(200); - }); - - it('should return the ML job id for anomalies of the selected environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( - `"apm-production-1369-high_mean_transaction_duration"` - ); - }); - - it('should return a non-empty anomaly series', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn).to.not.have.property('anomalyTimeseries'); }); }); } 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 1065bc9405838..72fb0e832412d 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 @@ -77,8 +77,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(Object.keys(currentPeriod).sort()).to.be.eql(transactionNames.sort()); - const currentPeriodItems = Object.values(currentPeriod).map((data) => data); - const previousPeriodItems = Object.values(previousPeriod).map((data) => data); + const currentPeriodItems = Object.values(currentPeriod); + const previousPeriodItems = Object.values(previousPeriod); expect(previousPeriodItems.length).to.be.eql(0); @@ -210,8 +210,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns correct latency data', () => { - const currentPeriodItems = Object.values(currentPeriod).map((data) => data); - const previousPeriodItems = Object.values(previousPeriod).map((data) => data); + const currentPeriodItems = Object.values(currentPeriod); + const previousPeriodItems = Object.values(previousPeriod); const currentPeriodFirstItem = currentPeriodItems[0]; const previousPeriodFirstItem = previousPeriodItems[0]; @@ -227,8 +227,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns correct throughput data', () => { - const currentPeriodItems = Object.values(currentPeriod).map((data) => data); - const previousPeriodItems = Object.values(previousPeriod).map((data) => data); + const currentPeriodItems = Object.values(currentPeriod); + const previousPeriodItems = Object.values(previousPeriod); const currentPeriodFirstItem = currentPeriodItems[0]; const previousPeriodFirstItem = previousPeriodItems[0]; @@ -244,8 +244,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns correct error rate data', () => { - const currentPeriodItems = Object.values(currentPeriod).map((data) => data); - const previousPeriodItems = Object.values(previousPeriod).map((data) => data); + const currentPeriodItems = Object.values(currentPeriod); + const previousPeriodItems = Object.values(previousPeriod); const currentPeriodFirstItem = currentPeriodItems[0]; const previousPeriodFirstItem = previousPeriodItems[0]; @@ -256,13 +256,26 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect( removeEmptyCoordinates(previousPeriodFirstItem.errorRate).length ).to.be.greaterThan(0); + expectSnapshot(currentPeriodFirstItem.errorRate).toMatch(); expectSnapshot(previousPeriodFirstItem.errorRate).toMatch(); }); + it('matches x-axis on current period and previous period', () => { + const currentPeriodItems = Object.values(currentPeriod); + const previousPeriodItems = Object.values(previousPeriod); + + const currentPeriodFirstItem = currentPeriodItems[0]; + const previousPeriodFirstItem = previousPeriodItems[0]; + + expect(currentPeriodFirstItem.errorRate.map(({ x }) => x)).to.be.eql( + previousPeriodFirstItem.errorRate.map(({ x }) => x) + ); + }); + it('returns correct impact data', () => { - const currentPeriodItems = Object.values(currentPeriod).map((data) => data); - const previousPeriodItems = Object.values(previousPeriod).map((data) => data); + const currentPeriodItems = Object.values(currentPeriod); + const previousPeriodItems = Object.values(previousPeriod); const currentPeriodFirstItem = currentPeriodItems[0]; const previousPeriodFirstItem = previousPeriodItems[0];