From df20e73bcf11623f54fc446790126161711d50b5 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 8 Oct 2020 16:55:47 +0200 Subject: [PATCH 01/15] adding elastic charts --- .../ErrorGroupDetails/Distribution/index.tsx | 140 +++++++++++----- .../TransactionDetails/Distribution/index.tsx | 155 ++++++++++++++---- .../elastic_charts.tsx | 104 ++++++++++++ .../shared/TransactionBreakdown/index.tsx | 7 + .../elastic_chart.tsx | 43 +++++ .../ErroneousTransactionsRateChart/index.tsx | 70 ++++---- .../TransactionCharts/elastic_chart.tsx | 107 ++++++++++++ .../shared/charts/TransactionCharts/index.tsx | 125 ++++++++------ .../shared/charts/annotations/index.tsx | 45 +++++ .../shared/charts/line_chart/index.tsx | 107 ++++++++++++ .../apm/public/context/ChartsSyncContext.tsx | 2 + .../public/context/charts_sync_context.tsx | 26 +++ .../apm/public/hooks/use_annotations.ts | 38 +++++ .../apm/public/hooks/use_charts_sync.tsx | 18 ++ 14 files changed, 834 insertions(+), 153 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/elastic_charts.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/elastic_chart.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/elastic_chart.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx create mode 100644 x-pack/plugins/apm/public/context/charts_sync_context.tsx create mode 100644 x-pack/plugins/apm/public/hooks/use_annotations.ts create mode 100644 x-pack/plugins/apm/public/hooks/use_charts_sync.tsx diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx index e17dd9a9eb038..dbb06c4b17c8c 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx @@ -4,8 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + Axis, + Chart, + HistogramBarSeries, + niceTimeFormatter, + Position, + ScaleType, + Settings, + SettingsSpec, + TooltipValue, +} from '@elastic/charts'; + import { EuiTitle } from '@elastic/eui'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; import d3 from 'd3'; @@ -13,6 +24,7 @@ import { scaleUtc } from 'd3-scale'; import { mean } from 'lodash'; import React from 'react'; import { asRelativeDateTimeRange } from '../../../../../common/utils/formatters'; +import { useTheme } from '../../../../hooks/useTheme'; import { getTimezoneOffsetInMs } from '../../../shared/charts/CustomPlot/getTimezoneOffsetInMs'; // @ts-expect-error import Histogram from '../../../shared/charts/Histogram'; @@ -62,6 +74,7 @@ const tooltipHeader = (bucket: FormattedBucket) => asRelativeDateTimeRange(bucket.x0, bucket.x); export function ErrorDistribution({ distribution, title }: Props) { + const theme = useTheme(); const buckets = getFormattedBuckets( distribution.buckets, distribution.bucketSize @@ -82,48 +95,99 @@ export function ErrorDistribution({ distribution, title }: Props) { const xMax = d3.max(buckets, (d) => d.x); const tickFormat = scaleUtc().domain([xMin, xMax]).tickFormat(); + const xFormatter = niceTimeFormatter([xMin, xMax]); + + const tooltipProps: SettingsSpec['tooltip'] = { + headerFormatter: (tooltip: TooltipValue) => { + const serie = buckets.find((bucket) => bucket.x0 === tooltip.value); + if (serie) { + return asRelativeDateTimeRange(serie.x0, serie.x); + } + return `${tooltip.value}`; + }, + }; + return (
{title} - bucket.x} - xType="time-utc" - formatX={(value: Date) => { - const time = value.getTime(); - return tickFormat(new Date(time - getTimezoneOffsetInMs(time))); - }} - buckets={buckets} - bucketSize={distribution.bucketSize} - formatYShort={(value: number) => - i18n.translate('xpack.apm.errorGroupDetails.occurrencesShortLabel', { - defaultMessage: '{occCount} occ.', - values: { occCount: value }, - }) - } - formatYLong={(value: number) => - i18n.translate('xpack.apm.errorGroupDetails.occurrencesLongLabel', { - defaultMessage: - '{occCount} {occCount, plural, one {occurrence} other {occurrences}}', - values: { occCount: value }, - }) - } - legends={[ - { - color: theme.euiColorVis1, - // 0a abbreviates large whole numbers with metric prefixes like: 1000 = 1k, 32000 = 32k, 1000000 = 1m - legendValue: numeral(averageValue).format('0a'), - title: i18n.translate('xpack.apm.errorGroupDetails.avgLabel', { - defaultMessage: 'Avg.', - }), - legendClickDisabled: true, - }, - ]} - /> +
+ bucket.x} + xType="time-utc" + formatX={(value: Date) => { + const time = value.getTime(); + return tickFormat(new Date(time - getTimezoneOffsetInMs(time))); + }} + buckets={buckets} + bucketSize={distribution.bucketSize} + formatYShort={(value: number) => + i18n.translate( + 'xpack.apm.errorGroupDetails.occurrencesShortLabel', + { + defaultMessage: '{occCount} occ.', + values: { occCount: value }, + } + ) + } + formatYLong={(value: number) => + i18n.translate('xpack.apm.errorGroupDetails.occurrencesLongLabel', { + defaultMessage: + '{occCount} {occCount, plural, one {occurrence} other {occurrences}}', + values: { occCount: value }, + }) + } + legends={[ + { + color: theme.eui.euiColorVis1, + // 0a abbreviates large whole numbers with metric prefixes like: 1000 = 1k, 32000 = 32k, 1000000 = 1m + legendValue: numeral(averageValue).format('0a'), + title: i18n.translate('xpack.apm.errorGroupDetails.avgLabel', { + defaultMessage: 'Avg.', + }), + legendClickDisabled: true, + }, + ]} + /> +
+
+ + + + `${value} occ.`} + /> + + +
); } diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx index 67125d41635a9..52f021ddcbe01 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx @@ -10,7 +10,25 @@ import d3 from 'd3'; import { isEmpty } from 'lodash'; import React, { useCallback } from 'react'; import { ValuesType } from 'utility-types'; +import { + Axis, + BarSeriesStyle, + Chart, + DataSeriesDatum, + ElementClickListener, + GeometryValue, + HistogramBarSeries, + PartialTheme, + Position, + RectAnnotation, + ScaleType, + Settings, + SettingsSpec, + TooltipValue, +} from '@elastic/charts'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; +import { useTheme } from '../../../../../../observability/public'; +import { unit } from '../../../../style/variables'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { TransactionDistributionAPIResponse } from '../../../../../server/lib/transactions/distribution'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -110,6 +128,7 @@ export function TransactionDistribution(props: Props) { bucketIndex, onBucketClick, } = props; + const theme = useTheme(); /* eslint-disable-next-line react-hooks/exhaustive-deps */ const formatYShort = useCallback(getFormatYShort(transactionType), [ @@ -150,9 +169,34 @@ export function TransactionDistribution(props: Props) { distribution.bucketSize ); + const xMin = d3.min(buckets, (d) => d.x0) || 0; const xMax = d3.max(buckets, (d) => d.x) || 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 onBarClick: ElementClickListener = (elements) => { + const chartPoint = elements[0][0] as GeometryValue; + const clickedBucket = distribution?.buckets.find((bucket) => { + return bucket.key === chartPoint.x; + }); + if (clickedBucket) { + onBucketClick(clickedBucket); + } + }; + + const selectedBucket = buckets[bucketIndex]; + return (
@@ -181,42 +225,85 @@ export function TransactionDistribution(props: Props) { /> +
+ { + const clickedBucket = getBucketFromChartPoint(chartPoint); - { - const clickedBucket = getBucketFromChartPoint(chartPoint); - - if (clickedBucket) { - onBucketClick(clickedBucket); - } - }} - formatX={(time: number) => timeFormatter(time).formatted} - formatYShort={formatYShort} - formatYLong={formatYLong} - verticalLineHover={(point: IChartPoint) => - isEmpty(getBucketFromChartPoint(point)?.samples) - } - backgroundHover={(point: IChartPoint) => - !isEmpty(getBucketFromChartPoint(point)?.samples) - } - tooltipHeader={(point: IChartPoint) => { - const xFormatted = timeFormatter(point.x); - const x0Formatted = timeFormatter(point.x0); - return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`; - }} - tooltipFooter={(point: IChartPoint) => - isEmpty(getBucketFromChartPoint(point)?.samples) && - i18n.translate( - 'xpack.apm.transactionDetails.transactionsDurationDistributionChart.noSampleTooltip', - { - defaultMessage: 'No sample available for this bucket', + if (clickedBucket) { + onBucketClick(clickedBucket); } - ) - } - /> + }} + formatX={(time: number) => timeFormatter(time).formatted} + formatYShort={formatYShort} + formatYLong={formatYLong} + verticalLineHover={(point: IChartPoint) => + isEmpty(getBucketFromChartPoint(point)?.samples) + } + backgroundHover={(point: IChartPoint) => + !isEmpty(getBucketFromChartPoint(point)?.samples) + } + tooltipHeader={(point: IChartPoint) => { + const xFormatted = timeFormatter(point.x); + const x0Formatted = timeFormatter(point.x0); + return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`; + }} + tooltipFooter={(point: IChartPoint) => + isEmpty(getBucketFromChartPoint(point)?.samples) && + i18n.translate( + 'xpack.apm.transactionDetails.transactionsDurationDistributionChart.noSampleTooltip', + { + defaultMessage: 'No sample available for this bucket', + } + ) + } + /> +
+
+ + + {selectedBucket && ( + + )} + timeFormatter(time).formatted} + /> + + + +
); } diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/elastic_charts.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/elastic_charts.tsx new file mode 100644 index 0000000000000..6b21fba118251 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/elastic_charts.tsx @@ -0,0 +1,104 @@ +/* + * 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 { + AreaSeries, + Axis, + Chart, + niceTimeFormatter, + Placement, + Position, + ScaleType, + Settings, +} from '@elastic/charts'; +import moment from 'moment'; +import React, { useEffect } from 'react'; +import { asPercent } from '../../../../../common/utils/formatters'; +import { TimeSeries } from '../../../../../typings/timeseries'; +import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useChartsSync as useChartsSync2 } from '../../../../hooks/use_charts_sync'; +import { unit } from '../../../../style/variables'; +import { Annotations } from '../../charts/annotations'; + +const XY_HEIGHT = unit * 16; + +interface Props { + timeseries: TimeSeries[]; + noHits: boolean; +} + +export function TransactionBreakdownGraphElasticChart({ + timeseries, + noHits, +}: Props) { + const chartRef = React.createRef(); + const { event, setEvent } = useChartsSync2(); + const { urlParams } = useUrlParams(); + const { start, end } = urlParams; + + useEffect(() => { + if (event.chartId !== 'timeSpentBySpan' && chartRef.current) { + chartRef.current.dispatchExternalPointerEvent(event); + } + }, [chartRef, event]); + + const min = moment.utc(start).valueOf(); + const max = moment.utc(end).valueOf(); + + const xFormatter = niceTimeFormatter([min, max]); + + return ( +
+ + { + setEvent(currEvent); + }} + externalPointerEvents={{ + tooltip: { visible: true, placement: Placement.Bottom }, + }} + /> + + asPercent(y ?? 0, 1)} + /> + + + + {timeseries.map((serie) => { + return ( + + ); + })} + +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx index 55826497ca385..5d796b3853c28 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { FETCH_STATUS } from '../../../hooks/useFetcher'; import { useTransactionBreakdown } from '../../../hooks/useTransactionBreakdown'; import { TransactionBreakdownGraph } from './TransactionBreakdownGraph'; +import { TransactionBreakdownGraphElasticChart } from './TransactionBreakdownGraph/elastic_charts'; function TransactionBreakdown() { const { data, status } = useTransactionBreakdown(); @@ -31,6 +32,12 @@ function TransactionBreakdown() { + + + ); diff --git a/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/elastic_chart.tsx b/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/elastic_chart.tsx new file mode 100644 index 0000000000000..8e964122f49b0 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/elastic_chart.tsx @@ -0,0 +1,43 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React from 'react'; +import { asPercent } from '../../../../../common/utils/formatters'; +import { useTheme } from '../../../../hooks/useTheme'; +import { LineChart } from '../line_chart'; + +interface Props { + errorRates: any[]; +} + +const tickFormatY = (y?: number) => { + return asPercent(y || 0, 1); +}; + +export function ErroneousTransactionsRateChartElasticChart({ + errorRates, +}: Props) { + const theme = useTheme(); + + return ( + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx index 8aec4184f924d..55e2393c1a364 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx @@ -3,12 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; import { max } from 'lodash'; import React, { useCallback } from 'react'; import { useParams } from 'react-router-dom'; +import { unit } from '../../../../style/variables'; import { asPercent } from '../../../../../common/utils/formatters'; import { useChartsSync } from '../../../../hooks/useChartsSync'; import { useFetcher } from '../../../../hooks/useFetcher'; @@ -16,6 +18,7 @@ import { useUrlParams } from '../../../../hooks/useUrlParams'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; // @ts-expect-error import CustomPlot from '../CustomPlot'; +import { ErroneousTransactionsRateChartElasticChart } from './elastic_chart'; const tickFormatY = (y?: number | null) => { return asPercent(y || 0, 1); @@ -69,38 +72,41 @@ export function ErroneousTransactionsRateChart() { - - Number.isFinite(y) ? tickFormatY(y) : 'N/A' - } - /> +
+ + Number.isFinite(y) ? tickFormatY(y) : 'N/A' + } + /> +
+ ); } diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/elastic_chart.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/elastic_chart.tsx new file mode 100644 index 0000000000000..278606c950853 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/elastic_chart.tsx @@ -0,0 +1,107 @@ +/* + * 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 { + Axis, + Chart, + LineSeries, + niceTimeFormatter, + Placement, + Position, + ScaleType, + Settings, + SettingsSpec, +} from '@elastic/charts'; +import moment from 'moment'; +import React, { useEffect } from 'react'; +import { TimeSeries } from '../../../../../typings/timeseries'; +import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useChartsSync as useChartsSync2 } from '../../../../hooks/use_charts_sync'; +import { unit } from '../../../../style/variables'; +import { Annotations } from '../annotations'; + +const XY_HEIGHT = unit * 16; + +interface Props { + timeseries: TimeSeries[]; + tickFormatY: (y: number) => React.ReactNode; + id: string; +} + +export function ElasticChart({ timeseries, tickFormatY, id }: Props) { + const chartRef = React.createRef(); + const { event, setEvent } = useChartsSync2(); + const { urlParams } = useUrlParams(); + const { start, end } = urlParams; + + useEffect(() => { + if (event.chartId !== id && chartRef.current) { + chartRef.current.dispatchExternalPointerEvent(event); + } + }, [event, chartRef, id]); + + const min = moment.utc(start).valueOf(); + const max = moment.utc(end).valueOf(); + + const xFormatter = niceTimeFormatter([min, max]); + + const chartTheme: SettingsSpec['theme'] = { + lineSeriesStyle: { + point: { visible: false }, + line: { strokeWidth: 2 }, + }, + }; + + return ( +
+ + { + setEvent(currEvent); + }} + externalPointerEvents={{ + tooltip: { visible: true, placement: Placement.Bottom }, + }} + showLegend + showLegendExtra + legendPosition={Position.Bottom} + xDomain={{ min, max }} + /> + + + + + + {timeseries.map((serie) => { + return ( + + ); + })} + +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index 0b741447f6fec..85aa1c54279dd 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -14,6 +14,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { ChartsSyncContextProvider2 } from '../../../../context/charts_sync_context'; +import { unit } from '../../../../style/variables'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { TRANSACTION_PAGE_LOAD, @@ -35,6 +37,7 @@ import { import { MLHeader } from './ml_header'; import { TransactionLineChart } from './TransactionLineChart'; import { useFormatter } from './use_formatter'; +import { LineChart } from '../line_chart'; interface TransactionChartProps { charts: ITransactionChartData; @@ -66,58 +69,82 @@ export function TransactionCharts({ return ( <> - - - - - - - {responseTimeLabel(transactionType)} - - - - {(license) => ( - - )} - - - - - + + + + + + + + {responseTimeLabel(transactionType)} + + + + {(license) => ( + + )} + + +
+ +
+
+
+
+ +
+
- - - - {tpmLabel(transactionType)} - - - - -
+ + + + {tpmLabel(transactionType)} + +
+ +
+
+
+
+ +
+
+
- + - - - - - - - - + + + + + + + + + ); } diff --git a/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx new file mode 100644 index 0000000000000..275c06fb9220f --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx @@ -0,0 +1,45 @@ +/* + * 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 { + AnnotationDomainTypes, + LineAnnotation, + Position, +} from '@elastic/charts'; +import { EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { useTheme } from '../../../../hooks/useTheme'; +import { useAnnotations } from '../../../../hooks/use_annotations'; +import { asAbsoluteDateTime } from '../../../../utils/formatters'; + +export function Annotations() { + const { annotations } = useAnnotations(); + const theme = useTheme(); + + if (!annotations.length) { + return null; + } + + const color = theme.eui.euiColorSecondary; + + return ( + ({ + dataValue: annotation['@timestamp'], + header: asAbsoluteDateTime(annotation['@timestamp']), + details: `${i18n.translate('xpack.apm.chart.annotation.version', { + defaultMessage: 'Version', + })} ${annotation.text}`, + }))} + style={{ line: { strokeWidth: 1, stroke: color, opacity: 1 } }} + marker={} + markerPosition={Position.Top} + /> + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx new file mode 100644 index 0000000000000..9313e4c6e520e --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx @@ -0,0 +1,107 @@ +/* + * 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 { + Axis, + Chart, + LineSeries, + niceTimeFormatter, + Placement, + Position, + ScaleType, + Settings, + SettingsSpec, +} from '@elastic/charts'; +import React, { useEffect } from 'react'; +import moment from 'moment'; +import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useChartsSync } from '../../../../hooks/use_charts_sync'; +import { unit } from '../../../../style/variables'; +import { TimeSeries } from '../../../../../typings/timeseries'; +import { Annotations } from '../annotations'; + +interface Props { + timeseries: TimeSeries[]; + tickFormatY: (y: number) => React.ReactNode; + id: string; +} + +const XY_HEIGHT = unit * 16; + +export function LineChart({ timeseries, tickFormatY, id }: Props) { + const chartRef = React.createRef(); + const { event, setEvent } = useChartsSync(); + const { urlParams } = useUrlParams(); + const { start, end } = urlParams; + + useEffect(() => { + if (event.chartId !== id && chartRef.current) { + chartRef.current.dispatchExternalPointerEvent(event); + } + }, [event, chartRef, id]); + + const min = moment.utc(start).valueOf(); + const max = moment.utc(end).valueOf(); + + const xFormatter = niceTimeFormatter([min, max]); + + const chartTheme: SettingsSpec['theme'] = { + lineSeriesStyle: { + point: { visible: false }, + line: { strokeWidth: 2 }, + }, + }; + + return ( +
+ + { + setEvent(currEvent); + }} + externalPointerEvents={{ + tooltip: { visible: true, placement: Placement.Bottom }, + }} + showLegend + showLegendExtra + legendPosition={Position.Bottom} + xDomain={{ min, max }} + /> + + + + + + {timeseries.map((serie) => { + return ( + + ); + })} + +
+ ); +} diff --git a/x-pack/plugins/apm/public/context/ChartsSyncContext.tsx b/x-pack/plugins/apm/public/context/ChartsSyncContext.tsx index 7df35bc443226..e4e7fe2260bd6 100644 --- a/x-pack/plugins/apm/public/context/ChartsSyncContext.tsx +++ b/x-pack/plugins/apm/public/context/ChartsSyncContext.tsx @@ -6,6 +6,7 @@ import React, { ReactNode, useMemo, useState } from 'react'; import { useHistory, useParams } from 'react-router-dom'; +import { Annotation } from '../../common/annotations'; import { fromQuery, toQuery } from '../components/shared/Links/url_helpers'; import { useFetcher } from '../hooks/useFetcher'; import { useUrlParams } from '../hooks/useUrlParams'; @@ -15,6 +16,7 @@ const ChartsSyncContext = React.createContext<{ onHover: (hoverX: number) => void; onMouseLeave: () => void; onSelectionEnd: (range: { start: number; end: number }) => void; + annotations: Annotation[]; } | null>(null); function ChartsSyncContextProvider({ children }: { children: ReactNode }) { diff --git a/x-pack/plugins/apm/public/context/charts_sync_context.tsx b/x-pack/plugins/apm/public/context/charts_sync_context.tsx new file mode 100644 index 0000000000000..c094bcab5cb19 --- /dev/null +++ b/x-pack/plugins/apm/public/context/charts_sync_context.tsx @@ -0,0 +1,26 @@ +/* + * 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 React, { ReactNode, useState } from 'react'; + +export const ChartsSyncContext2 = React.createContext<{ + event: any; + setEvent: Function; +} | null>(null); + +export function ChartsSyncContextProvider2({ + children, +}: { + children: ReactNode; +}) { + const [event, setEvent] = useState({}); + + return ( + + ); +} diff --git a/x-pack/plugins/apm/public/hooks/use_annotations.ts b/x-pack/plugins/apm/public/hooks/use_annotations.ts new file mode 100644 index 0000000000000..2b1c2bec52b3d --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_annotations.ts @@ -0,0 +1,38 @@ +/* + * 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 { useParams } from 'react-router-dom'; +import { callApmApi } from '../services/rest/createCallApmApi'; +import { useFetcher } from './useFetcher'; +import { useUrlParams } from './useUrlParams'; + +const INITIAL_STATE = { annotations: [] }; + +export function useAnnotations() { + const { serviceName } = useParams<{ serviceName?: string }>(); + const { urlParams, uiFilters } = useUrlParams(); + const { start, end } = urlParams; + const { environment } = uiFilters; + + const { data = INITIAL_STATE } = useFetcher(() => { + if (start && end && serviceName) { + return callApmApi({ + pathname: '/api/apm/services/{serviceName}/annotation/search', + params: { + path: { + serviceName, + }, + query: { + start, + end, + environment, + }, + }, + }); + } + }, [start, end, environment, serviceName]); + + return data; +} diff --git a/x-pack/plugins/apm/public/hooks/use_charts_sync.tsx b/x-pack/plugins/apm/public/hooks/use_charts_sync.tsx new file mode 100644 index 0000000000000..110451768e526 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_charts_sync.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useContext } from 'react'; +import { ChartsSyncContext2 } from '../context/charts_sync_context'; + +export function useChartsSync() { + const context = useContext(ChartsSyncContext2); + + if (!context) { + throw new Error('Missing ChartsSync context provider'); + } + + return context; +} From cce8240e357f42c3e7b42cb5d6a7ae40a084352b Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 19 Oct 2020 16:04:01 +0200 Subject: [PATCH 02/15] fixing some stuff --- .../ErrorGroupDetails/Distribution/index.tsx | 76 ++----------------- .../TransactionDetails/Distribution/index.tsx | 19 ++--- .../TransactionCharts/elastic_chart.tsx | 2 +- .../shared/charts/TransactionCharts/index.tsx | 3 +- .../shared/charts/annotations/index.tsx | 2 +- .../shared/charts/line_chart/index.tsx | 2 +- .../hooks/useTransactionDistribution.ts | 2 +- 7 files changed, 20 insertions(+), 86 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx index dbb06c4b17c8c..1915aa0464651 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx @@ -15,33 +15,16 @@ import { SettingsSpec, TooltipValue, } from '@elastic/charts'; - import { EuiTitle } from '@elastic/eui'; -import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; import d3 from 'd3'; -import { scaleUtc } from 'd3-scale'; -import { mean } from 'lodash'; import React from 'react'; import { asRelativeDateTimeRange } from '../../../../../common/utils/formatters'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import type { ErrorDistributionAPIResponse } from '../../../../../server/lib/errors/distribution/get_distribution'; import { useTheme } from '../../../../hooks/useTheme'; -import { getTimezoneOffsetInMs } from '../../../shared/charts/CustomPlot/getTimezoneOffsetInMs'; -// @ts-expect-error -import Histogram from '../../../shared/charts/Histogram'; import { EmptyMessage } from '../../../shared/EmptyMessage'; -interface IBucket { - key: number; - count: number | undefined; -} - -// TODO: cleanup duplication of this in distribution/get_distribution.ts (ErrorDistributionAPIResponse) and transactions/distribution/index.ts (TransactionDistributionAPIResponse) -interface IDistribution { - noHits: boolean; - buckets: IBucket[]; - bucketSize: number; -} - interface FormattedBucket { x0: number; x: number; @@ -49,7 +32,7 @@ interface FormattedBucket { } export function getFormattedBuckets( - buckets: IBucket[], + buckets: ErrorDistributionAPIResponse['buckets'], bucketSize: number ): FormattedBucket[] | null { if (!buckets) { @@ -66,13 +49,10 @@ export function getFormattedBuckets( } interface Props { - distribution: IDistribution; + distribution: ErrorDistributionAPIResponse; title: React.ReactNode; } -const tooltipHeader = (bucket: FormattedBucket) => - asRelativeDateTimeRange(bucket.x0, bucket.x); - export function ErrorDistribution({ distribution, title }: Props) { const theme = useTheme(); const buckets = getFormattedBuckets( @@ -90,10 +70,10 @@ export function ErrorDistribution({ distribution, title }: Props) { ); } - const averageValue = mean(buckets.map((bucket) => bucket.y)) || 0; + // TODO: caue check it + // const averageValue = mean(buckets.map((bucket) => bucket.y)) || 0; const xMin = d3.min(buckets, (d) => d.x0); - const xMax = d3.max(buckets, (d) => d.x); - const tickFormat = scaleUtc().domain([xMin, xMax]).tickFormat(); + const xMax = d3.max(buckets, (d) => d.x0); const xFormatter = niceTimeFormatter([xMin, xMax]); @@ -112,48 +92,6 @@ export function ErrorDistribution({ distribution, title }: Props) { {title} -
- bucket.x} - xType="time-utc" - formatX={(value: Date) => { - const time = value.getTime(); - return tickFormat(new Date(time - getTimezoneOffsetInMs(time))); - }} - buckets={buckets} - bucketSize={distribution.bucketSize} - formatYShort={(value: number) => - i18n.translate( - 'xpack.apm.errorGroupDetails.occurrencesShortLabel', - { - defaultMessage: '{occCount} occ.', - values: { occCount: value }, - } - ) - } - formatYLong={(value: number) => - i18n.translate('xpack.apm.errorGroupDetails.occurrencesLongLabel', { - defaultMessage: - '{occCount} {occCount, plural, one {occurrence} other {occurrences}}', - values: { occCount: value }, - }) - } - legends={[ - { - color: theme.eui.euiColorVis1, - // 0a abbreviates large whole numbers with metric prefixes like: 1000 = 1k, 32000 = 32k, 1000000 = 1m - legendValue: numeral(averageValue).format('0a'), - title: i18n.translate('xpack.apm.errorGroupDetails.avgLabel', { - defaultMessage: 'Avg.', - }), - legendClickDisabled: true, - }, - ]} - /> -
React.ReactNode; + tickFormatY: (y: number) => string; id: string; } diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index 85aa1c54279dd..c52fe496b93d8 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -49,8 +49,7 @@ export function TransactionCharts({ urlParams, }: TransactionChartProps) { const getTPMFormatter = (t: number) => { - const unit = tpmUnit(urlParams.transactionType); - return `${asDecimal(t)} ${unit}`; + return `${asDecimal(t)} ${tpmUnit(urlParams.transactionType)}`; }; const getTPMTooltipFormatter = (p: Coordinate) => { diff --git a/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx index 275c06fb9220f..683c66b2a96fe 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx @@ -12,9 +12,9 @@ import { import { EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { asAbsoluteDateTime } from '../../../../../common/utils/formatters'; import { useTheme } from '../../../../hooks/useTheme'; import { useAnnotations } from '../../../../hooks/use_annotations'; -import { asAbsoluteDateTime } from '../../../../utils/formatters'; export function Annotations() { const { annotations } = useAnnotations(); diff --git a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx index 9313e4c6e520e..fe55b06e1d024 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx @@ -25,7 +25,7 @@ import { Annotations } from '../annotations'; interface Props { timeseries: TimeSeries[]; - tickFormatY: (y: number) => React.ReactNode; + tickFormatY: (y: number) => string; id: string; } diff --git a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts b/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts index a5096a314388c..8c76225d03486 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts @@ -10,7 +10,7 @@ import { IUrlParams } from '../context/UrlParamsContext/types'; import { useFetcher } from './useFetcher'; import { useUiFilters } from '../context/UrlParamsContext'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { TransactionDistributionAPIResponse } from '../../server/lib/transactions/distribution'; +import type { TransactionDistributionAPIResponse } from '../../server/lib/transactions/distribution'; import { toQuery, fromQuery } from '../components/shared/Links/url_helpers'; import { maybe } from '../../common/utils/maybe'; From 73e3764504cd67d27cf88904109a3034f034846e Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 20 Oct 2020 14:35:38 +0200 Subject: [PATCH 03/15] refactoring --- .../ErrorGroupDetails/Distribution/index.tsx | 1 + .../TransactionDetails/Distribution/index.tsx | 76 ++++------- .../elastic_charts.tsx | 104 -------------- .../TransactionBreakdownGraph/index.tsx | 129 ++++++++++++------ .../shared/TransactionBreakdown/index.tsx | 14 +- .../elastic_chart.tsx | 43 ------ .../ErroneousTransactionsRateChart/index.tsx | 71 +++------- .../shared/charts/TransactionCharts/index.tsx | 33 +---- .../TransactionCharts/use_formatter.test.tsx | 106 ++++++-------- .../charts/TransactionCharts/use_formatter.ts | 32 ++++- .../shared/charts/helper/helper.test.ts | 39 ++++++ .../components/shared/charts/helper/helper.ts | 35 +++++ .../helper/{__test__ => }/timezone.test.ts | 2 +- .../shared/charts/line_chart/index.tsx | 22 ++- 14 files changed, 295 insertions(+), 412 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/elastic_charts.tsx delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/elastic_chart.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts create mode 100644 x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts rename x-pack/plugins/apm/public/components/shared/charts/helper/{__test__ => }/timezone.test.ts (97%) diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx index 1915aa0464651..0b24e51baa707 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx @@ -115,6 +115,7 @@ export function ErrorDistribution({ distribution, title }: Props) { tickFormat={(value) => `${value} occ.`} /> (t: number) => { 'xpack.apm.transactionDetails.transactionsDurationDistributionChart.requestTypeUnitLongLabel', { defaultMessage: - '{transCount, plural, =0 {# request} one {# request} other {# requests}}', + '{transCount, plural, =0 {request} one {request} other {requests}}', values: { transCount: t, }, @@ -99,7 +98,7 @@ const getFormatYLong = (transactionType: string | undefined) => (t: number) => { 'xpack.apm.transactionDetails.transactionsDurationDistributionChart.transactionTypeUnitLongLabel', { defaultMessage: - '{transCount, plural, =0 {# transaction} one {# transaction} other {# transactions}}', + '{transCount, plural, =0 {transaction} one {transaction} other {transactions}}', values: { transCount: t, }, @@ -153,21 +152,13 @@ export function TransactionDistribution(props: Props) { ); } - function getBucketFromChartPoint(chartPoint: IChartPoint) { - const clickedBucket = distribution?.buckets.find((bucket) => { - return bucket.key === chartPoint.x0; - }); - - return clickedBucket; - } - const buckets = getFormattedBuckets( distribution.buckets, distribution.bucketSize ); const xMin = d3.min(buckets, (d) => d.x0) || 0; - const xMax = d3.max(buckets, (d) => d.x) || 0; + const xMax = d3.max(buckets, (d) => d.x0) || 0; const timeFormatter = getDurationFormatter(xMax); const tooltipProps: SettingsSpec['tooltip'] = { @@ -222,43 +213,6 @@ export function TransactionDistribution(props: Props) { /> -
- { - const clickedBucket = getBucketFromChartPoint(chartPoint); - - if (clickedBucket) { - onBucketClick(clickedBucket); - } - }} - formatX={(time: number) => timeFormatter(time).formatted} - formatYShort={formatYShort} - formatYLong={formatYLong} - verticalLineHover={(point: IChartPoint) => - isEmpty(getBucketFromChartPoint(point)?.samples) - } - backgroundHover={(point: IChartPoint) => - !isEmpty(getBucketFromChartPoint(point)?.samples) - } - tooltipHeader={(point: IChartPoint) => { - const xFormatted = timeFormatter(point.x); - const x0Formatted = timeFormatter(point.x0); - return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`; - }} - tooltipFooter={(point: IChartPoint) => - isEmpty(getBucketFromChartPoint(point)?.samples) && - i18n.translate( - 'xpack.apm.transactionDetails.transactionsDurationDistributionChart.noSampleTooltip', - { - defaultMessage: 'No sample available for this bucket', - } - ) - } - /> -
)} timeFormatter(time).formatted} /> - + formatYShort(value)} + /> { + return `${value}`; + }} + minBarHeight={2} id="transactionDurationDistribution" - name="requests" + name={(series: XYChartSeriesIdentifier) => { + const bucketCount = series.splitAccessors.get( + series.yAccessor + ) as number; + return formatYLong(bucketCount); + }} + splitSeriesAccessors={['y']} xScaleType={ScaleType.Linear} yScaleType={ScaleType.Linear} xAccessor="x0" diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/elastic_charts.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/elastic_charts.tsx deleted file mode 100644 index 6b21fba118251..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/elastic_charts.tsx +++ /dev/null @@ -1,104 +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 { - AreaSeries, - Axis, - Chart, - niceTimeFormatter, - Placement, - Position, - ScaleType, - Settings, -} from '@elastic/charts'; -import moment from 'moment'; -import React, { useEffect } from 'react'; -import { asPercent } from '../../../../../common/utils/formatters'; -import { TimeSeries } from '../../../../../typings/timeseries'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useChartsSync as useChartsSync2 } from '../../../../hooks/use_charts_sync'; -import { unit } from '../../../../style/variables'; -import { Annotations } from '../../charts/annotations'; - -const XY_HEIGHT = unit * 16; - -interface Props { - timeseries: TimeSeries[]; - noHits: boolean; -} - -export function TransactionBreakdownGraphElasticChart({ - timeseries, - noHits, -}: Props) { - const chartRef = React.createRef(); - const { event, setEvent } = useChartsSync2(); - const { urlParams } = useUrlParams(); - const { start, end } = urlParams; - - useEffect(() => { - if (event.chartId !== 'timeSpentBySpan' && chartRef.current) { - chartRef.current.dispatchExternalPointerEvent(event); - } - }, [chartRef, event]); - - const min = moment.utc(start).valueOf(); - const max = moment.utc(end).valueOf(); - - const xFormatter = niceTimeFormatter([min, max]); - - return ( -
- - { - setEvent(currEvent); - }} - externalPointerEvents={{ - tooltip: { visible: true, placement: Placement.Bottom }, - }} - /> - - asPercent(y ?? 0, 1)} - /> - - - - {timeseries.map((serie) => { - return ( - - ); - })} - -
- ); -} diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx index b908eb8da4d03..efc6fb45436a3 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx @@ -4,62 +4,101 @@ * you may not use this file except in compliance with the Elastic License. */ -import { throttle } from 'lodash'; -import React, { useMemo } from 'react'; +import { + AreaSeries, + Axis, + Chart, + niceTimeFormatter, + Placement, + Position, + ScaleType, + Settings, +} from '@elastic/charts'; +import moment from 'moment'; +import React, { useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; import { asPercent } from '../../../../../common/utils/formatters'; -import { useUiTracker } from '../../../../../../observability/public'; -import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; -import { Maybe } from '../../../../../typings/common'; -import { Coordinate, TimeSeries } from '../../../../../typings/timeseries'; +import { TimeSeries } from '../../../../../typings/timeseries'; import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; -import { getEmptySeries } from '../../charts/CustomPlot/getEmptySeries'; -import { TransactionLineChart } from '../../charts/TransactionCharts/TransactionLineChart'; +import { useChartsSync as useChartsSync2 } from '../../../../hooks/use_charts_sync'; +import { unit } from '../../../../style/variables'; +import { Annotations } from '../../charts/annotations'; +import { onBrushEnd } from '../../charts/helper/helper'; + +const XY_HEIGHT = unit * 16; interface Props { timeseries: TimeSeries[]; - noHits: boolean; } -const tickFormatY = (y: Maybe) => { - return asPercent(y ?? 0, 1); -}; +export function TransactionBreakdownGraph({ timeseries }: Props) { + const history = useHistory(); + const chartRef = React.createRef(); + const { event, setEvent } = useChartsSync2(); + const { urlParams } = useUrlParams(); + const { start, end } = urlParams; -const formatTooltipValue = (coordinate: Coordinate) => { - return isValidCoordinateValue(coordinate.y) - ? asPercent(coordinate.y, 1) - : NOT_AVAILABLE_LABEL; -}; + useEffect(() => { + if (event.chartId !== 'timeSpentBySpan' && chartRef.current) { + chartRef.current.dispatchExternalPointerEvent(event); + } + }, [chartRef, event]); -function TransactionBreakdownGraph({ timeseries, noHits }: Props) { - const { urlParams } = useUrlParams(); - const { rangeFrom, rangeTo } = urlParams; - const trackApmEvent = useUiTracker({ app: 'apm' }); - const handleHover = useMemo( - () => - throttle(() => trackApmEvent({ metric: 'hover_breakdown_chart' }), 60000), - [trackApmEvent] - ); + const min = moment.utc(start).valueOf(); + const max = moment.utc(end).valueOf(); - const emptySeries = - rangeFrom && rangeTo - ? getEmptySeries( - new Date(rangeFrom).getTime(), - new Date(rangeTo).getTime() - ) - : []; + const xFormatter = niceTimeFormatter([min, max]); return ( - +
+ + onBrushEnd({ x, history })} + showLegend + showLegendExtra + legendPosition={Position.Bottom} + xDomain={{ min, max }} + flatLegend + onPointerUpdate={(currEvent: any) => { + setEvent(currEvent); + }} + externalPointerEvents={{ + tooltip: { visible: true, placement: Placement.Bottom }, + }} + /> + + asPercent(y ?? 0, 1)} + /> + + + + {timeseries.map((serie) => { + return ( + + ); + })} + +
); } - -export { TransactionBreakdownGraph }; diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx index 5d796b3853c28..752d03b3bb459 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx @@ -5,17 +5,13 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isEmpty } from 'lodash'; import React from 'react'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; import { useTransactionBreakdown } from '../../../hooks/useTransactionBreakdown'; import { TransactionBreakdownGraph } from './TransactionBreakdownGraph'; -import { TransactionBreakdownGraphElasticChart } from './TransactionBreakdownGraph/elastic_charts'; function TransactionBreakdown() { - const { data, status } = useTransactionBreakdown(); + const { data } = useTransactionBreakdown(); const { timeseries } = data; - const noHits = isEmpty(timeseries) && status === FETCH_STATUS.SUCCESS; return ( @@ -30,13 +26,7 @@ function TransactionBreakdown() { - - - - + diff --git a/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/elastic_chart.tsx b/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/elastic_chart.tsx deleted file mode 100644 index 8e964122f49b0..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/elastic_chart.tsx +++ /dev/null @@ -1,43 +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 { i18n } from '@kbn/i18n'; -import React from 'react'; -import { asPercent } from '../../../../../common/utils/formatters'; -import { useTheme } from '../../../../hooks/useTheme'; -import { LineChart } from '../line_chart'; - -interface Props { - errorRates: any[]; -} - -const tickFormatY = (y?: number) => { - return asPercent(y || 0, 1); -}; - -export function ErroneousTransactionsRateChartElasticChart({ - errorRates, -}: Props) { - const theme = useTheme(); - - return ( - - ); -} diff --git a/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx index 55e2393c1a364..1899b9f51761c 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx @@ -5,29 +5,24 @@ */ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; -import { max } from 'lodash'; -import React, { useCallback } from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; -import { unit } from '../../../../style/variables'; import { asPercent } from '../../../../../common/utils/formatters'; -import { useChartsSync } from '../../../../hooks/useChartsSync'; import { useFetcher } from '../../../../hooks/useFetcher'; +import { useTheme } from '../../../../hooks/useTheme'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; -// @ts-expect-error -import CustomPlot from '../CustomPlot'; -import { ErroneousTransactionsRateChartElasticChart } from './elastic_chart'; +import { LineChart } from '../line_chart'; const tickFormatY = (y?: number | null) => { return asPercent(y || 0, 1); }; export function ErroneousTransactionsRateChart() { + const theme = useTheme(); const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); - const syncedChartsProps = useChartsSync(); const { start, end, transactionType, transactionName } = urlParams; @@ -52,15 +47,7 @@ export function ErroneousTransactionsRateChart() { } }, [serviceName, start, end, uiFilters, transactionType, transactionName]); - const combinedOnHover = useCallback( - (hoverX: number) => { - return syncedChartsProps.onHover(hoverX); - }, - [syncedChartsProps] - ); - const errorRates = data?.transactionErrorRate || []; - const maxRate = max(errorRates.map((errorRate) => errorRate.y)); return ( @@ -72,41 +59,21 @@ export function ErroneousTransactionsRateChart() { -
- - Number.isFinite(y) ? tickFormatY(y) : 'N/A' - } - /> -
- +
); } diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index c52fe496b93d8..5801f3c4fe556 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -62,9 +62,7 @@ export function TransactionCharts({ const { responseTimeSeries, tpmSeries } = charts; - const { formatter, setDisabledSeriesState } = useFormatter( - responseTimeSeries - ); + const { formatter, toggleSerie } = useFormatter(responseTimeSeries); return ( <> @@ -87,23 +85,15 @@ export function TransactionCharts({ )} -
- -
-
-
-
{ + if (serie) { + toggleSerie(serie); + } + }} /> @@ -113,17 +103,6 @@ export function TransactionCharts({ {tpmLabel(transactionType)} -
- -
-
-
-
{ - setDisabledSeriesState(disabledSeries); - }; - - return ( -
- - {formatter(value).formatted} -
- ); -} describe('useFormatter', () => { const timeSeries = ([ { + title: 'avg', data: [ { x: 1, y: toMicroseconds(11, 'minutes') }, { x: 2, y: toMicroseconds(1, 'minutes') }, @@ -42,6 +21,7 @@ describe('useFormatter', () => { ], }, { + title: '95th percentile', data: [ { x: 1, y: toMicroseconds(120, 'seconds') }, { x: 2, y: toMicroseconds(1, 'minutes') }, @@ -49,6 +29,7 @@ describe('useFormatter', () => { ], }, { + title: '99th percentile', data: [ { x: 1, y: toMicroseconds(60, 'seconds') }, { x: 2, y: toMicroseconds(5, 'minutes') }, @@ -56,54 +37,47 @@ describe('useFormatter', () => { ], }, ] as unknown) as TimeSeries[]; + it('returns new formatter when disabled series state changes', () => { - const { getByText } = render( - - ); - expect(getByText('2.0 min')).toBeInTheDocument(); + const { result } = renderHook(() => useFormatter(timeSeries)); + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('2.0 min'); + act(() => { - fireEvent.click(getByText('disable series')); + result.current.toggleSerie({ + specId: 'avg', + } as SeriesIdentifier); }); - expect(getByText('120 s')).toBeInTheDocument(); + + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('120 s'); }); + it('falls back to the first formatter when disabled series is empty', () => { - const { getByText } = render( - - ); - expect(getByText('2.0 min')).toBeInTheDocument(); + const { result } = renderHook(() => useFormatter(timeSeries)); + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('2.0 min'); + act(() => { - fireEvent.click(getByText('disable series')); + result.current.toggleSerie({ + specId: 'avg', + } as SeriesIdentifier); }); - expect(getByText('2.0 min')).toBeInTheDocument(); - // const { formatter, setDisabledSeriesState } = useFormatter(timeSeries); - // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); - // setDisabledSeriesState([true, true, false]); - // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); - }); - it('falls back to the first formatter when disabled series is all true', () => { - const { getByText } = render( - - ); - expect(getByText('2.0 min')).toBeInTheDocument(); + + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('120 s'); + act(() => { - fireEvent.click(getByText('disable series')); + result.current.toggleSerie({ + specId: 'avg', + } as SeriesIdentifier); }); - expect(getByText('2.0 min')).toBeInTheDocument(); - // const { formatter, setDisabledSeriesState } = useFormatter(timeSeries); - // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); - // setDisabledSeriesState([true, true, false]); - // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('2.0 min'); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts index d4694bc3caf1d..03434f9747f1b 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useState, Dispatch, SetStateAction } from 'react'; -import { isEmpty } from 'lodash'; +import { SeriesIdentifier } from '@elastic/charts'; +import { isEmpty, omit } from 'lodash'; +import { useState } from 'react'; import { getDurationFormatter, TimeFormatter, @@ -17,14 +18,33 @@ export const useFormatter = ( series: TimeSeries[] ): { formatter: TimeFormatter; - setDisabledSeriesState: Dispatch>; + toggleSerie: (disabledSerie: SeriesIdentifier) => void; } => { - const [disabledSeriesState, setDisabledSeriesState] = useState([]); + const [disabledSeries, setDisabledSeries] = useState< + Record + >({}); + const visibleSeries = series.filter( - (serie, index) => disabledSeriesState[index] !== true + (serie) => disabledSeries[serie.title] === undefined ); + const maxY = getMaxY(isEmpty(visibleSeries) ? series : visibleSeries); const formatter = getDurationFormatter(maxY); - return { formatter, setDisabledSeriesState }; + const toggleSerie = ({ specId }: SeriesIdentifier) => { + if (disabledSeries[specId] !== undefined) { + setDisabledSeries((prevState) => { + return omit(prevState, specId); + }); + } else { + setDisabledSeries((prevState) => { + return { ...prevState, [specId]: 0 }; + }); + } + }; + + return { + formatter, + toggleSerie, + }; }; diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts new file mode 100644 index 0000000000000..585eef546e754 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts @@ -0,0 +1,39 @@ +/* + * 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 { onBrushEnd } from './helper'; +import { History } from 'history'; + +describe('Chart helper', () => { + describe('onBrushEnd', () => { + const history = ({ + push: jest.fn(), + location: { + search: '', + }, + } as unknown) as History; + it("doesn't push a new history when x is not defined", () => { + onBrushEnd({ x: undefined, history }); + expect(history.push).not.toBeCalled(); + }); + + it('pushes a new history with time range converted to ISO', () => { + onBrushEnd({ x: [1593409448167, 1593415727797], history }); + expect(history.push).toBeCalledWith({ + search: + 'rangeFrom=2020-06-29T05:44:08.167Z&rangeTo=2020-06-29T07:28:47.797Z', + }); + }); + + it('pushes a new history keeping current search', () => { + history.location.search = '?foo=bar'; + onBrushEnd({ x: [1593409448167, 1593415727797], history }); + expect(history.push).toBeCalledWith({ + search: + 'foo=bar&rangeFrom=2020-06-29T05:44:08.167Z&rangeTo=2020-06-29T07:28:47.797Z', + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts new file mode 100644 index 0000000000000..a9c1337feac99 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts @@ -0,0 +1,35 @@ +/* + * 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 { XYBrushArea } from '@elastic/charts'; +import { History } from 'history'; +import { fromQuery, toQuery } from '../../Links/url_helpers'; + +export const onBrushEnd = ({ + x, + history, +}: { + x: XYBrushArea['x']; + history: History; +}) => { + if (x) { + const start = x[0]; + const end = x[1]; + + const currentSearch = toQuery(history.location.search); + const nextSearch = { + rangeFrom: new Date(start).toISOString(), + rangeTo: new Date(end).toISOString(), + }; + history.push({ + ...history.location, + search: fromQuery({ + ...currentSearch, + ...nextSearch, + }), + }); + } +}; diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/__test__/timezone.test.ts b/x-pack/plugins/apm/public/components/shared/charts/helper/timezone.test.ts similarity index 97% rename from x-pack/plugins/apm/public/components/shared/charts/helper/__test__/timezone.test.ts rename to x-pack/plugins/apm/public/components/shared/charts/helper/timezone.test.ts index 0a6daf47b3ca6..3997448d17385 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/helper/__test__/timezone.test.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/timezone.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import moment from 'moment-timezone'; -import { getDomainTZ, getTimeTicksTZ } from '../timezone'; +import { getDomainTZ, getTimeTicksTZ } from './timezone'; describe('Timezone helper', () => { let originalTimezone: moment.MomentZone | null; diff --git a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx index fe55b06e1d024..cbb608117156a 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx @@ -7,6 +7,7 @@ import { Axis, Chart, + LegendItemListener, LineSeries, niceTimeFormatter, Placement, @@ -15,23 +16,32 @@ import { Settings, SettingsSpec, } from '@elastic/charts'; -import React, { useEffect } from 'react'; import moment from 'moment'; +import React, { useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; +import { TimeSeries } from '../../../../../typings/timeseries'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { useChartsSync } from '../../../../hooks/use_charts_sync'; import { unit } from '../../../../style/variables'; -import { TimeSeries } from '../../../../../typings/timeseries'; import { Annotations } from '../annotations'; +import { onBrushEnd } from '../helper/helper'; interface Props { timeseries: TimeSeries[]; tickFormatY: (y: number) => string; id: string; + onToggleLegend?: LegendItemListener; } const XY_HEIGHT = unit * 16; -export function LineChart({ timeseries, tickFormatY, id }: Props) { +export function LineChart({ + timeseries, + tickFormatY, + id, + onToggleLegend, +}: Props) { + const history = useHistory(); const chartRef = React.createRef(); const { event, setEvent } = useChartsSync(); const { urlParams } = useUrlParams(); @@ -59,6 +69,7 @@ export function LineChart({ timeseries, tickFormatY, id }: Props) {
onBrushEnd({ x, history })} theme={chartTheme} onPointerUpdate={(currEvent: any) => { setEvent(currEvent); @@ -70,6 +81,11 @@ export function LineChart({ timeseries, tickFormatY, id }: Props) { showLegendExtra legendPosition={Position.Bottom} xDomain={{ min, max }} + onLegendItemClick={(legend) => { + if (onToggleLegend) { + onToggleLegend(legend); + } + }} /> Date: Tue, 20 Oct 2020 14:51:41 +0200 Subject: [PATCH 04/15] fixing ts issues --- .../shared/charts/TransactionCharts/index.tsx | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index 5801f3c4fe556..42f8d752a68fa 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -14,30 +14,25 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { ChartsSyncContextProvider2 } from '../../../../context/charts_sync_context'; -import { unit } from '../../../../style/variables'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, TRANSACTION_ROUTE_CHANGE, } from '../../../../../common/transaction_types'; +import { asDecimal, tpmUnit } from '../../../../../common/utils/formatters'; import { Coordinate } from '../../../../../typings/timeseries'; +import { ChartsSyncContextProvider2 } from '../../../../context/charts_sync_context'; import { LicenseContext } from '../../../../context/LicenseContext'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { ITransactionChartData } from '../../../../selectors/chartSelectors'; -import { asDecimal, tpmUnit } from '../../../../../common/utils/formatters'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; -import { ErroneousTransactionsRateChart } from '../ErroneousTransactionsRateChart'; import { TransactionBreakdown } from '../../TransactionBreakdown'; -import { - getResponseTimeTickFormatter, - getResponseTimeTooltipFormatter, -} from './helper'; +import { ErroneousTransactionsRateChart } from '../ErroneousTransactionsRateChart'; +import { LineChart } from '../line_chart'; +import { getResponseTimeTickFormatter } from './helper'; import { MLHeader } from './ml_header'; -import { TransactionLineChart } from './TransactionLineChart'; import { useFormatter } from './use_formatter'; -import { LineChart } from '../line_chart'; interface TransactionChartProps { charts: ITransactionChartData; @@ -52,10 +47,8 @@ export function TransactionCharts({ return `${asDecimal(t)} ${tpmUnit(urlParams.transactionType)}`; }; - const getTPMTooltipFormatter = (p: Coordinate) => { - return isValidCoordinateValue(p.y) - ? getTPMFormatter(p.y) - : NOT_AVAILABLE_LABEL; + const getTPMTooltipFormatter = (y: Coordinate['y']) => { + return isValidCoordinateValue(y) ? getTPMFormatter(y) : NOT_AVAILABLE_LABEL; }; const { transactionType } = urlParams; @@ -106,7 +99,7 @@ export function TransactionCharts({ From 4aadb9101495ae0a30395f4d98a403c0ace63ccf Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 20 Oct 2020 14:54:37 +0200 Subject: [PATCH 05/15] fixing unit test --- .../TransactionOverview.test.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx index b7d1b93600a73..c530a7e1489ad 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx @@ -4,12 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - fireEvent, - getByText, - queryByLabelText, - render, -} from '@testing-library/react'; +import { fireEvent, getByText, queryByLabelText } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import { CoreStart } from 'kibana/public'; import React from 'react'; @@ -20,7 +15,10 @@ import { UrlParamsProvider } from '../../../context/UrlParamsContext'; import { IUrlParams } from '../../../context/UrlParamsContext/types'; import * as useFetcherHook from '../../../hooks/useFetcher'; import * as useServiceTransactionTypesHook from '../../../hooks/useServiceTransactionTypes'; -import { disableConsoleWarning } from '../../../utils/testHelpers'; +import { + disableConsoleWarning, + renderWithTheme, +} from '../../../utils/testHelpers'; import { fromQuery } from '../../shared/Links/url_helpers'; import { TransactionOverview } from './'; @@ -54,7 +52,7 @@ function setup({ jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any); - return render( + return renderWithTheme( From e244cbe947444112194dd56038a12cc800393d40 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 20 Oct 2020 15:08:29 +0200 Subject: [PATCH 06/15] fix i18n --- .../TransactionLineChart/index.tsx | 70 ------------------- .../translations/translations/ja-JP.json | 5 -- .../translations/translations/zh-CN.json | 5 -- 3 files changed, 80 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx deleted file mode 100644 index 09e6b0e43945f..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx +++ /dev/null @@ -1,70 +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 React, { useCallback } from 'react'; -import { Coordinate, TimeSeries } from '../../../../../../typings/timeseries'; -import { useChartsSync } from '../../../../../hooks/useChartsSync'; -// @ts-expect-error -import CustomPlot from '../../CustomPlot'; - -interface Props { - series: TimeSeries[]; - truncateLegends?: boolean; - tickFormatY: (y: number) => React.ReactNode; - formatTooltipValue: (c: Coordinate) => React.ReactNode; - yMax?: string | number; - height?: number; - stacked?: boolean; - onHover?: () => void; - visibleLegendCount?: number; - onToggleLegend?: (disabledSeriesState: boolean[]) => void; -} - -function TransactionLineChart(props: Props) { - const { - series, - tickFormatY, - formatTooltipValue, - yMax = 'max', - height, - truncateLegends, - stacked = false, - onHover, - visibleLegendCount, - onToggleLegend, - } = props; - - const syncedChartsProps = useChartsSync(); - - // combine callback for syncedChartsProps.onHover and props.onHover - const combinedOnHover = useCallback( - (hoverX: number) => { - if (onHover) { - onHover(); - } - return syncedChartsProps.onHover(hoverX); - }, - [syncedChartsProps, onHover] - ); - - return ( - - ); -} - -export { TransactionLineChart }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d4498a626ab9e..7b00b440f5a80 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4895,7 +4895,6 @@ "xpack.apm.error.prompt.title": "申し訳ございませんが、エラーが発生しました :(", "xpack.apm.errorCountAlert.name": "エラー数しきい値", "xpack.apm.errorCountAlertTrigger.errors": " エラー", - "xpack.apm.errorGroupDetails.avgLabel": "平均", "xpack.apm.errorGroupDetails.culpritLabel": "原因", "xpack.apm.errorGroupDetails.errorGroupTitle": "エラーグループ {errorGroupId}", "xpack.apm.errorGroupDetails.errorOccurrenceTitle": "エラーのオカレンス", @@ -4903,12 +4902,9 @@ "xpack.apm.errorGroupDetails.logMessageLabel": "ログメッセージ", "xpack.apm.errorGroupDetails.noErrorsLabel": "エラーが見つかりませんでした", "xpack.apm.errorGroupDetails.occurrencesChartLabel": "オカレンス", - "xpack.apm.errorGroupDetails.occurrencesLongLabel": "{occCount} {occCount, plural, one {件の発生} other {件の発生}}", - "xpack.apm.errorGroupDetails.occurrencesShortLabel": "{occCount} 件", "xpack.apm.errorGroupDetails.relatedTransactionSample": "関連トランザクションサンプル", "xpack.apm.errorGroupDetails.unhandledLabel": "未対応", "xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel": "ディスカバリで {occurrencesCount} 件の{occurrencesCount, plural, one {ドキュメント} other {ドキュメント}}を表示。", - "xpack.apm.errorRateChart.avgLabel": "平均", "xpack.apm.errorRateChart.rateLabel": "レート", "xpack.apm.errorRateChart.title": "トランザクションエラー率", "xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel": "エラーメッセージと原因", @@ -5283,7 +5279,6 @@ "xpack.apm.transactionDetails.traceNotFound": "選択されたトレースが見つかりません", "xpack.apm.transactionDetails.traceSampleTitle": "トレースのサンプル", "xpack.apm.transactionDetails.transactionLabel": "トランザクション", - "xpack.apm.transactionDetails.transactionsDurationDistributionChart.noSampleTooltip": "このバケットに利用可能なサンプルがありません", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.requestTypeUnitLongLabel": "{transCount, plural, =0 {# request} 1 {# 件のリクエスト} other {# 件のリクエスト}}", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.transactionTypeUnitLongLabel": "{transCount, plural, =0 {# transaction} 1 {# 件のトランザクション} other {# 件のトランザクション}}", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.unitShortLabel": "{transCount} {transType, select, request {件のリクエスト} other {件のトランザクション}}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 5f1c72929b2c6..02e35ec00a1df 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4897,7 +4897,6 @@ "xpack.apm.error.prompt.title": "抱歉,发生错误 :(", "xpack.apm.errorCountAlert.name": "错误计数阈值", "xpack.apm.errorCountAlertTrigger.errors": " 错误", - "xpack.apm.errorGroupDetails.avgLabel": "平均", "xpack.apm.errorGroupDetails.culpritLabel": "原因", "xpack.apm.errorGroupDetails.errorGroupTitle": "错误组 {errorGroupId}", "xpack.apm.errorGroupDetails.errorOccurrenceTitle": "错误发生", @@ -4905,12 +4904,9 @@ "xpack.apm.errorGroupDetails.logMessageLabel": "日志消息", "xpack.apm.errorGroupDetails.noErrorsLabel": "未找到任何错误", "xpack.apm.errorGroupDetails.occurrencesChartLabel": "发生次数", - "xpack.apm.errorGroupDetails.occurrencesLongLabel": "{occCount} 次{occCount, plural, one {出现} other {出现}}", - "xpack.apm.errorGroupDetails.occurrencesShortLabel": "{occCount} 次发生", "xpack.apm.errorGroupDetails.relatedTransactionSample": "相关的事务样本", "xpack.apm.errorGroupDetails.unhandledLabel": "未处理", "xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel": "在 Discover 查看 {occurrencesCount} 个 {occurrencesCount, plural, one {匹配项} other {匹配项}}。", - "xpack.apm.errorRateChart.avgLabel": "平均", "xpack.apm.errorRateChart.rateLabel": "比率", "xpack.apm.errorRateChart.title": "事务错误率", "xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel": "错误消息和原因", @@ -5287,7 +5283,6 @@ "xpack.apm.transactionDetails.traceNotFound": "找不到所选跟踪", "xpack.apm.transactionDetails.traceSampleTitle": "跟踪样例", "xpack.apm.transactionDetails.transactionLabel": "事务", - "xpack.apm.transactionDetails.transactionsDurationDistributionChart.noSampleTooltip": "此存储桶没有可用样例", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.requestTypeUnitLongLabel": "{transCount, plural, =0 {# 个请求} one {# 个请求} other {# 个请求}}", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.transactionTypeUnitLongLabel": "{transCount, plural, =0 {# 个事务} one {# 个事务} other {# 个事务}}", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.unitShortLabel": "{transCount} 个{transType, select, request {请求} other {事务}}", From 60463f840218c65e7225e041b4ad4f92e6146800 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 2 Nov 2020 15:29:30 -0300 Subject: [PATCH 07/15] adding isLoading prop --- .../components/app/TransactionDetails/index.tsx | 10 +++++++++- .../components/app/TransactionOverview/index.tsx | 13 +++++++++++-- .../shared/charts/TransactionCharts/index.tsx | 8 ++++++-- .../components/shared/charts/line_chart/index.tsx | 4 ++-- .../plugins/apm/public/hooks/useTransactionList.ts | 4 ++-- 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx index efdd7b1f34221..17b9e0c85d471 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx @@ -52,7 +52,10 @@ export function TransactionDetails({ status: distributionStatus, } = useTransactionDistribution(urlParams); - const { data: transactionChartsData } = useTransactionCharts(); + const { + data: transactionChartsData, + status: transactionChartsStatus, + } = useTransactionCharts(); const { waterfall, exceedsMax, status: waterfallStatus } = useWaterfall( urlParams ); @@ -121,6 +124,11 @@ export function TransactionDetails({ diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx index 5444d2d521f37..334d0342045ea 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx @@ -38,6 +38,7 @@ import { useRedirect } from './useRedirect'; import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; import { UserExperienceCallout } from './user_experience_callout'; import { Correlations } from '../Correlations'; +import { FETCH_STATUS } from '../../../hooks/useFetcher'; function getRedirectLocation({ urlParams, @@ -83,7 +84,10 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { }) ); - const { data: transactionCharts } = useTransactionCharts(); + const { + data: transactionCharts, + status: transactionChartsStatus, + } = useTransactionCharts(); useTrackPageview({ app: 'apm', path: 'transaction_overview' }); useTrackPageview({ app: 'apm', path: 'transaction_overview', delay: 15000 }); @@ -137,6 +141,11 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { )} @@ -190,7 +199,7 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index 4198dea5cdbe7..0afc31ecae1e4 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -37,11 +37,13 @@ import { useFormatter } from './use_formatter'; interface TransactionChartProps { charts: ITransactionChartData; urlParams: IUrlParams; + isLoading: boolean; } export function TransactionCharts({ charts, urlParams, + isLoading, }: TransactionChartProps) { const getTPMFormatter = (t: number) => { return `${asDecimal(t)} ${tpmUnit(urlParams.transactionType)}`; @@ -79,9 +81,10 @@ export function TransactionCharts({ { if (serie) { toggleSerie(serie); @@ -97,9 +100,10 @@ export function TransactionCharts({ {tpmLabel(transactionType)} diff --git a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx index 46aae35313eba..f37f82a6ce16f 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx @@ -39,7 +39,7 @@ interface Props { /** * Formatter for legend and tooltip values */ - yTickFormat: (y: number) => string; + yTickFormat?: (y: number) => string; } const XY_HEIGHT = unit * 16; @@ -116,7 +116,7 @@ export function LineChart({ id="y-axis" ticks={3} position={Position.Left} - tickFormat={yTickFormat} + tickFormat={yTickFormat ? yTickFormat : yLabelFormat} labelFormat={yLabelFormat} showGridLines /> diff --git a/x-pack/plugins/apm/public/hooks/useTransactionList.ts b/x-pack/plugins/apm/public/hooks/useTransactionList.ts index 9c3a18b9c0d0d..b2c2cc30f78ec 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionList.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionList.ts @@ -14,8 +14,8 @@ type TransactionsAPIResponse = APIReturnType< '/api/apm/services/{serviceName}/transaction_groups' >; -const DEFAULT_RESPONSE: TransactionsAPIResponse = { - items: [], +const DEFAULT_RESPONSE: Partial = { + items: undefined, isAggregationAccurate: true, bucketSize: 0, }; From 3ddf577c09a2dbbd606ef90722cacc023bb15ed7 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 2 Nov 2020 15:58:45 -0300 Subject: [PATCH 08/15] adding annotations toggle, replacing transaction error rate to elastic chart --- .../app/TransactionOverview/index.tsx | 21 ++-- .../components/app/service_overview/index.tsx | 16 +-- .../TransactionBreakdownGraph/index.tsx | 13 +- .../shared/TransactionBreakdown/index.tsx | 12 +- .../shared/charts/TransactionCharts/index.tsx | 4 +- .../legacy.tsx | 112 ------------------ .../shared/charts/line_chart/index.tsx | 4 +- .../index.tsx | 60 ++++++---- .../public/hooks/useTransactionBreakdown.ts | 2 +- 9 files changed, 75 insertions(+), 169 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx rename x-pack/plugins/apm/public/components/shared/charts/{erroneous_transactions_rate_chart => transaction_error_rate_chart}/index.tsx (63%) diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx index 334d0342045ea..a1e0bae3fccd5 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx @@ -22,7 +22,6 @@ import React, { useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; -import { LegacyChartsSyncContextProvider as ChartsSyncContextProvider } from '../../../context/charts_sync_context'; import { IUrlParams } from '../../../context/UrlParamsContext/types'; import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes'; import { useTransactionCharts } from '../../../hooks/useTransactionCharts'; @@ -139,17 +138,15 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { )} - - - + diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 342152b572f1e..016ee3daf6b51 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -11,7 +11,7 @@ import styled from 'styled-components'; import { useTrackPageview } from '../../../../../observability/public'; import { isRumAgentName } from '../../../../common/agent_name'; import { ChartsSyncContextProvider } from '../../../context/charts_sync_context'; -import { ErroneousTransactionsRateChart } from '../../shared/charts/erroneous_transactions_rate_chart'; +import { TransactionErrorRateChart } from '../../shared/charts/transaction_error_rate_chart'; import { ErrorOverviewLink } from '../../shared/Links/apm/ErrorOverviewLink'; import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink'; import { TransactionOverviewLink } from '../../shared/Links/apm/TransactionOverviewLink'; @@ -125,19 +125,7 @@ export function ServiceOverview({ {!isRumAgentName(agentName) && ( - - -

- {i18n.translate( - 'xpack.apm.serviceOverview.errorRateChartTitle', - { - defaultMessage: 'Error rate', - } - )} -

-
- -
+
)} diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx index efc6fb45436a3..9e3bbf37b1278 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx @@ -23,15 +23,20 @@ import { useUrlParams } from '../../../../hooks/useUrlParams'; import { useChartsSync as useChartsSync2 } from '../../../../hooks/use_charts_sync'; import { unit } from '../../../../style/variables'; import { Annotations } from '../../charts/annotations'; +import { ChartContainer } from '../../charts/chart_container'; import { onBrushEnd } from '../../charts/helper/helper'; const XY_HEIGHT = unit * 16; interface Props { - timeseries: TimeSeries[]; + isLoading: boolean; + timeseries?: TimeSeries[]; } -export function TransactionBreakdownGraph({ timeseries }: Props) { +export function TransactionBreakdownGraph({ + isLoading, + timeseries = [], +}: Props) { const history = useHistory(); const chartRef = React.createRef(); const { event, setEvent } = useChartsSync2(); @@ -50,7 +55,7 @@ export function TransactionBreakdownGraph({ timeseries }: Props) { const xFormatter = niceTimeFormatter([min, max]); return ( -
+ onBrushEnd({ x, history })} @@ -99,6 +104,6 @@ export function TransactionBreakdownGraph({ timeseries }: Props) { ); })} -
+ ); } diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx index 752d03b3bb459..1bea6f0840322 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx @@ -6,11 +6,12 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { FETCH_STATUS } from '../../../hooks/useFetcher'; import { useTransactionBreakdown } from '../../../hooks/useTransactionBreakdown'; import { TransactionBreakdownGraph } from './TransactionBreakdownGraph'; function TransactionBreakdown() { - const { data } = useTransactionBreakdown(); + const { data, status } = useTransactionBreakdown(); const { timeseries } = data; return ( @@ -26,7 +27,14 @@ function TransactionBreakdown() {
- +
diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index 0afc31ecae1e4..9ea1347734954 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -27,7 +27,7 @@ import { LicenseContext } from '../../../../context/LicenseContext'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { ITransactionChartData } from '../../../../selectors/chartSelectors'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; -import { ErroneousTransactionsRateChart } from '../erroneous_transactions_rate_chart/legacy'; +import { TransactionErrorRateChart } from '../transaction_error_rate_chart/'; import { TransactionBreakdown } from '../../TransactionBreakdown'; import { LineChart } from '../line_chart'; import { getResponseTimeTickFormatter } from './helper'; @@ -113,7 +113,7 @@ export function TransactionCharts({ - + diff --git a/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx b/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx deleted file mode 100644 index 29102f606414f..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx +++ /dev/null @@ -1,112 +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 { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { i18n } from '@kbn/i18n'; -import { max } from 'lodash'; -import React, { useCallback } from 'react'; -import { useParams } from 'react-router-dom'; -import { asPercent } from '../../../../../common/utils/formatters'; -import { useLegacyChartsSync as useChartsSync } from '../../../../hooks/use_charts_sync'; -import { useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { callApmApi } from '../../../../services/rest/createCallApmApi'; -// @ts-expect-error -import CustomPlot from '../CustomPlot'; - -const tickFormatY = (y?: number | null) => { - return asPercent(y || 0, 1); -}; - -/** - * "Legacy" version of this chart using react-vis charts. See index.tsx for the - * Elastic Charts version. - * - * This will be removed with #70290. - */ -export function ErroneousTransactionsRateChart() { - const { serviceName } = useParams<{ serviceName?: string }>(); - const { urlParams, uiFilters } = useUrlParams(); - const syncedChartsProps = useChartsSync(); - - const { start, end, transactionType, transactionName } = urlParams; - - const { data } = useFetcher(() => { - if (serviceName && start && end) { - return callApmApi({ - pathname: - '/api/apm/services/{serviceName}/transaction_groups/error_rate', - params: { - path: { - serviceName, - }, - query: { - start, - end, - transactionType, - transactionName, - uiFilters: JSON.stringify(uiFilters), - }, - }, - }); - } - }, [serviceName, start, end, uiFilters, transactionType, transactionName]); - - const combinedOnHover = useCallback( - (hoverX: number) => { - return syncedChartsProps.onHover(hoverX); - }, - [syncedChartsProps] - ); - - const errorRates = data?.transactionErrorRate || []; - const maxRate = max(errorRates.map((errorRate) => errorRate.y)); - - return ( - - - - {i18n.translate('xpack.apm.errorRateChart.title', { - defaultMessage: 'Transaction error rate', - })} - - - - - Number.isFinite(y) ? tickFormatY(y) : 'N/A' - } - /> - - ); -} diff --git a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx index f37f82a6ce16f..22f5299b38af5 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx @@ -40,6 +40,7 @@ interface Props { * Formatter for legend and tooltip values */ yTickFormat?: (y: number) => string; + showAnnotations?: boolean; } const XY_HEIGHT = unit * 16; @@ -51,6 +52,7 @@ export function LineChart({ timeseries, yLabelFormat, yTickFormat, + showAnnotations = true, }: Props) { const history = useHistory(); const chartRef = React.createRef(); @@ -121,7 +123,7 @@ export function LineChart({ showGridLines /> - + {showAnnotations && } {timeseries.map((serie) => { return ( diff --git a/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx similarity index 63% rename from x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/index.tsx rename to x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx index e08e8cec44a56..2502321459930 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiTitle } from '@elastic/eui'; +import { EuiPanel } from '@elastic/eui'; + import { i18n } from '@kbn/i18n'; import React from 'react'; import { useParams } from 'react-router-dom'; @@ -25,7 +28,11 @@ function yTickFormat(y?: number | null) { }); } -export function ErroneousTransactionsRateChart() { +interface Props { + showAnnotations?: boolean; +} + +export function TransactionErrorRateChart({ showAnnotations = true }: Props) { const theme = useTheme(); const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); @@ -56,25 +63,36 @@ export function ErroneousTransactionsRateChart() { const errorRates = data?.transactionErrorRate || []; return ( - + + +

+ {i18n.translate('xpack.apm.errorRate', { + defaultMessage: 'Error rate', + })} +

+
+ +
); } diff --git a/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts b/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts index 08d2300c3254a..0705383ecb0ca 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts @@ -15,7 +15,7 @@ export function useTransactionBreakdown() { uiFilters, } = useUrlParams(); - const { data = { timeseries: [] }, error, status } = useFetcher( + const { data = { timeseries: undefined }, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && transactionType) { return callApmApi({ From ff5b956a545d7fb9504a4fe36462ef589ee2bdc7 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 3 Nov 2020 10:41:19 -0300 Subject: [PATCH 09/15] adding loading state --- .../app/TransactionDetails/index.tsx | 6 +-- .../app/TransactionOverview/index.tsx | 5 +-- .../shared/charts/TransactionCharts/index.tsx | 8 ++-- .../charts/TransactionCharts/use_formatter.ts | 8 ++-- .../apm/public/selectors/chartSelectors.ts | 41 +++++++++---------- 5 files changed, 32 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx index 17b9e0c85d471..412da777d62dd 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx @@ -56,6 +56,7 @@ export function TransactionDetails({ data: transactionChartsData, status: transactionChartsStatus, } = useTransactionCharts(); + const { waterfall, exceedsMax, status: waterfallStatus } = useWaterfall( urlParams ); @@ -125,9 +126,8 @@ export function TransactionDetails({ { if (serie) { @@ -100,9 +100,9 @@ export function TransactionCharts({ {tpmLabel(transactionType)} diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts index 03434f9747f1b..1475ec2934e95 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts @@ -5,7 +5,7 @@ */ import { SeriesIdentifier } from '@elastic/charts'; -import { isEmpty, omit } from 'lodash'; +import { omit } from 'lodash'; import { useState } from 'react'; import { getDurationFormatter, @@ -15,7 +15,7 @@ import { TimeSeries } from '../../../../../typings/timeseries'; import { getMaxY } from './helper'; export const useFormatter = ( - series: TimeSeries[] + series?: TimeSeries[] ): { formatter: TimeFormatter; toggleSerie: (disabledSerie: SeriesIdentifier) => void; @@ -24,11 +24,11 @@ export const useFormatter = ( Record >({}); - const visibleSeries = series.filter( + const visibleSeries = series?.filter( (serie) => disabledSeries[serie.title] === undefined ); - const maxY = getMaxY(isEmpty(visibleSeries) ? series : visibleSeries); + const maxY = getMaxY(visibleSeries || series || []); const formatter = getDurationFormatter(maxY); const toggleSerie = ({ specId }: SeriesIdentifier) => { diff --git a/x-pack/plugins/apm/public/selectors/chartSelectors.ts b/x-pack/plugins/apm/public/selectors/chartSelectors.ts index 8c6093859f969..450f02f70c6a4 100644 --- a/x-pack/plugins/apm/public/selectors/chartSelectors.ts +++ b/x-pack/plugins/apm/public/selectors/chartSelectors.ts @@ -31,40 +31,37 @@ export interface ITpmBucket { } export interface ITransactionChartData { - tpmSeries: ITpmBucket[]; - responseTimeSeries: TimeSeries[]; + tpmSeries?: ITpmBucket[]; + responseTimeSeries?: TimeSeries[]; mlJobId: string | undefined; } -const INITIAL_DATA = { - apmTimeseries: { - responseTimes: { - avg: [], - p95: [], - p99: [], - }, - tpmBuckets: [], - overallAvgDuration: null, - }, +const INITIAL_DATA: Partial = { + apmTimeseries: undefined, anomalyTimeseries: undefined, }; export function getTransactionCharts( { transactionType }: IUrlParams, - { apmTimeseries, anomalyTimeseries }: TimeSeriesAPIResponse = INITIAL_DATA + charts = INITIAL_DATA ): ITransactionChartData { - const tpmSeries = getTpmSeries(apmTimeseries, transactionType); - - const responseTimeSeries = getResponseTimeSeries({ - apmTimeseries, - anomalyTimeseries, - }); + const { apmTimeseries, anomalyTimeseries } = charts; - return { - tpmSeries, - responseTimeSeries, + const transactionCharts: ITransactionChartData = { + tpmSeries: undefined, + responseTimeSeries: undefined, mlJobId: anomalyTimeseries?.jobId, }; + + if (apmTimeseries) { + transactionCharts.tpmSeries = getTpmSeries(apmTimeseries, transactionType); + + transactionCharts.responseTimeSeries = getResponseTimeSeries({ + apmTimeseries, + anomalyTimeseries, + }); + } + return transactionCharts; } export function getResponseTimeSeries({ From b85f1f763dc0ae4521c8ad7b3f50e321c9614cb9 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 3 Nov 2020 11:19:15 -0300 Subject: [PATCH 10/15] adding empty message --- .../ErrorGroupDetails/Distribution/index.tsx | 22 +--------- .../TransactionDetails/Distribution/index.tsx | 27 ++++++------- .../TransactionBreakdownGraph/index.tsx | 40 +++++++++++-------- 3 files changed, 37 insertions(+), 52 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx index 0b24e51baa707..582704a05354d 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx @@ -16,14 +16,12 @@ import { TooltipValue, } from '@elastic/charts'; import { EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import d3 from 'd3'; import React from 'react'; import { asRelativeDateTimeRange } from '../../../../../common/utils/formatters'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import type { ErrorDistributionAPIResponse } from '../../../../../server/lib/errors/distribution/get_distribution'; import { useTheme } from '../../../../hooks/useTheme'; -import { EmptyMessage } from '../../../shared/EmptyMessage'; interface FormattedBucket { x0: number; @@ -34,11 +32,7 @@ interface FormattedBucket { export function getFormattedBuckets( buckets: ErrorDistributionAPIResponse['buckets'], bucketSize: number -): FormattedBucket[] | null { - if (!buckets) { - return null; - } - +): FormattedBucket[] { return buckets.map(({ count, key }) => { return { x0: key, @@ -60,18 +54,6 @@ export function ErrorDistribution({ distribution, title }: Props) { distribution.bucketSize ); - if (!buckets) { - return ( - - ); - } - - // TODO: caue check it - // const averageValue = mean(buckets.map((bucket) => bucket.y)) || 0; const xMin = d3.min(buckets, (d) => d.x0); const xMax = d3.max(buckets, (d) => d.x0); @@ -122,7 +104,7 @@ export function ErrorDistribution({ distribution, title }: Props) { yScaleType={ScaleType.Linear} xAccessor="x0" yAccessors={['y']} - data={buckets} + data={distribution.noHits ? [] : buckets} color={theme.eui.euiColorVis1} />
diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx index aa87f28c798ab..e45927626294a 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx @@ -32,8 +32,8 @@ import { TransactionDistributionAPIResponse } from '../../../../../server/lib/tr import { DistributionBucket } from '../../../../../server/lib/transactions/distribution/get_buckets'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { unit } from '../../../../style/variables'; +import { ChartContainer } from '../../../shared/charts/chart_container'; import { EmptyMessage } from '../../../shared/EmptyMessage'; -import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; interface IChartPoint { x0: number; @@ -45,10 +45,10 @@ interface IChartPoint { } export function getFormattedBuckets( - buckets: DistributionBucket[], - bucketSize: number + buckets?: DistributionBucket[], + bucketSize?: number ) { - if (!buckets) { + if (!buckets || !bucketSize) { return []; } @@ -135,14 +135,8 @@ export function TransactionDistribution(props: Props) { const formatYLong = useCallback(getFormatYLong(transactionType), [ transactionType, ]); - // no data in response - if (!distribution || distribution.noHits) { - // only show loading state if there is no data - else show stale data until new data has loaded - if (isLoading) { - return ; - } - + if ((!distribution || distribution.noHits) && !isLoading) { return ( d.x0) || 0; @@ -213,7 +207,10 @@ export function TransactionDistribution(props: Props) { /> -
+ -
+
); } diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx index 9e3bbf37b1278..16075d667819c 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx @@ -14,6 +14,7 @@ import { ScaleType, Settings, } from '@elastic/charts'; +import { isEmpty } from 'lodash'; import moment from 'moment'; import React, { useEffect } from 'react'; import { useHistory } from 'react-router-dom'; @@ -86,23 +87,28 @@ export function TransactionBreakdownGraph({ - {timeseries.map((serie) => { - return ( - - ); - })} + {isEmpty(timeseries) ? ( + // When timeseries is empty, loads an AreaSeries chart to show the default empty message. + + ) : ( + timeseries.map((serie) => { + return ( + + ); + }) + )}
); From f9d5368456d84c9a38f2d5d6e9ff0037799e25e0 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 3 Nov 2020 11:30:36 -0300 Subject: [PATCH 11/15] fixing i18n --- x-pack/plugins/translations/translations/ja-JP.json | 3 --- x-pack/plugins/translations/translations/zh-CN.json | 3 --- 2 files changed, 6 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 2b7ffa76f0bcb..e325e880ff857 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4892,13 +4892,10 @@ "xpack.apm.errorGroupDetails.errorOccurrenceTitle": "エラーのオカレンス", "xpack.apm.errorGroupDetails.exceptionMessageLabel": "例外メッセージ", "xpack.apm.errorGroupDetails.logMessageLabel": "ログメッセージ", - "xpack.apm.errorGroupDetails.noErrorsLabel": "エラーが見つかりませんでした", "xpack.apm.errorGroupDetails.occurrencesChartLabel": "オカレンス", "xpack.apm.errorGroupDetails.relatedTransactionSample": "関連トランザクションサンプル", "xpack.apm.errorGroupDetails.unhandledLabel": "未対応", "xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel": "ディスカバリで {occurrencesCount} 件の{occurrencesCount, plural, one {ドキュメント} other {ドキュメント}}を表示。", - "xpack.apm.errorRateChart.rateLabel": "レート", - "xpack.apm.errorRateChart.title": "トランザクションエラー率", "xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel": "エラーメッセージと原因", "xpack.apm.errorsTable.groupIdColumnDescription": "スタックトレースのハッシュ。動的パラメータのため、エラーメッセージが異なる場合でも、類似したエラーをグループ化します。", "xpack.apm.errorsTable.groupIdColumnLabel": "グループ ID", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 8f493a39356d0..ebe5e23c5c231 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4894,13 +4894,10 @@ "xpack.apm.errorGroupDetails.errorOccurrenceTitle": "错误发生", "xpack.apm.errorGroupDetails.exceptionMessageLabel": "异常消息", "xpack.apm.errorGroupDetails.logMessageLabel": "日志消息", - "xpack.apm.errorGroupDetails.noErrorsLabel": "未找到任何错误", "xpack.apm.errorGroupDetails.occurrencesChartLabel": "发生次数", "xpack.apm.errorGroupDetails.relatedTransactionSample": "相关的事务样本", "xpack.apm.errorGroupDetails.unhandledLabel": "未处理", "xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel": "在 Discover 查看 {occurrencesCount} 个 {occurrencesCount, plural, one {匹配项} other {匹配项}}。", - "xpack.apm.errorRateChart.rateLabel": "比率", - "xpack.apm.errorRateChart.title": "事务错误率", "xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel": "错误消息和原因", "xpack.apm.errorsTable.groupIdColumnDescription": "堆栈跟踪的哈希。将类似错误分组在一起,即使因动态参数造成错误消息不同。", "xpack.apm.errorsTable.groupIdColumnLabel": "组 ID", From ea5b9abaaf75ab33beb6fbe01a332f69e459605c Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 3 Nov 2020 11:51:15 -0300 Subject: [PATCH 12/15] removing unused files --- .../shared/charts/Histogram/SingleRect.js | 29 - .../Histogram/__test__/Histogram.test.js | 119 -- .../__snapshots__/Histogram.test.js.snap | 1504 ----------------- .../charts/Histogram/__test__/response.json | 106 -- .../shared/charts/Histogram/index.js | 319 ---- .../TransactionCharts/elastic_chart.tsx | 107 -- 6 files changed, 2184 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/elastic_chart.tsx diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js deleted file mode 100644 index ca85ee961f5d8..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js +++ /dev/null @@ -1,29 +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 React from 'react'; -import PropTypes from 'prop-types'; - -function SingleRect({ innerHeight, marginTop, style, x, width }) { - return ( - - ); -} - -SingleRect.requiresSVG = true; -SingleRect.propTypes = { - x: PropTypes.number.isRequired, -}; - -export default SingleRect; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js deleted file mode 100644 index 03fd039a3401e..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js +++ /dev/null @@ -1,119 +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 React from 'react'; - -import d3 from 'd3'; -import { HistogramInner } from '../index'; -import response from './response.json'; -import { - disableConsoleWarning, - toJson, - mountWithTheme, -} from '../../../../../utils/testHelpers'; -import { getFormattedBuckets } from '../../../../app/TransactionDetails/Distribution/index'; -import { - asInteger, - getDurationFormatter, -} from '../../../../../../common/utils/formatters'; - -describe('Histogram', () => { - let mockConsole; - let wrapper; - - const onClick = jest.fn(); - - beforeAll(() => { - mockConsole = disableConsoleWarning('Warning: componentWillReceiveProps'); - }); - - afterAll(() => { - mockConsole.mockRestore(); - }); - - beforeEach(() => { - const buckets = getFormattedBuckets(response.buckets, response.bucketSize); - const xMax = d3.max(buckets, (d) => d.x); - const timeFormatter = getDurationFormatter(xMax); - - wrapper = mountWithTheme( - timeFormatter(time).formatted} - formatYShort={(t) => `${asInteger(t)} occ.`} - formatYLong={(t) => `${asInteger(t)} occurrences`} - tooltipHeader={(bucket) => { - const xFormatted = timeFormatter(bucket.x); - const x0Formatted = timeFormatter(bucket.x0); - return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`; - }} - width={800} - /> - ); - }); - - describe('Initially', () => { - it('should have default markup', () => { - expect(toJson(wrapper)).toMatchSnapshot(); - }); - - it('should not show tooltip', () => { - expect(wrapper.find('Tooltip').length).toBe(0); - }); - }); - - describe('when hovering over an empty bucket', () => { - beforeEach(() => { - wrapper.find('.rv-voronoi__cell').at(2).simulate('mouseOver'); - }); - - it('should not display tooltip', () => { - expect(wrapper.find('Tooltip').length).toBe(0); - }); - }); - - describe('when hovering over a non-empty bucket', () => { - beforeEach(() => { - wrapper.find('.rv-voronoi__cell').at(7).simulate('mouseOver'); - }); - - it('should display tooltip', () => { - const tooltips = wrapper.find('Tooltip'); - - expect(tooltips.length).toBe(1); - expect(tooltips.prop('header')).toBe('811 - 927 ms'); - expect(tooltips.prop('tooltipPoints')).toEqual([ - { value: '49 occurrences' }, - ]); - expect(tooltips.prop('x')).toEqual(869010); - expect(tooltips.prop('y')).toEqual(27.5); - }); - - it('should have correct markup for tooltip', () => { - const tooltips = wrapper.find('Tooltip'); - expect(toJson(tooltips)).toMatchSnapshot(); - }); - }); - - describe('when clicking on a non-empty bucket', () => { - beforeEach(() => { - wrapper.find('.rv-voronoi__cell').at(7).simulate('click'); - }); - - it('should call onClick with bucket', () => { - expect(onClick).toHaveBeenCalledWith({ - style: { cursor: 'pointer' }, - xCenter: 869010, - x0: 811076, - x: 926944, - y: 49, - }); - }); - }); -}); diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap deleted file mode 100644 index a31b9735628ab..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap +++ /dev/null @@ -1,1504 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Histogram Initially should have default markup 1`] = ` -.c0 { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - position: absolute; - top: 0; - left: 0; -} - -
- -
-
- - - - - - - - - - - - - 0 ms - - - - - - 500 ms - - - - - - 1,000 ms - - - - - - 1,500 ms - - - - - - 2,000 ms - - - - - - 2,500 ms - - - - - - 3,000 ms - - - - - - - - - - 0 occ. - - - - - - 28 occ. - - - - - - 55 occ. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-`; - -exports[`Histogram when hovering over a non-empty bucket should have correct markup for tooltip 1`] = ` -.c0 { - margin: 0 16px; - -webkit-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - border: 1px solid #d3dae6; - background: #ffffff; - border-radius: 4px; - font-size: 14px; - color: #000000; -} - -.c1 { - background: #f5f7fa; - border-bottom: 1px solid #d3dae6; - border-radius: 4px 4px 0 0; - padding: 8px; - color: #98a2b3; -} - -.c2 { - margin: 8px; - margin-right: 16px; - font-size: 12px; -} - -.c4 { - color: #98a2b3; - margin: 8px; - font-size: 12px; -} - -.c3 { - color: #69707d; - font-size: 14px; -} - -
- -
- -
- 811 - 927 ms -
-
- -
- -
- 49 occurrences -
-
-
-
- -
- -
-
-
-`; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json deleted file mode 100644 index 302e105dfa997..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "buckets": [ - { "key": 0, "count": 0 }, - { "key": 115868, "count": 0 }, - { "key": 231736, "count": 0 }, - { "key": 347604, "count": 0 }, - { "key": 463472, "count": 0 }, - { - "key": 579340, - "count": 8, - "samples": [ - { - "transactionId": "99437ee4-08d4-41f5-9b2b-93cc32ec3dfb" - } - ] - }, - { - "key": 695208, - "count": 23, - "samples": [ - { - "transactionId": "d327611b-e999-4942-a94f-c60208940180" - } - ] - }, - { - "key": 811076, - "count": 49, - "samples": [ - { - "transactionId": "99c50a5b-44b4-4289-a3d1-a2815d128192" - } - ] - }, - { - "key": 926944, - "count": 51, - "transactionId": "9706a1ec-23f5-4ce8-97e8-69ce35fb0a9a" - }, - { - "key": 1042812, - "count": 46, - "transactionId": "f8d360c3-dd5e-47b6-b082-9e0bf821d3b2" - }, - { - "key": 1158680, - "count": 13, - "samples": [ - { - "transactionId": "8486d3e2-7f15-48df-aa37-6ee9955adbd2" - } - ] - }, - { - "key": 1274548, - "count": 7, - "transactionId": "54b4b5a7-f065-4cab-9016-534e58f4fc0a" - }, - { - "key": 1390416, - "count": 4, - "transactionId": "8cfac2a3-38e7-4d3a-9792-d008b4bcb867" - }, - { - "key": 1506284, - "count": 3, - "transactionId": "ce3f3bd3-a37c-419e-bb9c-5db956ded149" - }, - { "key": 1622152, "count": 0 }, - { - "key": 1738020, - "count": 4, - "transactionId": "2300174b-85d8-40ba-a6cb-eeba2a49debf" - }, - { "key": 1853888, "count": 0 }, - { "key": 1969756, "count": 0 }, - { - "key": 2085624, - "count": 1, - "transactionId": "774955a4-2ba3-4461-81a6-65759db4805d" - }, - { "key": 2201492, "count": 0 }, - { "key": 2317360, "count": 0 }, - { "key": 2433228, "count": 0 }, - { "key": 2549096, "count": 0 }, - { "key": 2664964, "count": 0 }, - { - "key": 2780832, - "count": 1, - "transactionId": "035d1b9d-af71-46cf-8910-57bd4faf412d" - }, - { - "key": 2896700, - "count": 1, - "transactionId": "4a845b32-9de4-4796-8ef4-d7bbdedc9099" - }, - { "key": 3012568, "count": 0 }, - { - "key": 3128436, - "count": 1, - "transactionId": "68620ffb-7a1b-4f8e-b9bb-009fa5b092be" - } - ], - "bucketSize": 115868, - "defaultBucketIndex": 12 -} diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js deleted file mode 100644 index 3b2109d68c613..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js +++ /dev/null @@ -1,319 +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 React, { PureComponent } from 'react'; -import d3 from 'd3'; -import { isEmpty } from 'lodash'; -import PropTypes from 'prop-types'; -import { scaleLinear } from 'd3-scale'; -import styled from 'styled-components'; -import SingleRect from './SingleRect'; -import { - XYPlot, - XAxis, - YAxis, - HorizontalGridLines, - VerticalRectSeries, - Voronoi, - makeWidthFlexible, - VerticalGridLines, -} from 'react-vis'; -import { unit } from '../../../../style/variables'; -import Tooltip from '../Tooltip'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { tint } from 'polished'; -import { getTimeTicksTZ, getDomainTZ } from '../helper/timezone'; -import Legends from '../CustomPlot/Legends'; -import StatusText from '../CustomPlot/StatusText'; -import { i18n } from '@kbn/i18n'; -import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; - -const XY_HEIGHT = unit * 10; -const XY_MARGIN = { - top: unit, - left: unit * 5, - right: unit, - bottom: unit * 2, -}; - -const X_TICK_TOTAL = 8; - -// position absolutely to make sure that window resizing/zooming works -const ChartsWrapper = styled.div` - user-select: none; - position: absolute; - top: 0; - left: 0; -`; - -export class HistogramInner extends PureComponent { - constructor(props) { - super(props); - this.state = { - hoveredBucket: {}, - }; - } - - onClick = (bucket) => { - if (this.props.onClick) { - this.props.onClick(bucket); - } - }; - - onHover = (bucket) => { - this.setState({ hoveredBucket: bucket }); - }; - - onBlur = () => { - this.setState({ hoveredBucket: {} }); - }; - - getChartData(items, selectedItem) { - const yMax = d3.max(items, (d) => d.y); - const MINIMUM_BUCKET_SIZE = yMax * 0.02; - - return items.map((item) => { - const padding = (item.x - item.x0) / 20; - return { - ...item, - color: - item === selectedItem - ? theme.euiColorVis1 - : tint(0.5, theme.euiColorVis1), - x0: item.x0 + padding, - x: item.x - padding, - y: item.y > 0 ? Math.max(item.y, MINIMUM_BUCKET_SIZE) : 0, - }; - }); - } - - render() { - const { - backgroundHover, - bucketIndex, - buckets, - bucketSize, - formatX, - formatYShort, - formatYLong, - tooltipFooter, - tooltipHeader, - verticalLineHover, - width: XY_WIDTH, - height, - legends, - } = this.props; - const { hoveredBucket } = this.state; - if (isEmpty(buckets) || XY_WIDTH === 0) { - return null; - } - - const isTimeSeries = - this.props.xType === 'time' || this.props.xType === 'time-utc'; - - const xMin = d3.min(buckets, (d) => d.x0); - const xMax = d3.max(buckets, (d) => d.x); - const yMin = 0; - const yMax = d3.max(buckets, (d) => d.y); - const selectedBucket = buckets[bucketIndex]; - const chartData = this.getChartData(buckets, selectedBucket); - - const x = scaleLinear() - .domain([xMin, xMax]) - .range([XY_MARGIN.left, XY_WIDTH - XY_MARGIN.right]); - - const y = scaleLinear().domain([yMin, yMax]).range([XY_HEIGHT, 0]).nice(); - - const [xMinZone, xMaxZone] = getDomainTZ(xMin, xMax); - const xTickValues = isTimeSeries - ? getTimeTicksTZ({ - domain: [xMinZone, xMaxZone], - totalTicks: X_TICK_TOTAL, - width: XY_WIDTH, - }) - : undefined; - - const xDomain = x.domain(); - const yDomain = y.domain(); - const yTickValues = [0, yDomain[1] / 2, yDomain[1]]; - const shouldShowTooltip = - hoveredBucket.x > 0 && (hoveredBucket.y > 0 || isTimeSeries); - - const showVerticalLineHover = verticalLineHover(hoveredBucket); - const showBackgroundHover = backgroundHover(hoveredBucket); - - const hasValidCoordinates = buckets.some((bucket) => - isValidCoordinateValue(bucket.y) - ); - const noHits = this.props.noHits || !hasValidCoordinates; - - const xyPlotProps = { - dontCheckIfEmpty: true, - xType: this.props.xType, - width: XY_WIDTH, - height: XY_HEIGHT, - margin: XY_MARGIN, - xDomain: xDomain, - yDomain: yDomain, - }; - - const xAxisProps = { - style: { strokeWidth: '1px' }, - marginRight: 10, - tickSize: 0, - tickTotal: X_TICK_TOTAL, - tickFormat: formatX, - tickValues: xTickValues, - }; - - const emptyStateChart = ( - - - - - ); - - return ( -
- - {noHits ? ( - <>{emptyStateChart} - ) : ( - <> - - - - - - {showBackgroundHover && ( - - )} - - {shouldShowTooltip && ( - - )} - - {selectedBucket && ( - - )} - - - - {showVerticalLineHover && hoveredBucket?.x && ( - - )} - - { - return { - ...bucket, - xCenter: (bucket.x0 + bucket.x) / 2, - }; - })} - onClick={this.onClick} - onHover={this.onHover} - onBlur={this.onBlur} - x={(d) => x(d.xCenter)} - y={() => 1} - /> - - - {legends && ( - {}} - truncateLegends={false} - noHits={noHits} - /> - )} - - )} - -
- ); - } -} - -HistogramInner.propTypes = { - backgroundHover: PropTypes.func, - bucketIndex: PropTypes.number, - buckets: PropTypes.array.isRequired, - bucketSize: PropTypes.number.isRequired, - formatX: PropTypes.func, - formatYLong: PropTypes.func, - formatYShort: PropTypes.func, - onClick: PropTypes.func, - tooltipFooter: PropTypes.func, - tooltipHeader: PropTypes.func, - verticalLineHover: PropTypes.func, - width: PropTypes.number.isRequired, - height: PropTypes.number, - xType: PropTypes.string, - legends: PropTypes.array, - noHits: PropTypes.bool, -}; - -HistogramInner.defaultProps = { - backgroundHover: () => null, - formatYLong: (value) => value, - formatYShort: (value) => value, - tooltipFooter: () => null, - tooltipHeader: () => null, - verticalLineHover: () => null, - xType: 'linear', - noHits: false, - height: XY_HEIGHT, -}; - -export default makeWidthFlexible(HistogramInner); diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/elastic_chart.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/elastic_chart.tsx deleted file mode 100644 index 3a9f9e24844c6..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/elastic_chart.tsx +++ /dev/null @@ -1,107 +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 { - Axis, - Chart, - LineSeries, - niceTimeFormatter, - Placement, - Position, - ScaleType, - Settings, - SettingsSpec, -} from '@elastic/charts'; -import moment from 'moment'; -import React, { useEffect } from 'react'; -import { TimeSeries } from '../../../../../typings/timeseries'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useChartsSync as useChartsSync2 } from '../../../../hooks/use_charts_sync'; -import { unit } from '../../../../style/variables'; -import { Annotations } from '../annotations'; - -const XY_HEIGHT = unit * 16; - -interface Props { - timeseries: TimeSeries[]; - tickFormatY: (y: number) => string; - id: string; -} - -export function ElasticChart({ timeseries, tickFormatY, id }: Props) { - const chartRef = React.createRef(); - const { event, setEvent } = useChartsSync2(); - const { urlParams } = useUrlParams(); - const { start, end } = urlParams; - - useEffect(() => { - if (event.chartId !== id && chartRef.current) { - chartRef.current.dispatchExternalPointerEvent(event); - } - }, [event, chartRef, id]); - - const min = moment.utc(start).valueOf(); - const max = moment.utc(end).valueOf(); - - const xFormatter = niceTimeFormatter([min, max]); - - const chartTheme: SettingsSpec['theme'] = { - lineSeriesStyle: { - point: { visible: false }, - line: { strokeWidth: 2 }, - }, - }; - - return ( -
- - { - setEvent(currEvent); - }} - externalPointerEvents={{ - tooltip: { visible: true, placement: Placement.Bottom }, - }} - showLegend - showLegendExtra - legendPosition={Position.Bottom} - xDomain={{ min, max }} - /> - - - - - - {timeseries.map((serie) => { - return ( - - ); - })} - -
- ); -} From 6d166871de4093d2fe49a59e213e5b6c20dbfb55 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 3 Nov 2020 12:50:47 -0300 Subject: [PATCH 13/15] fixing i18n --- x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 2 files changed, 2 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e325e880ff857..12deddef1a653 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4923,7 +4923,6 @@ "xpack.apm.header.badge.readOnly.text": "読み込み専用", "xpack.apm.header.badge.readOnly.tooltip": "を保存できませんでした", "xpack.apm.helpMenu.upgradeAssistantLink": "アップグレードアシスタント", - "xpack.apm.histogram.plot.noDataLabel": "この時間範囲のデータがありません。", "xpack.apm.home.alertsMenu.alerts": "アラート", "xpack.apm.home.alertsMenu.createAnomalyAlert": "異常アラートを作成", "xpack.apm.home.alertsMenu.createThresholdAlert": "しきい値アラートを作成", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ebe5e23c5c231..e6540e26e6e6c 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4925,7 +4925,6 @@ "xpack.apm.header.badge.readOnly.text": "只读", "xpack.apm.header.badge.readOnly.tooltip": "无法保存", "xpack.apm.helpMenu.upgradeAssistantLink": "升级助手", - "xpack.apm.histogram.plot.noDataLabel": "此时间范围内没有数据。", "xpack.apm.home.alertsMenu.alerts": "告警", "xpack.apm.home.alertsMenu.createAnomalyAlert": "创建异常告警", "xpack.apm.home.alertsMenu.createThresholdAlert": "创建阈值告警", From 38c43ad9829d2bd13fd675b1682f488538346b16 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 3 Nov 2020 14:59:07 -0300 Subject: [PATCH 14/15] removing e2e test since elastic charts uses canvas --- .../plugins/apm/e2e/cypress/integration/apm.feature | 3 +-- .../apm/e2e/cypress/support/step_definitions/apm.ts | 13 ------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/x-pack/plugins/apm/e2e/cypress/integration/apm.feature b/x-pack/plugins/apm/e2e/cypress/integration/apm.feature index 285615108266b..494a6b5fadb5b 100644 --- a/x-pack/plugins/apm/e2e/cypress/integration/apm.feature +++ b/x-pack/plugins/apm/e2e/cypress/integration/apm.feature @@ -3,5 +3,4 @@ Feature: APM Scenario: Transaction duration charts Given a user browses the APM UI application When the user inspects the opbeans-node service - Then should redirect to correct path with correct params - And should have correct y-axis ticks + Then should redirect to correct path with correct params \ No newline at end of file diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts index 50c620dca9ddf..42c2bc7ffd318 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts @@ -29,16 +29,3 @@ Then(`should redirect to correct path with correct params`, () => { cy.url().should('contain', `/app/apm/services/opbeans-node/transactions`); cy.url().should('contain', `transactionType=request`); }); - -Then(`should have correct y-axis ticks`, () => { - const yAxisTick = - '[data-cy=transaction-duration-charts] .rv-xy-plot__axis--vertical .rv-xy-plot__axis__tick__text'; - - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); - - // literal assertions because snapshot() doesn't retry - cy.get(yAxisTick).eq(2).should('have.text', '55 ms'); - cy.get(yAxisTick).eq(1).should('have.text', '28 ms'); - cy.get(yAxisTick).eq(0).should('have.text', '0 ms'); -}); From df7c68613557384be3a889e27d6cd0e3ec68921f Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 6 Nov 2020 14:42:42 -0300 Subject: [PATCH 15/15] addressing pr comments --- .../ErrorGroupDetails/Distribution/index.tsx | 10 +- .../TransactionDetails/Distribution/index.tsx | 35 +++---- .../app/TransactionDetails/index.tsx | 7 +- .../app/TransactionOverview/index.tsx | 10 +- .../TransactionBreakdownGraph/index.tsx | 23 ++--- .../shared/TransactionBreakdown/index.tsx | 7 +- .../shared/charts/TransactionCharts/index.tsx | 11 ++- .../shared/charts/chart_container.test.tsx | 91 ++++++++++++++++--- .../shared/charts/chart_container.tsx | 41 +++++++-- .../shared/charts/line_chart/index.tsx | 9 +- .../transaction_error_rate_chart/index.tsx | 12 +-- .../lib/errors/distribution/get_buckets.ts | 2 +- 12 files changed, 168 insertions(+), 90 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx index 582704a05354d..a17bf7e93e466 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx @@ -89,13 +89,7 @@ export function ErrorDistribution({ distribution, title }: Props) { showOverlappingTicks tickFormat={xFormatter} /> - `${value} occ.`} - /> +
diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx index e45927626294a..bf1bda793179f 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx @@ -27,10 +27,11 @@ import { ValuesType } from 'utility-types'; import { useTheme } from '../../../../../../observability/public'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { TransactionDistributionAPIResponse } from '../../../../../server/lib/transactions/distribution'; +import type { TransactionDistributionAPIResponse } from '../../../../../server/lib/transactions/distribution'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { DistributionBucket } from '../../../../../server/lib/transactions/distribution/get_buckets'; +import type { DistributionBucket } from '../../../../../server/lib/transactions/distribution/get_buckets'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; import { unit } from '../../../../style/variables'; import { ChartContainer } from '../../../shared/charts/chart_container'; import { EmptyMessage } from '../../../shared/EmptyMessage'; @@ -109,21 +110,20 @@ const getFormatYLong = (transactionType: string | undefined) => (t: number) => { interface Props { distribution?: TransactionDistributionAPIResponse; urlParams: IUrlParams; - isLoading: boolean; + fetchStatus: FETCH_STATUS; bucketIndex: number; onBucketClick: ( bucket: ValuesType ) => void; } -export function TransactionDistribution(props: Props) { - const { - distribution, - urlParams: { transactionType }, - isLoading, - bucketIndex, - onBucketClick, - } = props; +export function TransactionDistribution({ + distribution, + urlParams: { transactionType }, + fetchStatus, + bucketIndex, + onBucketClick, +}: Props) { const theme = useTheme(); /* eslint-disable-next-line react-hooks/exhaustive-deps */ @@ -135,8 +135,12 @@ export function TransactionDistribution(props: Props) { const formatYLong = useCallback(getFormatYLong(transactionType), [ transactionType, ]); + // no data in response - if ((!distribution || distribution.noHits) && !isLoading) { + if ( + (!distribution || distribution.noHits) && + fetchStatus !== FETCH_STATUS.LOADING + ) { return ( formatYShort(value)} /> { - return `${value}`; - }} + tickFormat={(value: string) => value} minBarHeight={2} id="transactionDurationDistribution" name={(series: XYChartSeriesIdentifier) => { diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx index 412da777d62dd..e4c36b028e55c 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx @@ -125,10 +125,7 @@ export function TransactionDetails({ @@ -139,7 +136,7 @@ export function TransactionDetails({ { diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx index 4a5e7f3a4cc11..df9e673ed4847 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx @@ -22,6 +22,7 @@ import React, { useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; +import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; import { IUrlParams } from '../../../context/UrlParamsContext/types'; import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes'; import { useTransactionCharts } from '../../../hooks/useTransactionCharts'; @@ -32,12 +33,10 @@ import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { TransactionTypeFilter } from '../../shared/LocalUIFilters/TransactionTypeFilter'; +import { Correlations } from '../Correlations'; import { TransactionList } from './TransactionList'; import { useRedirect } from './useRedirect'; -import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; import { UserExperienceCallout } from './user_experience_callout'; -import { Correlations } from '../Correlations'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; function getRedirectLocation({ urlParams, @@ -139,10 +138,7 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { )} diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx index 16075d667819c..05cae589c19fc 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx @@ -14,12 +14,12 @@ import { ScaleType, Settings, } from '@elastic/charts'; -import { isEmpty } from 'lodash'; import moment from 'moment'; import React, { useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { asPercent } from '../../../../../common/utils/formatters'; import { TimeSeries } from '../../../../../typings/timeseries'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { useChartsSync as useChartsSync2 } from '../../../../hooks/use_charts_sync'; import { unit } from '../../../../style/variables'; @@ -30,14 +30,11 @@ import { onBrushEnd } from '../../charts/helper/helper'; const XY_HEIGHT = unit * 16; interface Props { - isLoading: boolean; + fetchStatus: FETCH_STATUS; timeseries?: TimeSeries[]; } -export function TransactionBreakdownGraph({ - isLoading, - timeseries = [], -}: Props) { +export function TransactionBreakdownGraph({ fetchStatus, timeseries }: Props) { const history = useHistory(); const chartRef = React.createRef(); const { event, setEvent } = useChartsSync2(); @@ -56,7 +53,11 @@ export function TransactionBreakdownGraph({ const xFormatter = niceTimeFormatter([min, max]); return ( - + onBrushEnd({ x, history })} @@ -87,10 +88,7 @@ export function TransactionBreakdownGraph({ - {isEmpty(timeseries) ? ( - // When timeseries is empty, loads an AreaSeries chart to show the default empty message. - - ) : ( + {timeseries?.length ? ( timeseries.map((serie) => { return ( ); }) + ) : ( + // When timeseries is empty, loads an AreaSeries chart to show the default empty message. + )} diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx index 1bea6f0840322..9b0c041aaf7b5 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx @@ -6,7 +6,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; import { useTransactionBreakdown } from '../../../hooks/useTransactionBreakdown'; import { TransactionBreakdownGraph } from './TransactionBreakdownGraph'; @@ -29,11 +28,7 @@ function TransactionBreakdown() { diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index 835094eab8996..2a5948d0ebf0b 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -25,11 +25,12 @@ import { Coordinate } from '../../../../../typings/timeseries'; import { ChartsSyncContextProvider } from '../../../../context/charts_sync_context'; import { LicenseContext } from '../../../../context/LicenseContext'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; import { ITransactionChartData } from '../../../../selectors/chartSelectors'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; -import { TransactionErrorRateChart } from '../transaction_error_rate_chart/'; import { TransactionBreakdown } from '../../TransactionBreakdown'; import { LineChart } from '../line_chart'; +import { TransactionErrorRateChart } from '../transaction_error_rate_chart/'; import { getResponseTimeTickFormatter } from './helper'; import { MLHeader } from './ml_header'; import { useFormatter } from './use_formatter'; @@ -37,13 +38,13 @@ import { useFormatter } from './use_formatter'; interface TransactionChartProps { charts: ITransactionChartData; urlParams: IUrlParams; - isLoading: boolean; + fetchStatus: FETCH_STATUS; } export function TransactionCharts({ charts, urlParams, - isLoading, + fetchStatus, }: TransactionChartProps) { const getTPMFormatter = (t: number) => { return `${asDecimal(t)} ${tpmUnit(urlParams.transactionType)}`; @@ -81,7 +82,7 @@ export function TransactionCharts({ {tpmLabel(transactionType)} { - describe('when isLoading is true', () => { - it('shows loading the indicator', () => { - const component = render( - + describe('loading indicator', () => { + it('shows loading when status equals to Loading or Pending and has no data', () => { + [FETCH_STATUS.PENDING, FETCH_STATUS.LOADING].map((status) => { + const { queryAllByTestId } = render( + +
My amazing component
+
+ ); + + expect(queryAllByTestId('loading')[0]).toBeInTheDocument(); + }); + }); + it('does not show loading when status equals to Loading or Pending and has data', () => { + [FETCH_STATUS.PENDING, FETCH_STATUS.LOADING].map((status) => { + const { queryAllByText } = render( + +
My amazing component
+
+ ); + expect(queryAllByText('My amazing component')[0]).toBeInTheDocument(); + }); + }); + }); + + describe('failure indicator', () => { + it('shows failure message when status equals to Failure and has data', () => { + const { getByText } = render( +
My amazing component
); - - expect(component.getByTestId('loading')).toBeInTheDocument(); + expect( + getByText( + 'An error happened when trying to fetch data. Please try again' + ) + ).toBeInTheDocument(); + }); + it('shows failure message when status equals to Failure and has no data', () => { + const { getByText } = render( + +
My amazing component
+
+ ); + expect( + getByText( + 'An error happened when trying to fetch data. Please try again' + ) + ).toBeInTheDocument(); }); }); - describe('when isLoading is false', () => { - it('does not show the loading indicator', () => { - const component = render( - + describe('render component', () => { + it('shows children component when status Success and has data', () => { + const { getByText } = render( +
My amazing component
); - - expect(component.queryByTestId('loading')).not.toBeInTheDocument(); + expect(getByText('My amazing component')).toBeInTheDocument(); + }); + it('shows children component when status Success and has no data', () => { + const { getByText } = render( + +
My amazing component
+
+ ); + expect(getByText('My amazing component')).toBeInTheDocument(); }); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx index a6f579308597f..b4486f1e9b94a 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx @@ -3,27 +3,56 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiLoadingChart } from '@elastic/eui'; + +import { EuiLoadingChart, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React from 'react'; +import { FETCH_STATUS } from '../../../hooks/useFetcher'; interface Props { - isLoading: boolean; + hasData: boolean; + status: FETCH_STATUS; height: number; children: React.ReactNode; } -export function ChartContainer({ isLoading, children, height }: Props) { +export function ChartContainer({ children, height, status, hasData }: Props) { + if ( + !hasData && + (status === FETCH_STATUS.LOADING || status === FETCH_STATUS.PENDING) + ) { + return ; + } + + if (status === FETCH_STATUS.FAILURE) { + return ; + } + + return
{children}
; +} + +function LoadingChartPlaceholder({ height }: { height: number }) { return (
- {isLoading && } - {children} +
); } + +function FailedChartPlaceholder({ height }: { height: number }) { + return ( + + {i18n.translate('xpack.apm.chart.error', { + defaultMessage: + 'An error happened when trying to fetch data. Please try again', + })} + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx index 22f5299b38af5..507acc49d89db 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx @@ -20,16 +20,17 @@ import moment from 'moment'; import React, { useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { TimeSeries } from '../../../../../typings/timeseries'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { useChartsSync } from '../../../../hooks/use_charts_sync'; import { unit } from '../../../../style/variables'; +import { Annotations } from '../annotations'; import { ChartContainer } from '../chart_container'; import { onBrushEnd } from '../helper/helper'; -import { Annotations } from '../annotations'; interface Props { id: string; - isLoading: boolean; + fetchStatus: FETCH_STATUS; onToggleLegend?: LegendItemListener; timeseries: TimeSeries[]; /** @@ -47,7 +48,7 @@ const XY_HEIGHT = unit * 16; export function LineChart({ id, - isLoading, + fetchStatus, onToggleLegend, timeseries, yLabelFormat, @@ -87,7 +88,7 @@ export function LineChart({ ); return ( - + onBrushEnd({ x, history })} diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx index 2502321459930..2743d12a3eb04 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx @@ -4,14 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiTitle } from '@elastic/eui'; -import { EuiPanel } from '@elastic/eui'; - +import { EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { useParams } from 'react-router-dom'; import { asPercent } from '../../../../../common/utils/formatters'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/useFetcher'; import { useTheme } from '../../../../hooks/useTheme'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; @@ -74,11 +72,7 @@ export function TransactionErrorRateChart({ showAnnotations = true }: Props) { 0 ? buckets : [], }; }