From d051183adee6328ef86a8124b835ae69bfbae802 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 1 Mar 2023 16:46:25 +0100 Subject: [PATCH] [Synthetics] Error timeline date range (#151965) --- .../common/runtime_types/ping/error_state.ts | 16 +++- .../synthetics/test_run_details.journey.ts | 2 +- .../browser_steps_list.tsx | 51 ++++++++----- .../components/error_duration.tsx | 39 ++++++++-- .../components/error_timeline.tsx | 28 ++++++- .../error_details/components/resolved_at.tsx | 8 +- .../error_details/error_details_page.tsx | 20 +++-- .../hooks/use_error_details_breadcrumbs.ts | 7 +- .../hooks/use_error_failed_tests.tsx | 3 +- .../hooks/use_find_my_killer_state.ts | 76 +++++++++++++++++++ .../components/error_details/route_config.tsx | 2 +- .../hooks/use_monitor_errors.tsx | 9 ++- .../monitor_errors/errors_list.tsx | 74 +++++++++++------- .../monitor_errors/failed_tests.tsx | 33 +++++--- .../monitor_errors/monitor_errors.tsx | 6 +- .../test_time_formats.test.ts | 20 ++--- .../monitor_test_result/test_time_formats.ts | 30 +++++++- 17 files changed, 320 insertions(+), 104 deletions(-) create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_find_my_killer_state.ts diff --git a/x-pack/plugins/synthetics/common/runtime_types/ping/error_state.ts b/x-pack/plugins/synthetics/common/runtime_types/ping/error_state.ts index 86b0b07052eac..cf09da6eb92fc 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/ping/error_state.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/ping/error_state.ts @@ -6,14 +6,26 @@ */ import * as t from 'io-ts'; +export const StateEndsCodec = t.type({ + duration_ms: t.union([t.string, t.number]), + checks: t.number, + ends: t.union([t.string, t.null]), + started_at: t.string, + id: t.string, + up: t.number, + down: t.number, + status: t.string, +}); export const ErrorStateCodec = t.type({ - duration_ms: t.string, + duration_ms: t.union([t.string, t.number]), checks: t.number, - ends: t.union([t.string, t.null]), + ends: t.union([StateEndsCodec, t.null]), started_at: t.string, id: t.string, up: t.number, down: t.number, status: t.string, }); + +export type ErrorState = t.TypeOf; diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts index ea8f70742f578..e9f931e64bf04 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts @@ -65,6 +65,6 @@ journey(`TestRunDetailsPage`, async ({ page, params }) => { await page.waitForSelector('text=Test run details'); await page.waitForSelector('text=Go to https://www.google.com'); - await page.waitForSelector('text=After 2.1 s'); + await page.waitForSelector('text=After 2.12 s'); }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx index c90a00710f32f..a9341eae5fa69 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import React, { CSSProperties, ReactElement, useState } from 'react'; +import React, { CSSProperties, ReactElement, useCallback, useEffect, useState } from 'react'; import { EuiBasicTable, EuiBasicTableColumn, @@ -62,25 +62,38 @@ export const BrowserStepsList = ({ Record >({}); - const toggleDetails = (item: JourneyStep) => { - const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; - if (itemIdToExpandedRowMapValues[item._id]) { - delete itemIdToExpandedRowMapValues[item._id]; - } else { - if (testNowMode) { - itemIdToExpandedRowMapValues[item._id] = ( - - - - - - ); - } else { - itemIdToExpandedRowMapValues[item._id] = <>; - } + const toggleDetails = useCallback( + (item: JourneyStep) => { + setItemIdToExpandedRowMap((prevState) => { + const itemIdToExpandedRowMapValues = { ...prevState }; + if (itemIdToExpandedRowMapValues[item._id]) { + delete itemIdToExpandedRowMapValues[item._id]; + } else { + if (testNowMode) { + itemIdToExpandedRowMapValues[item._id] = ( + + + + + + ); + } else { + itemIdToExpandedRowMapValues[item._id] = <>; + } + } + return itemIdToExpandedRowMapValues; + }); + }, + [steps, testNowMode] + ); + + const failedStep = stepEnds?.find((step) => step.synthetics.step?.status === 'failed'); + + useEffect(() => { + if (failedStep && showExpand) { + toggleDetails(failedStep); } - setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); - }; + }, [failedStep, showExpand, toggleDetails]); const columns: Array> = [ ...(showExpand diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_duration.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_duration.tsx index 9b65149be3ad0..bac7b09ecbf84 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_duration.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_duration.tsx @@ -8,7 +8,8 @@ import React from 'react'; import { EuiDescriptionList } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import moment from 'moment'; +import moment, { Moment } from 'moment'; +import { useFindMyKillerState } from '../hooks/use_find_my_killer_state'; import { useErrorFailedTests } from '../hooks/use_last_error_state'; export const ErrorDuration: React.FC = () => { @@ -16,13 +17,41 @@ export const ErrorDuration: React.FC = () => { const state = failedTests?.[0]?.state; - const duration = state ? moment().diff(moment(state?.started_at), 'minutes') : 0; + const { killerState } = useFindMyKillerState(); - return ( - - ); + const endsAt = killerState?.timestamp ? moment(killerState?.timestamp) : moment(); + const startedAt = moment(state?.started_at); + + const duration = state ? getErrorDuration(startedAt, endsAt) : 0; + + return ; }; const ERROR_DURATION = i18n.translate('xpack.synthetics.errorDetails.errorDuration', { defaultMessage: 'Error duration', }); + +const getErrorDuration = (startedAt: Moment, endsAt: Moment) => { + // const endsAt = state.ends ? moment(state.ends) : moment(); + // const startedAt = moment(state?.started_at); + + const diffInDays = endsAt.diff(startedAt, 'days'); + if (diffInDays > 1) { + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.days', { + defaultMessage: '{value} days', + values: { value: diffInDays }, + }); + } + const diffInHours = endsAt.diff(startedAt, 'hours'); + if (diffInHours > 1) { + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.hours', { + defaultMessage: '{value} hours', + values: { value: diffInHours }, + }); + } + const diffInMinutes = endsAt.diff(startedAt, 'minutes'); + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.mins', { + defaultMessage: '{value} mins', + values: { value: diffInMinutes }, + }); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_timeline.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_timeline.tsx index 30461842e963f..d2e0b12793bc6 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_timeline.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_timeline.tsx @@ -5,8 +5,32 @@ * 2.0. */ import React from 'react'; +import { EuiLoadingContent } from '@elastic/eui'; +import moment from 'moment'; +import { Ping } from '../../../../../../common/runtime_types'; import { MonitorFailedTests } from '../../monitor_details/monitor_errors/failed_tests'; -export const ErrorTimeline = () => { - return ; +export const ErrorTimeline = ({ lastTestRun }: { lastTestRun?: Ping }) => { + if (!lastTestRun) { + return ; + } + const diff = moment(lastTestRun.monitor.timespan?.lt).diff( + moment(lastTestRun.monitor.timespan?.gte), + 'minutes' + ); + const startedAt = lastTestRun?.state?.started_at; + + return ( + + ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/resolved_at.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/resolved_at.tsx index 75b1f9a31690b..76e8ca2ebaea1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/resolved_at.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/resolved_at.tsx @@ -8,15 +8,13 @@ import React, { ReactElement } from 'react'; import { EuiDescriptionList } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useErrorFailedTests } from '../hooks/use_last_error_state'; import { useFormatTestRunAt } from '../../../utils/monitor_test_result/test_time_formats'; +import { useFindMyKillerState } from '../hooks/use_find_my_killer_state'; export const ResolvedAt: React.FC = () => { - const { failedTests } = useErrorFailedTests(); + const { killerState } = useFindMyKillerState(); - const state = failedTests?.[0]?.state; - - let endsAt: string | ReactElement = useFormatTestRunAt(state?.ends ?? ''); + let endsAt: string | ReactElement = useFormatTestRunAt(killerState?.timestamp); if (!endsAt) { endsAt = 'N/A'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/error_details_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/error_details_page.tsx index 084dd68934626..4a6ea2d3f34e1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/error_details_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/error_details_page.tsx @@ -43,7 +43,7 @@ export function ErrorDetailsPage() { return (
- + @@ -71,13 +71,19 @@ export function ErrorDetailsPage() { - - {data?.details?.journey && failedStep && ( - - )} - + {data?.details?.journey && failedStep && ( + <> + + + + + + )} - diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_details_breadcrumbs.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_details_breadcrumbs.ts index 61cef2b818615..c96df76b40456 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_details_breadcrumbs.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_details_breadcrumbs.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useSelectedLocation } from '../../monitor_details/hooks/use_selected_location'; import { useTestRunDetailsBreadcrumbs } from '../../test_run_details/hooks/use_test_run_details_breadcrumbs'; import { useSelectedMonitor } from '../../monitor_details/hooks/use_selected_monitor'; import { ConfigKey } from '../../../../../../common/runtime_types'; @@ -19,10 +20,14 @@ export const useErrorDetailsBreadcrumbs = ( const { monitor } = useSelectedMonitor(); + const selectedLocation = useSelectedLocation(); + const errorsBreadcrumbs = [ { text: ERRORS_CRUMB, - href: `${appPath}/monitor/${monitor?.[ConfigKey.CONFIG_ID]}/errors`, + href: `${appPath}/monitor/${monitor?.[ConfigKey.CONFIG_ID]}/errors?locationId=${ + selectedLocation?.id + }`, }, ...(extraCrumbs ?? []), ]; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_failed_tests.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_failed_tests.tsx index 241c410038276..8b061d7c587f7 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_failed_tests.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_failed_tests.tsx @@ -57,7 +57,8 @@ export function useErrorFailedTests() { return useMemo(() => { const failedTests = data?.hits.hits?.map((doc) => { - return doc._source as Ping; + const source = doc._source as any; + return { ...source, timestamp: source['@timestamp'] } as Ping; }) ?? []; return { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_find_my_killer_state.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_find_my_killer_state.ts new file mode 100644 index 0000000000000..33f923c94d2ba --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_find_my_killer_state.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useParams } from 'react-router-dom'; +import { useMemo } from 'react'; +import { useReduxEsSearch } from '../../../hooks/use_redux_es_search'; +import { Ping } from '../../../../../../common/runtime_types'; +import { + EXCLUDE_RUN_ONCE_FILTER, + SUMMARY_FILTER, +} from '../../../../../../common/constants/client_defaults'; +import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; +import { useSyntheticsRefreshContext } from '../../../contexts'; +import { useGetUrlParams } from '../../../hooks'; + +export function useFindMyKillerState() { + const { lastRefresh } = useSyntheticsRefreshContext(); + + const { errorStateId, monitorId } = useParams<{ errorStateId: string; monitorId: string }>(); + + const { dateRangeStart, dateRangeEnd } = useGetUrlParams(); + + const { data, loading } = useReduxEsSearch( + { + index: SYNTHETICS_INDEX_PATTERN, + + body: { + // TODO: remove this once we have a better way to handle this mapping + runtime_mappings: { + 'state.ends.id': { + type: 'keyword', + }, + }, + size: 1, + query: { + bool: { + filter: [ + SUMMARY_FILTER, + EXCLUDE_RUN_ONCE_FILTER, + { + term: { + 'state.ends.id': errorStateId, + }, + }, + { + term: { + config_id: monitorId, + }, + }, + ], + }, + }, + sort: [{ '@timestamp': 'desc' }], + }, + }, + [lastRefresh, monitorId, dateRangeStart, dateRangeEnd], + { name: 'getStateWhichEndTheState' } + ); + + return useMemo(() => { + const killerStates = + data?.hits.hits?.map((doc) => { + const source = doc._source as any; + return { ...source, timestamp: source['@timestamp'] } as Ping; + }) ?? []; + + return { + loading, + killerState: killerStates?.[0], + }; + }, [data, loading]); +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/route_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/route_config.tsx index c53de93fa5123..3e2d0f39000fe 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/route_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/route_config.tsx @@ -39,7 +39,7 @@ export const getErrorDetailsRouteConfig = ( ), rightSideItems: [ , - , + , , , ], diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx index 6f6052af64a3f..e45e64dab1c15 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx @@ -87,12 +87,15 @@ export function useMonitorErrors(monitorIdArg?: string) { }, }, }, - [lastRefresh, monitorId, monitorIdArg, dateRangeStart, dateRangeEnd], - { name: 'getMonitorErrors', isRequestReady: Boolean(selectedLocation?.label) } + [lastRefresh, monitorId, monitorIdArg, dateRangeStart, dateRangeEnd, selectedLocation?.label], + { + name: `getMonitorErrors/${dateRangeStart}/${dateRangeEnd}`, + isRequestReady: Boolean(selectedLocation?.label), + } ); return useMemo(() => { - const errorStates = (data?.aggregations?.errorStates.buckets ?? []).map((loc) => { + const errorStates = data?.aggregations?.errorStates.buckets?.map((loc) => { return loc.summary.hits.hits?.[0]._source as PingState; }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx index ecd1f6141c7f4..d8fb9cc827fcd 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx @@ -50,6 +50,10 @@ export const ErrorsList = ({ const selectedLocation = useSelectedLocation(); + const lastTestRun = errorStates?.sort((a, b) => { + return moment(b.state.started_at).valueOf() - moment(a.state.started_at).valueOf(); + })?.[0]; + const columns = [ { field: 'item.state.started_at', @@ -67,49 +71,56 @@ export const ErrorsList = ({ /> ); const isActive = isActiveState(item); - if (!isActive) { + if (!isActive || lastTestRun.state.id !== item.state.id) { return link; } return ( - {link} + + {link} + - Active + {ACTIVE_LABEL} ); }, }, + ...(isBrowserType + ? [ + { + field: 'monitor.check_group', + name: FAILED_STEP_LABEL, + truncateText: true, + sortable: (a: PingState) => { + const failedStep = failedSteps.find( + (step) => step.monitor.check_group === a.monitor.check_group + ); + if (!failedStep) { + return a.monitor.check_group; + } + return failedStep.synthetics?.step?.name; + }, + render: (value: string, item: PingState) => { + const failedStep = failedSteps.find((step) => step.monitor.check_group === value); + if (!failedStep) { + return <>--; + } + return ( + + {failedStep.synthetics?.step?.index}. {failedStep.synthetics?.step?.name} + + ); + }, + }, + ] + : []), { - field: 'monitor.check_group', - name: !isBrowserType ? ERROR_MESSAGE_LABEL : FAILED_STEP_LABEL, - truncateText: true, - sortable: (a: PingState) => { - const failedStep = failedSteps.find( - (step) => step.monitor.check_group === a.monitor.check_group - ); - if (!failedStep) { - return a.monitor.check_group; - } - return failedStep.synthetics?.step?.name; - }, - render: (value: string, item: PingState) => { - if (!isBrowserType) { - return {item.error.message ?? '--'}; - } - const failedStep = failedSteps.find((step) => step.monitor.check_group === value); - if (!failedStep) { - return <>--; - } - return ( - - {failedStep.synthetics?.step?.index}. {failedStep.synthetics?.step?.name} - - ); - }, + field: 'error.message', + name: ERROR_MESSAGE_LABEL, }, { field: 'state.duration_ms', @@ -157,6 +168,7 @@ export const ErrorsList = ({
{ +export const MonitorFailedTests = ({ + time, + allowBrushing = true, +}: { + time: { to: string; from: string }; + allowBrushing?: boolean; +}) => { const { observability } = useKibana().services; const { ExploratoryViewEmbeddable } = observability; @@ -41,7 +47,8 @@ export const MonitorFailedTests = ({ time }: { time: { to: string; from: string { time, reportDefinitions: { - ...(monitorId ? { 'monitor.id': [monitorId] } : { 'state.id': [errorStateId] }), + ...(monitorId ? { 'monitor.id': [monitorId] } : {}), + ...(errorStateId ? { 'state.id': [errorStateId] } : {}), }, dataType: 'synthetics', selectedMetricField: 'failed_tests', @@ -49,21 +56,25 @@ export const MonitorFailedTests = ({ time }: { time: { to: string; from: string }, ]} onBrushEnd={({ range }) => { - updateUrl({ - dateRangeStart: moment(range[0]).toISOString(), - dateRangeEnd: moment(range[1]).toISOString(), - }); + if (allowBrushing) { + updateUrl({ + dateRangeStart: moment(range[0]).toISOString(), + dateRangeEnd: moment(range[1]).toISOString(), + }); + } }} /> {FAILED_TESTS_LABEL} - - - {BRUSH_LABEL} - - + {allowBrushing && ( + + + {BRUSH_LABEL} + + + )} ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx index 4d2794e9da995..04930af018152 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx @@ -23,9 +23,9 @@ import { ErrorsTabContent } from './errors_tab_content'; export const MonitorErrors = () => { const { errorStates, loading, data } = useMonitorErrors(); - const initialLoading = loading && !data; + const initialLoading = !data; - const emptyState = !loading && errorStates.length === 0; + const emptyState = !loading && errorStates && errorStates?.length === 0; const redirect = useMonitorDetailsPage(); if (redirect) { @@ -39,7 +39,7 @@ export const MonitorErrors = () => { {initialLoading && } {emptyState && }
- +
); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.test.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.test.ts index 5337468f6e730..b1e808d748d76 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.test.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.test.ts @@ -9,16 +9,16 @@ import { formatTestDuration } from './test_time_formats'; describe('formatTestDuration', () => { it.each` - duration | expected | isMilli - ${undefined} | ${'0 ms'} | ${undefined} - ${120_000_000} | ${'2 min'} | ${undefined} - ${6_200_000} | ${'6.2 s'} | ${false} - ${500_000} | ${'500 ms'} | ${undefined} - ${100} | ${'0 ms'} | ${undefined} - ${undefined} | ${'0 ms'} | ${true} - ${600_000} | ${'10 min'} | ${true} - ${6_200} | ${'6.2 s'} | ${true} - ${500} | ${'500 ms'} | ${true} + duration | expected | isMilli + ${undefined} | ${'0 ms'} | ${undefined} + ${120_000_000} | ${'2 mins'} | ${undefined} + ${6_200_000} | ${'6.2 sec'} | ${false} + ${500_000} | ${'500 ms'} | ${undefined} + ${100} | ${'0 ms'} | ${undefined} + ${undefined} | ${'0 ms'} | ${true} + ${600_000} | ${'10 mins'} | ${true} + ${6_200} | ${'6.2 sec'} | ${true} + ${500} | ${'500 ms'} | ${true} `( 'returns $expected when `duration` is $duration and `isMilli` $isMilli', ({ diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts index 5d605ad4c2192..efae8b1652738 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts @@ -6,6 +6,7 @@ */ import moment from 'moment'; +import { i18n } from '@kbn/i18n'; import { useKibanaDateFormat } from '../../../../hooks/use_kibana_date_format'; /** @@ -16,19 +17,40 @@ import { useKibanaDateFormat } from '../../../../hooks/use_kibana_date_format'; export const formatTestDuration = (duration = 0, isMilli = false) => { const secs = isMilli ? duration / 1e3 : duration / 1e6; + const hours = Math.floor(secs / 3600); + + if (hours >= 1) { + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.hours', { + defaultMessage: '{value} hours', + values: { value: hours }, + }); + } + if (secs >= 60) { - return `${parseFloat((secs / 60).toFixed(1))} min`; + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.minutes', { + defaultMessage: '{value} mins', + values: { value: parseFloat((secs / 60).toFixed(1)) }, + }); } if (secs >= 1) { - return `${parseFloat(secs.toFixed(1))} s`; + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.seconds', { + defaultMessage: '{value} sec', + values: { value: parseFloat(secs.toFixed(1)) }, + }); } if (isMilli) { - return `${duration.toFixed(0)} ms`; + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.milliseconds', { + defaultMessage: '{value} ms', + values: { value: duration.toFixed(0) }, + }); } - return `${(duration / 1000).toFixed(0)} ms`; + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.microseconds', { + defaultMessage: '{value} ms', + values: { value: (duration / 1000).toFixed(0) }, + }); }; export function formatTestRunAt(timestamp: string, format: string) {