From d111c27808e16f9615e8ffd91f315f14339a0b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Thu, 29 Apr 2021 16:57:06 -0400 Subject: [PATCH] [APM] Transaction duration histogram buckets without samples are clickable (#98540) * adding no samples label on distribution chart * adding custom tooltip * addressing PR comments --- .../Distribution/custom_tooltip.tsx | 68 +++++++++++++++++++ .../Distribution/index.tsx | 36 ++++++---- 2 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/app/transaction_details/Distribution/custom_tooltip.tsx diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/custom_tooltip.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/custom_tooltip.tsx new file mode 100644 index 00000000000000..ba007015b25f86 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/custom_tooltip.tsx @@ -0,0 +1,68 @@ +/* + * 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 { TooltipInfo } from '@elastic/charts'; +import { EuiIcon, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { TimeFormatter } from '../../../../../common/utils/formatters'; +import { useTheme } from '../../../../hooks/use_theme'; +import { formatYLong, IChartPoint } from './'; + +export function CustomTooltip( + props: TooltipInfo & { + serie?: IChartPoint; + isSamplesEmpty: boolean; + timeFormatter: TimeFormatter; + } +) { + const theme = useTheme(); + const { values, header, serie, isSamplesEmpty, timeFormatter } = props; + const { color, value } = values[0]; + + let headerTitle = `${timeFormatter(header?.value)}`; + if (serie) { + const xFormatted = timeFormatter(serie.x); + const x0Formatted = timeFormatter(serie.x0); + headerTitle = `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`; + } + + return ( +
+ <> +
{headerTitle}
+
+
+
+
+
+
+ {formatYLong(value)} + {value} +
+
+
+ + {isSamplesEmpty && ( +
+ + + {i18n.translate( + 'xpack.apm.transactionDetails.transactionsDurationDistributionChart.noSamplesAvailable', + { defaultMessage: 'No samples available' } + )} + +
+ )} +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/index.tsx index 6d621afc99e53c..c7dae6ce3d1d43 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/index.tsx @@ -15,13 +15,13 @@ import { ScaleType, Settings, SettingsSpec, - TooltipValue, + TooltipInfo, XYChartSeriesIdentifier, } from '@elastic/charts'; import { EuiIconTip, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import d3 from 'd3'; -import { isEmpty } from 'lodash'; +import { isEmpty, keyBy } from 'lodash'; import React from 'react'; import { ValuesType } from 'utility-types'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; @@ -32,12 +32,13 @@ import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { unit } from '../../../../style/variables'; import { ChartContainer } from '../../../shared/charts/chart_container'; import { EmptyMessage } from '../../../shared/EmptyMessage'; +import { CustomTooltip } from './custom_tooltip'; type TransactionDistributionAPIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/distribution'>; type DistributionBucket = TransactionDistributionAPIResponse['buckets'][0]; -interface IChartPoint { +export interface IChartPoint { x0: number; x: number; y: number; @@ -78,7 +79,7 @@ const formatYShort = (t: number) => { ); }; -const formatYLong = (t: number) => { +export const formatYLong = (t: number) => { return i18n.translate( 'xpack.apm.transactionDetails.transactionsDurationDistributionChart.transactionTypeUnitLongLabel', { @@ -133,15 +134,22 @@ export function TransactionDistribution({ const xMax = d3.max(buckets, (d) => d.x0) || 0; const timeFormatter = getDurationFormatter(xMax); - const tooltipProps: SettingsSpec['tooltip'] = { - headerFormatter: (tooltip: TooltipValue) => { - const serie = buckets.find((bucket) => bucket.x0 === tooltip.value); - if (serie) { - const xFormatted = timeFormatter(serie.x); - const x0Formatted = timeFormatter(serie.x0); - return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`; - } - return `${timeFormatter(tooltip.value)}`; + const distributionMap = keyBy(distribution?.buckets, 'key'); + const bucketsMap = keyBy(buckets, 'x0'); + + const tooltip: SettingsSpec['tooltip'] = { + customTooltip: (props: TooltipInfo) => { + const datum = props.header?.datum as IChartPoint; + const selectedDistribution = distributionMap[datum?.x0]; + const serie = bucketsMap[datum?.x0]; + return ( + + ); }, }; @@ -192,7 +200,7 @@ export function TransactionDistribution({ {selectedBucket && (