From 3864554b3661fd65106e0d9c5c4d50cfa167dd68 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 25 Apr 2023 16:30:11 +0200 Subject: [PATCH] [Synthetics] Monitor overview item display median duration (#155694) ## Summary Display median duration instead of avg on monitor overview item image --- .../monitor_summary/duration_panel.tsx | 8 +-- .../monitor_summary/duration_sparklines.tsx | 4 +- .../overview/overview/metric_item.tsx | 51 +++++++++++++++---- .../overview/overview/overview_grid.test.tsx | 22 +++++--- .../overview/overview/overview_grid_item.tsx | 12 ++++- .../hooks/use_last_50_duration_chart.test.ts | 10 +++- .../hooks/use_last_50_duration_chart.ts | 29 ++++++++--- .../synthetics/utils/formatting/format.ts | 15 +++++- .../translations/translations/fr-FR.json | 3 +- .../translations/translations/ja-JP.json | 3 +- .../translations/translations/zh-CN.json | 3 +- 11 files changed, 121 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_panel.tsx index 85ab6773033bef..7ca1d1e003cc44 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_panel.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_panel.tsx @@ -42,7 +42,7 @@ export const DurationPanel = (props: DurationPanelProps) => { attributes={[ { time: props, - name: AVG_DURATION_LABEL, + name: MEDIAN_DURATION_LABEL, dataType: 'synthetics', selectedMetricField: 'monitor_duration', reportDefinitions: { @@ -55,9 +55,9 @@ export const DurationPanel = (props: DurationPanelProps) => { ); }; -export const AVG_DURATION_LABEL = i18n.translate( - 'xpack.synthetics.monitorDetails.summary.avgDuration', +export const MEDIAN_DURATION_LABEL = i18n.translate( + 'xpack.synthetics.monitorDetails.summary.medianDuration', { - defaultMessage: 'Avg. duration', + defaultMessage: 'Median duration', } ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx index 1c1370d4da3ab2..5851d1c47cdf50 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { ReportTypes } from '@kbn/exploratory-view-plugin/public'; import { useTheme } from '@kbn/observability-plugin/public'; -import { AVG_DURATION_LABEL } from './duration_panel'; +import { MEDIAN_DURATION_LABEL } from './duration_panel'; import { useMonitorQueryId } from '../hooks/use_monitor_query_id'; import { ClientPluginsStart } from '../../../../../plugin'; import { useSelectedLocation } from '../hooks/use_selected_location'; @@ -47,7 +47,7 @@ export const DurationSparklines = (props: DurationSparklinesProps) => { { seriesType: 'area', time: props, - name: AVG_DURATION_LABEL, + name: MEDIAN_DURATION_LABEL, dataType: 'synthetics', selectedMetricField: 'monitor.duration.us', reportDefinitions: { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/metric_item.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/metric_item.tsx index 88cfaf75e061c4..369010408917c0 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/metric_item.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/metric_item.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; import { Chart, Settings, Metric, MetricTrendShape } from '@elastic/charts'; -import { EuiPanel } from '@elastic/eui'; +import { EuiPanel, EuiIconTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { DARK_THEME } from '@elastic/charts'; import { useTheme } from '@kbn/observability-plugin/public'; import { useDispatch, useSelector } from 'react-redux'; @@ -46,13 +46,19 @@ export const getColor = ( export const MetricItem = ({ monitor, - averageDuration, + medianDuration, + maxDuration, + minDuration, + avgDuration, data, onClick, }: { monitor: MonitorOverviewItem; data: Array<{ x: number; y: number }>; - averageDuration: number; + medianDuration: number; + avgDuration: number; + minDuration: number; + maxDuration: number; onClick: (params: { id: string; configId: string; location: string; locationId: string }) => void; }) => { const [isMouseOver, setIsMouseOver] = useState(false); @@ -119,15 +125,42 @@ export const MetricItem = ({ { title: monitor.name, subtitle: locationName, - value: averageDuration, + value: medianDuration, trendShape: MetricTrendShape.Area, trend: data, extra: ( - - {i18n.translate('xpack.synthetics.overview.duration.label', { - defaultMessage: 'Duration Avg.', - })} - + + + {i18n.translate('xpack.synthetics.overview.duration.label', { + defaultMessage: 'Duration', + })} + + + + + ), valueFormatter: (d: number) => formatDuration(d), color: getColor(theme, monitor.isEnabled, status), diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.test.tsx index 8238d7b26b62a7..ed470f6f24bced 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.test.tsx @@ -64,9 +64,14 @@ describe('Overview Grid', () => { const perPage = 20; it('renders correctly', async () => { - jest - .spyOn(hooks, 'useLast50DurationChart') - .mockReturnValue({ data: getMockChart(), averageDuration: 30000, loading: false }); + jest.spyOn(hooks, 'useLast50DurationChart').mockReturnValue({ + data: getMockChart(), + avgDuration: 30000, + minDuration: 0, + maxDuration: 50000, + medianDuration: 15000, + loading: false, + }); const { getByText, getAllByTestId, queryByText } = render(, { state: { @@ -124,9 +129,14 @@ describe('Overview Grid', () => { }); it('displays showing all monitors label when reaching the end of the list', async () => { - jest - .spyOn(hooks, 'useLast50DurationChart') - .mockReturnValue({ data: getMockChart(), averageDuration: 30000, loading: false }); + jest.spyOn(hooks, 'useLast50DurationChart').mockReturnValue({ + data: getMockChart(), + avgDuration: 30000, + minDuration: 0, + maxDuration: 50000, + medianDuration: 15000, + loading: false, + }); const { getByText } = render(, { state: { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid_item.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid_item.tsx index de153cf01eca7c..952a48d4247336 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid_item.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid_item.tsx @@ -32,12 +32,20 @@ export const OverviewGridItem = ({ const { timestamp } = useStatusByLocationOverview(monitor.configId, locationName); - const { data, averageDuration } = useLast50DurationChart({ + const { data, medianDuration, maxDuration, avgDuration, minDuration } = useLast50DurationChart({ locationId: monitor.location?.id, monitorId: monitor.id, timestamp, }); return ( - + ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_last_50_duration_chart.test.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_last_50_duration_chart.test.ts index bb2e74712322f8..47cb97793bab1f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_last_50_duration_chart.test.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_last_50_duration_chart.test.ts @@ -33,7 +33,10 @@ describe('useLast50DurationChart', () => { { wrapper: WrappedHelper } ); expect(result.current).toEqual({ - averageDuration: 4.5, + medianDuration: 5, + maxDuration: 9, + minDuration: 0, + avgDuration: 4.5, data: [ { x: 0, @@ -132,7 +135,10 @@ describe('useLast50DurationChart', () => { ]; expect(result.current).toEqual({ - averageDuration: data.reduce((acc, datum) => (acc += datum.y), 0) / 9, + medianDuration: [...data].sort((a, b) => a.y - b.y)[Math.floor(data.length / 2)].y, + maxDuration: 9, + minDuration: 0, + avgDuration: 4.4, data, loading: false, }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_last_50_duration_chart.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_last_50_duration_chart.ts index 78e4cc6cecbbf7..8cb7d524635c72 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_last_50_duration_chart.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_last_50_duration_chart.ts @@ -28,20 +28,23 @@ export function useLast50DurationChart({ size: 50, timestamp, }); - const { data, averageDuration } = useMemo(() => { + const { data, median, min, max, avg } = useMemo(() => { if (loading) { return { data: [], - averageDuration: 0, + median: 0, + avg: 0, + min: 0, + max: 0, }; } - let totalDuration = 0; + + // calculate min, max, average duration and median const coords = hits .reverse() // results are returned in desc order by timestamp. Reverse to ensure the data is in asc order by timestamp .map((hit, index) => { const duration = hit?.['monitor.duration.us']?.[0]; - totalDuration += duration || 0; if (duration === undefined) { return null; } @@ -52,18 +55,30 @@ export function useLast50DurationChart({ }) .filter((item) => item !== null); + const sortedByDuration = [...hits].sort( + (a, b) => (a?.['monitor.duration.us']?.[0] || 0) - (b?.['monitor.duration.us']?.[0] || 0) + ); + return { data: coords as Array<{ x: number; y: number }>, - averageDuration: totalDuration / coords.length, + median: sortedByDuration[Math.floor(hits.length / 2)]?.['monitor.duration.us']?.[0] || 0, + avg: + sortedByDuration.reduce((acc, curr) => acc + (curr?.['monitor.duration.us']?.[0] || 0), 0) / + hits.length, + min: sortedByDuration[0]?.['monitor.duration.us']?.[0] || 0, + max: sortedByDuration[sortedByDuration.length - 1]?.['monitor.duration.us']?.[0] || 0, }; }, [hits, loading]); return useMemo( () => ({ data, - averageDuration, + medianDuration: median, + avgDuration: avg, + minDuration: min, + maxDuration: max, loading, }), - [loading, data, averageDuration] + [data, median, avg, min, max, loading] ); } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/formatting/format.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/formatting/format.ts index 5dab17f55ad68b..433d4a9c8cfec2 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/formatting/format.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/formatting/format.ts @@ -25,8 +25,14 @@ export const microsToMillis = (microseconds: number | null): number | null => { return Math.floor(microseconds / NUM_MICROSECONDS_IN_MILLISECOND); }; -export const formatDuration = (durationMicros: number) => { +export const formatDuration = (durationMicros: number, { noSpace }: { noSpace?: true } = {}) => { if (durationMicros < MILLIS_LIMIT) { + if (noSpace) { + return i18n.translate('xpack.synthetics.overview.durationMsFormattingNoSpace', { + values: { millis: microsToMillis(durationMicros) }, + defaultMessage: '{millis}ms', + }); + } return i18n.translate('xpack.synthetics.overview.durationMsFormatting', { values: { millis: microsToMillis(durationMicros) }, defaultMessage: '{millis} ms', @@ -34,6 +40,13 @@ export const formatDuration = (durationMicros: number) => { } const seconds = (durationMicros / ONE_SECOND_AS_MICROS).toFixed(0); + if (noSpace) { + return i18n.translate('xpack.synthetics.overview.durationSecondsFormattingNoSpace', { + values: { seconds }, + defaultMessage: '{seconds}s', + }); + } + return i18n.translate('xpack.synthetics.overview.durationSecondsFormatting', { values: { seconds }, defaultMessage: '{seconds} s', diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index d3a86b78b95515..191f6310ace50b 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -34806,7 +34806,6 @@ "xpack.synthetics.monitorDetails.statusBar.pingType.icmp": "ICMP", "xpack.synthetics.monitorDetails.statusBar.pingType.tcp": "TCP", "xpack.synthetics.monitorDetails.summary.availability": "Disponibilité", - "xpack.synthetics.monitorDetails.summary.avgDuration": "Durée moy.", "xpack.synthetics.monitorDetails.summary.brushArea": "Brosser une zone pour une plus haute fidélité", "xpack.synthetics.monitorDetails.summary.complete": "Terminé", "xpack.synthetics.monitorDetails.summary.duration": "Durée", @@ -37947,4 +37946,4 @@ "xpack.painlessLab.title": "Painless Lab", "xpack.painlessLab.walkthroughButtonLabel": "Présentation" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 723902ad29739c..48521b3e3b558e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -34785,7 +34785,6 @@ "xpack.synthetics.monitorDetails.statusBar.pingType.icmp": "ICMP", "xpack.synthetics.monitorDetails.statusBar.pingType.tcp": "TCP", "xpack.synthetics.monitorDetails.summary.availability": "可用性", - "xpack.synthetics.monitorDetails.summary.avgDuration": "平均期間", "xpack.synthetics.monitorDetails.summary.brushArea": "信頼度を高めるためにエリアを精査", "xpack.synthetics.monitorDetails.summary.complete": "完了", "xpack.synthetics.monitorDetails.summary.duration": "期間", @@ -37915,4 +37914,4 @@ "xpack.painlessLab.title": "Painless Lab", "xpack.painlessLab.walkthroughButtonLabel": "実地検証" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 469bb9a2cf748b..d3b2c66d7d642f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -34802,7 +34802,6 @@ "xpack.synthetics.monitorDetails.statusBar.pingType.icmp": "ICMP", "xpack.synthetics.monitorDetails.statusBar.pingType.tcp": "TCP", "xpack.synthetics.monitorDetails.summary.availability": "可用性", - "xpack.synthetics.monitorDetails.summary.avgDuration": "平均持续时间", "xpack.synthetics.monitorDetails.summary.brushArea": "轻刷某个区域以提高保真度", "xpack.synthetics.monitorDetails.summary.complete": "已完成", "xpack.synthetics.monitorDetails.summary.duration": "持续时间", @@ -37943,4 +37942,4 @@ "xpack.painlessLab.title": "Painless 实验室", "xpack.painlessLab.walkthroughButtonLabel": "指导" } -} \ No newline at end of file +}