From c9469e0b22a50e81655515c6ce2031edd05f981e Mon Sep 17 00:00:00 2001 From: Shahzad Date: Fri, 13 Jan 2023 17:12:37 +0100 Subject: [PATCH] [Synthetics] Added step page detail header (#148706) Fixes https://github.com/elastic/kibana/issues/145385 --- .../plugins/synthetics/common/constants/ui.ts | 3 + .../common/links/step_details_link.tsx | 4 +- .../browser_steps_list.tsx | 1 + .../hooks/use_journey_steps.tsx | 6 +- .../monitor_details_location.tsx | 10 +- .../common/network_data/data_formatting.ts | 2 +- .../hooks/use_step_detail_page.ts | 64 +++---- .../hooks/use_step_details_breadcrumbs.ts | 32 ++-- .../step_details_page/route_config.tsx | 50 ++++++ .../step_details_page/step_detail_page.tsx | 38 +++-- .../step_details_page/step_details_status.tsx | 30 ++++ .../step_details_page/step_number_nav.tsx | 109 ++++++++++++ .../step_objects/color_palette.tsx | 6 + .../step_details_page/step_page_nav.tsx | 119 +++++++++++++ .../step_details_page/step_title.tsx | 21 ++- .../step_waterfall_chart/labels.ts | 15 -- .../step_detail_container.tsx | 56 ------ .../step_waterfall_chart/step_page_nav.tsx | 71 -------- .../step_waterfall_chart/step_page_title.tsx | 64 ------- .../use_monitor_breadcrumb.tsx | 64 ------- .../use_monitor_breadcrumbs.test.tsx | 160 ------------------ .../waterfall/waterfall_chart_container.tsx | 25 +-- .../components/test_run_date.tsx | 2 +- .../public/apps/synthetics/routes.tsx | 23 +-- .../state/network_events/actions.ts | 15 +- .../state/network_events/effects.ts | 13 +- .../synthetics/state/network_events/index.ts | 130 +++++--------- .../monitor_test_result/test_time_formats.ts | 5 +- 28 files changed, 494 insertions(+), 644 deletions(-) create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/route_config.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_details_status.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_number_nav.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_page_nav.tsx delete mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/labels.ts delete mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/step_detail_container.tsx delete mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/step_page_nav.tsx delete mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/step_page_title.tsx delete mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/use_monitor_breadcrumb.tsx delete mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/use_monitor_breadcrumbs.test.tsx diff --git a/x-pack/plugins/synthetics/common/constants/ui.ts b/x-pack/plugins/synthetics/common/constants/ui.ts index 08752f08d231b..bc6c70f60a7fe 100644 --- a/x-pack/plugins/synthetics/common/constants/ui.ts +++ b/x-pack/plugins/synthetics/common/constants/ui.ts @@ -29,6 +29,9 @@ export const SYNTHETICS_SETTINGS_ROUTE = '/settings/:tabId'; export const CERTIFICATES_ROUTE = '/certificates'; +export const SYNTHETICS_STEP_DETAIL_ROUTE = + '/monitor/:monitorId/test-run/:checkGroupId/step/:stepIndex'; + export const STEP_DETAIL_ROUTE = '/journey/:checkGroupId/step/:stepIndex'; export const SYNTHETIC_CHECK_STEPS_ROUTE = '/journey/:checkGroupId/steps'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/step_details_link.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/step_details_link.tsx index 078e9d924fbbe..f95984eafa08d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/step_details_link.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/step_details_link.tsx @@ -14,8 +14,10 @@ import { useSyntheticsSettingsContext } from '../../../contexts'; export const StepDetailsLinkIcon = ({ stepIndex, checkGroup, + configId, }: { checkGroup: string; + configId: string; stepIndex?: number; }) => { const { basePath } = useSyntheticsSettingsContext(); @@ -26,7 +28,7 @@ export const StepDetailsLinkIcon = ({ aria-label={VIEW_DETAILS} title={VIEW_DETAILS} size="s" - href={`${basePath}/app/synthetics/journey/${checkGroup}/step/${stepIndex}?locationId=${selectedLocation?.id}`} + href={`${basePath}/app/synthetics/monitor/${configId}/test-run/${checkGroup}/step/${stepIndex}?locationId=${selectedLocation?.id}`} target="_self" iconType="apmTrace" /> 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 2a24e733bc907..3ddf55d7300fc 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 @@ -122,6 +122,7 @@ export const BrowserStepsList = ({ ), }, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_journey_steps.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_journey_steps.tsx index 8e023f58ff482..a5f6d38b2a4a4 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_journey_steps.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_journey_steps.tsx @@ -17,8 +17,10 @@ import { } from '../../../state'; export const useJourneySteps = (checkGroup?: string, lastRefresh?: number) => { - const { stepIndex } = useParams<{ stepIndex: string }>(); - const { checkGroupId: urlCheckGroup } = useParams<{ checkGroupId: string }>(); + const { stepIndex, checkGroupId: urlCheckGroup } = useParams<{ + stepIndex: string; + checkGroupId: string; + }>(); const checkGroupId = checkGroup ?? urlCheckGroup; const journeyData = useSelector(selectBrowserJourney(checkGroupId)); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_location.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_location.tsx index d41b086010482..d599461c1002e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_location.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_location.tsx @@ -25,7 +25,7 @@ import { useStatusByLocation } from '../../hooks'; import { useSelectedLocation } from './hooks/use_selected_location'; import { useSelectedMonitor } from './hooks/use_selected_monitor'; -export const MonitorDetailsLocation: React.FC = () => { +export const MonitorDetailsLocation = ({ isDisabled }: { isDisabled?: boolean }) => { const { monitor } = useSelectedMonitor(); const { services } = useKibana(); const { locations } = useLocations(); @@ -45,8 +45,8 @@ export const MonitorDetailsLocation: React.FC = () => { if (monitor?.locations && monitor.locations.length > 1) { const button = ( - - {selectedLocation.label} + + {selectedLocation.label} {!isDisabled && } ); @@ -109,6 +109,7 @@ export const MonitorDetailsLocation: React.FC = () => { } }, [ closeLocationList, + isDisabled, isLocationListOpen, loadingLocationsStatus, locations, @@ -117,7 +118,8 @@ export const MonitorDetailsLocation: React.FC = () => { openLocationList, selectedLocation, services.application, - theme, + theme.eui.euiColorVis0, + theme.eui.euiColorVis9, ]); if (!selectedLocation || !monitor) { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts index c35152a563833..10b70d0c86eea 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts @@ -27,7 +27,7 @@ import { export const extractItems = (data: NetworkEvent[]): NetworkEvent[] => { // NOTE: This happens client side as the "payload" property is mapped // in such a way it can't be queried (or sorted on) via ES. - return data.sort((a: NetworkEvent, b: NetworkEvent) => { + return [...data].sort((a: NetworkEvent, b: NetworkEvent) => { return a.requestSentTime - b.requestSentTime; }); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_detail_page.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_detail_page.ts index 4c79502ce665f..4b76ca977777b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_detail_page.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_detail_page.ts @@ -9,60 +9,60 @@ import { useParams } from 'react-router-dom'; import { useMemo } from 'react'; import { useSyntheticsSettingsContext } from '../../../contexts'; import { useJourneySteps } from '../../monitor_details/hooks/use_journey_steps'; -import { JourneyStep, SyntheticsJourneyApiResponse } from '../../../../../../common/runtime_types'; -export const useStepDetailPage = (): { - activeStep?: JourneyStep; - checkGroupId: string; - handleNextStepHref: string; - handlePreviousStepHref: string; - handleNextRunHref: string; - handlePreviousRunHref: string; - hasNextStep: boolean; - hasPreviousStep: boolean; - journey?: SyntheticsJourneyApiResponse; - stepIndex: number; -} => { - const { checkGroupId, stepIndex: stepIndexString } = useParams<{ +export const useStepDetailPage = () => { + const { + checkGroupId, + monitorId, + stepIndex: stepIndexString, + } = useParams<{ checkGroupId: string; stepIndex: string; + monitorId: string; }>(); const stepIndex = Number(stepIndexString); - const { data: journey } = useJourneySteps(checkGroupId); + const { data: journey, stepEnds } = useJourneySteps(checkGroupId); const memoized = useMemo( () => ({ - hasPreviousStep: stepIndex > 1 ? true : false, activeStep: journey?.steps?.find((step) => step.synthetics?.step?.index === stepIndex), - hasNextStep: journey && journey.steps && stepIndex < journey.steps.length ? true : false, }), [journey, stepIndex] ); const { basePath } = useSyntheticsSettingsContext(); - const handleNextStepHref = `${basePath}/app/synthetics/journey/${checkGroupId}/step/${ - stepIndex + 1 - }`; - - const handlePreviousStepHref = `${basePath}/app/synthetics/journey/${checkGroupId}/step/${ - stepIndex - 1 - }`; - - const handleNextRunHref = `${basePath}/app/synthetics/journey/${journey?.details?.next?.checkGroup}/step/1`; - - const handlePreviousRunHref = `${basePath}/app/synthetics/journey/${journey?.details?.previous?.checkGroup}/step/1`; + const handleStepHref = (stepNo: number) => + `${basePath}/app/synthetics/monitor/${monitorId}/test-run/${checkGroupId}/step/${stepNo}`; return { checkGroupId, journey, stepIndex, + stepEnds, ...memoized, - handleNextStepHref, - handlePreviousStepHref, - handleNextRunHref, - handlePreviousRunHref, + handleStepHref, }; }; + +export const useStepDetailLink = ({ + checkGroupId, + stepIndex, +}: { + checkGroupId?: string; + stepIndex: string; +}) => { + const { basePath } = useSyntheticsSettingsContext(); + + const { monitorId } = useParams<{ + monitorId: string; + }>(); + + if (!checkGroupId) { + return ''; + } + + return `${basePath}/app/synthetics/monitor/${monitorId}/test-run/${checkGroupId}/step/${stepIndex}`; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_details_breadcrumbs.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_details_breadcrumbs.ts index b42417083cc3a..934cc24e48d62 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_details_breadcrumbs.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_details_breadcrumbs.ts @@ -4,25 +4,35 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { i18n } from '@kbn/i18n'; +import moment from 'moment'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs'; -import { MONITORS_ROUTE } from '../../../../../../common/constants'; +import { useParams, generatePath } from 'react-router-dom'; +import { useSelectedLocation } from '../../monitor_details/hooks/use_selected_location'; +import { TEST_RUN_DETAILS_ROUTE } from '../../../../../../common/constants/ui'; +import { useJourneySteps } from '../../monitor_details/hooks/use_journey_steps'; +import { useTestRunDetailsBreadcrumbs } from '../../test_run_details/hooks/use_test_run_details_breadcrumbs'; import { PLUGIN } from '../../../../../../common/constants/plugin'; export const useStepDetailsBreadcrumbs = (extraCrumbs?: Array<{ text: string; href?: string }>) => { + const { data, currentStep } = useJourneySteps(); const kibana = useKibana(); const appPath = kibana.services.application?.getUrlForApp(PLUGIN.SYNTHETICS_PLUGIN_ID) ?? ''; - useBreadcrumbs([ + const params = useParams<{ + checkGroupId: string; + monitorId: string; + }>(); + + const selectedLocation = useSelectedLocation(); + + useTestRunDetailsBreadcrumbs([ { - text: MONITOR_MANAGEMENT_CRUMB, - href: `${appPath}/${MONITORS_ROUTE}`, + text: data ? moment(data.details?.timestamp).format('LLL') : '', + href: `${appPath}/${generatePath(TEST_RUN_DETAILS_ROUTE, params)}?locationId=${ + selectedLocation?.id ?? '' + }`, }, - ...(extraCrumbs ?? []), + + { text: `${currentStep?.synthetics.step?.index}. ${currentStep?.synthetics.step?.name}` ?? '' }, ]); }; - -const MONITOR_MANAGEMENT_CRUMB = i18n.translate('xpack.synthetics.monitorsPage.monitorsMCrumb', { - defaultMessage: 'Monitors', -}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/route_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/route_config.tsx new file mode 100644 index 0000000000000..9d26c14528db9 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/route_config.tsx @@ -0,0 +1,50 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import { OutPortal } from 'react-reverse-portal'; +import { StepRunDate } from './step_page_nav'; +import { StepDetailPageStepNav } from './step_number_nav'; +import { StepDetailsStatus } from './step_details_status'; +import { MonitorDetailsLocation } from '../monitor_details/monitor_details_location'; +import { StepDetailPage } from './step_detail_page'; +import { RouteProps } from '../../routes'; +import { SYNTHETICS_STEP_DETAIL_ROUTE } from '../../../../../common/constants'; +import { MonitorDetailsLinkPortalNode } from '../monitor_add_edit/portals'; +import { StepTitle } from './step_title'; + +export const getStepDetailsRoute = ( + history: ReturnType, + syntheticsPath: string, + baseTitle: string +): RouteProps => { + return { + title: i18n.translate('xpack.synthetics.stepDetailsRoute.title', { + defaultMessage: 'Step details | {baseTitle}', + values: { baseTitle }, + }), + path: SYNTHETICS_STEP_DETAIL_ROUTE, + component: StepDetailPage, + dataTestSubj: 'syntheticsMonitorEditPage', + pageHeader: { + pageTitle: , + rightSideItems: [ + , + , + , + , + ], + breadcrumbs: [ + { + text: , + }, + ], + }, + }; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx index e1dd157a1b8a4..dfb72e3315cbd 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx @@ -5,22 +5,23 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { useParams } from 'react-router-dom'; import { useTrackPageview } from '@kbn/observability-plugin/public'; -import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; -import { BreakdownLegend } from './step_timing_breakdown/breakdown_legend'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui'; +import { useDispatch } from 'react-redux'; +import { useStepDetailsBreadcrumbs } from './hooks/use_step_details_breadcrumbs'; import { WaterfallChartContainer } from './step_waterfall_chart/waterfall/waterfall_chart_container'; -import { ObjectWeightList } from './step_objects/object_weight_list'; import { NetworkTimingsDonut } from './step_timing_breakdown/network_timings_donut'; +import { useJourneySteps } from '../monitor_details/hooks/use_journey_steps'; +import { getNetworkEvents } from '../../state/network_events/actions'; +import { ObjectWeightList } from './step_objects/object_weight_list'; import { StepMetrics } from './step_metrics/step_metrics'; -import { NetworkTimingsBreakdown } from './network_timings_breakdown'; import { ObjectCountList } from './step_objects/object_count_list'; -import { StepImage } from './step_screenshot/step_image'; -import { useJourneySteps } from '../monitor_details/hooks/use_journey_steps'; import { MonitorDetailsLinkPortal } from '../monitor_add_edit/monitor_details_portal'; - -import { useStepDetailsBreadcrumbs } from './hooks/use_step_details_breadcrumbs'; +import { StepImage } from './step_screenshot/step_image'; +import { BreakdownLegend } from './step_timing_breakdown/breakdown_legend'; +import { NetworkTimingsBreakdown } from './network_timings_breakdown'; export const StepDetailPage = () => { const { checkGroupId, stepIndex } = useParams<{ checkGroupId: string; stepIndex: string }>(); @@ -28,21 +29,24 @@ export const StepDetailPage = () => { useTrackPageview({ app: 'synthetics', path: 'stepDetail' }); useTrackPageview({ app: 'synthetics', path: 'stepDetail', delay: 15000 }); - const { data, loading, isFailed, currentStep } = useJourneySteps(checkGroupId); + const { data, isFailed, currentStep } = useJourneySteps(); + + useStepDetailsBreadcrumbs(); const activeStep = data?.steps?.find( (step) => step.synthetics?.step?.index === Number(stepIndex) ); - useStepDetailsBreadcrumbs([{ text: data?.details?.journey.monitor.name ?? '' }]); + const dispatch = useDispatch(); - if (loading) { - return ( -
- -
+ useEffect(() => { + dispatch( + getNetworkEvents.get({ + checkGroup: checkGroupId, + stepIndex: Number(stepIndex), + }) ); - } + }, [dispatch, stepIndex, checkGroupId]); return ( <> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_details_status.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_details_status.tsx new file mode 100644 index 0000000000000..ed74c03166ef5 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_details_status.tsx @@ -0,0 +1,30 @@ +/* + * 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 React from 'react'; + +import { EuiDescriptionList, EuiLoadingContent } from '@elastic/eui'; +import { parseBadgeStatus, StatusBadge } from '../common/monitor_test_result/status_badge'; +import { useJourneySteps } from '../monitor_details/hooks/use_journey_steps'; +import { STATUS_LABEL } from '../common/components/monitor_status'; + +export const StepDetailsStatus = () => { + const { currentStep } = useJourneySteps(); + + let content = ; + + if (currentStep?.synthetics.step?.status) { + content = ; + } + + return ( + + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_number_nav.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_number_nav.tsx new file mode 100644 index 0000000000000..408468258b009 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_number_nav.tsx @@ -0,0 +1,109 @@ +/* + * 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 React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { times } from 'lodash'; +import { + EuiButtonEmpty, + EuiDescriptionList, + EuiContextMenuPanel, + EuiContextMenuItem, + EuiPopover, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useStepDetailPage } from './hooks/use_step_detail_page'; + +interface Props { + stepIndex: number; + totalSteps: number; + handleStepHref: (no: number) => string; +} + +export const StepNav = ({ stepIndex, totalSteps, handleStepHref }: Props) => { + const [isPopoverOpen, setPopover] = useState(false); + + const onButtonClick = () => { + setPopover(!isPopoverOpen); + }; + + const closePopover = () => { + setPopover(false); + }; + + const button = ( + + + + ); + + const items = times(totalSteps).map((num) => ( + + + + )); + + return ( + + + + ); +}; + +export const StepDetailPageStepNav = () => { + const { activeStep, journey, stepIndex, handleStepHref, stepEnds } = useStepDetailPage(); + + if (!journey || !activeStep) return null; + + return ( + + ), + }, + ]} + /> + ); +}; + +export const STEP_LABEL = i18n.translate('xpack.synthetics.synthetics.stepDetail.stepLabel', { + defaultMessage: 'Step', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/color_palette.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/color_palette.tsx index 23a53c2518ba6..7d43f7d1dd26f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/color_palette.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/color_palette.tsx @@ -74,6 +74,12 @@ export const ColorPaletteFlexItem = ({ }, 10); }, [percent, value]); + useEffect(() => { + if (!loading) { + setVal(0); + } + }, [loading]); + if (loading) { return ; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_page_nav.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_page_nav.tsx new file mode 100644 index 0000000000000..87d1208a9efb8 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_page_nav.tsx @@ -0,0 +1,119 @@ +/* + * 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 React, { ReactElement, useState } from 'react'; +import { + EuiButtonEmpty, + EuiDescriptionList, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingContent, + EuiText, + EuiPopover, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useParams } from 'react-router-dom'; +import { useStepDetailLink } from './hooks/use_step_detail_page'; +import { useFormatTestRunAt } from '../../utils/monitor_test_result/test_time_formats'; +import { useJourneySteps } from '../monitor_details/hooks/use_journey_steps'; + +export const StepRunDate = () => { + return ( + }]} + /> + ); +}; + +const ERROR_DURATION = i18n.translate('xpack.synthetics.testDetails.date', { + defaultMessage: 'Date', +}); + +export const StepPageNavigation = () => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const { data } = useJourneySteps(); + + let startedAt: string | ReactElement = useFormatTestRunAt(data?.details?.timestamp); + + const { stepIndex } = useParams<{ stepIndex: string; monitorId: string }>(); + + const prevHref = useStepDetailLink({ + stepIndex, + checkGroupId: data?.details?.previous?.checkGroup, + }); + + const nextHref = useStepDetailLink({ + stepIndex, + checkGroupId: data?.details?.next?.checkGroup, + }); + + if (!startedAt) { + startedAt = ; + } + + return ( + setIsPopoverOpen(false)} + isOpen={isPopoverOpen} + button={ + setIsPopoverOpen(true)} + iconType="arrowDown" + iconSide="right" + flush="left" + > + {startedAt} + + } + > + + + + {PREVIOUS_CHECK_BUTTON_TEXT} + + + + + {startedAt} + + + + + {NEXT_CHECK_BUTTON_TEXT} + + + + + ); +}; + +export const PREVIOUS_CHECK_BUTTON_TEXT = i18n.translate( + 'xpack.synthetics.synthetics.stepDetail.previousCheckButtonText', + { + defaultMessage: 'Previous check', + } +); + +export const NEXT_CHECK_BUTTON_TEXT = i18n.translate( + 'xpack.synthetics.synthetics.stepDetail.nextCheckButtonText', + { + defaultMessage: 'Next check', + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_title.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_title.tsx index 658ad28890a91..ffae133a971ac 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_title.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_title.tsx @@ -7,7 +7,8 @@ import React from 'react'; import { useParams } from 'react-router-dom'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiLoadingContent } from '@elastic/eui'; +import styled from 'styled-components'; import { useJourneySteps } from '../monitor_details/hooks/use_journey_steps'; export const StepTitle = () => { @@ -17,9 +18,17 @@ export const StepTitle = () => { const currentStep = data?.steps.find((step) => step.synthetics.step?.index === Number(stepIndex)); - return ( - - {currentStep?.synthetics?.step?.name} - - ); + if (!currentStep) { + return ; + } + + return <>{`${currentStep?.synthetics?.step?.index}. ${currentStep?.synthetics?.step?.name}`}; }; + +const StyledContent = styled(EuiLoadingContent)` + &&& { + span { + height: 45px; + } + } +`; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/labels.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/labels.ts deleted file mode 100644 index cf7ab30c8867d..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/labels.ts +++ /dev/null @@ -1,15 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const VIEW_PERFORMANCE = i18n.translate( - 'xpack.synthetics.pingList.synthetics.performanceBreakDown', - { - defaultMessage: 'View performance breakdown', - } -); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/step_detail_container.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/step_detail_container.tsx deleted file mode 100644 index c1a60433b0922..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/step_detail_container.tsx +++ /dev/null @@ -1,56 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingSpinner } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { useStepDetailPage } from '../hooks/use_step_detail_page'; -import { useMonitorBreadcrumb } from './use_monitor_breadcrumb'; -import { WaterfallChartContainer } from './waterfall/waterfall_chart_container'; - -export const NO_STEP_DATA = i18n.translate('xpack.synthetics.synthetics.stepDetail.noData', { - defaultMessage: 'No data could be found for this step', -}); - -interface Props { - checkGroup: string; - stepIndex: number; -} - -export const StepDetailContainer: React.FC = ({ checkGroup, stepIndex }) => { - const { activeStep, journey } = useStepDetailPage(); - - useMonitorBreadcrumb({ details: journey?.details, activeStep, performanceBreakDownView: true }); - - return ( - <> - {!journey && ( - - - - - - )} - {journey && !activeStep && ( - - - -

{NO_STEP_DATA}

-
-
-
- )} - {journey && activeStep && ( - - )} - - ); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/step_page_nav.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/step_page_nav.tsx deleted file mode 100644 index bddad0132def2..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/step_page_nav.tsx +++ /dev/null @@ -1,71 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import moment from 'moment'; -import { i18n } from '@kbn/i18n'; - -export const PREVIOUS_CHECK_BUTTON_TEXT = i18n.translate( - 'xpack.synthetics.synthetics.stepDetail.previousCheckButtonText', - { - defaultMessage: 'Previous check', - } -); - -export const NEXT_CHECK_BUTTON_TEXT = i18n.translate( - 'xpack.synthetics.synthetics.stepDetail.nextCheckButtonText', - { - defaultMessage: 'Next check', - } -); - -interface Props { - previousCheckGroup?: string; - dateFormat: string; - checkTimestamp?: string; - nextCheckGroup?: string; - handlePreviousRun: () => void; - handleNextRun: () => void; -} -export const StepPageNavigation = ({ - previousCheckGroup, - dateFormat, - handleNextRun, - handlePreviousRun, - checkTimestamp, - nextCheckGroup, -}: Props) => { - return ( - - - - {PREVIOUS_CHECK_BUTTON_TEXT} - - - - {moment(checkTimestamp).format(dateFormat)} - - - - {NEXT_CHECK_BUTTON_TEXT} - - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/step_page_title.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/step_page_title.tsx deleted file mode 100644 index 2e90fc3299ba1..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/step_page_title.tsx +++ /dev/null @@ -1,64 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; - -interface Props { - stepName: string; - stepIndex: number; - totalSteps: number; - hasPreviousStep: boolean; - hasNextStep: boolean; - handlePreviousStep: () => void; - handleNextStep: () => void; -} - -export const StepPageTitleContent = ({ - stepIndex, - totalSteps, - handleNextStep, - handlePreviousStep, - hasNextStep, - hasPreviousStep, -}: Props) => { - return ( - - - - - - - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/use_monitor_breadcrumb.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/use_monitor_breadcrumb.tsx deleted file mode 100644 index 499647d16b0a0..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/use_monitor_breadcrumb.tsx +++ /dev/null @@ -1,64 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import moment from 'moment'; -import { i18n } from '@kbn/i18n'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { getShortTimeStamp } from '../../../utils/monitor_test_result/timestamp'; -import { PLUGIN } from '../../../../../../common/constants/plugin'; -import { useBreadcrumbs } from '../../../hooks'; -import { SyntheticsJourneyApiResponse } from '../../../../../../common/runtime_types'; - -interface ActiveStep { - monitor: { - id: string; - name?: string; - }; -} - -interface Props { - details: SyntheticsJourneyApiResponse['details']; - activeStep?: ActiveStep; - performanceBreakDownView?: boolean; -} - -export const useMonitorBreadcrumb = ({ - details, - activeStep, - performanceBreakDownView = false, -}: Props) => { - const kibana = useKibana(); - const appPath = kibana.services.application?.getUrlForApp(PLUGIN.ID) ?? ''; - - useBreadcrumbs([ - ...(activeStep?.monitor - ? [ - { - text: activeStep?.monitor?.name || activeStep?.monitor.id, - href: `${appPath}/monitor/${btoa(activeStep?.monitor.id)}`, - }, - ] - : []), - ...(details?.journey?.monitor?.check_group - ? [ - { - text: getShortTimeStamp(moment(details?.timestamp)), - href: `${appPath}/journey/${details.journey.monitor.check_group}/steps`, - }, - ] - : []), - ...(performanceBreakDownView - ? [ - { - text: i18n.translate('xpack.synthetics.synthetics.performanceBreakDown.label', { - defaultMessage: 'Performance breakdown', - }), - }, - ] - : []), - ]); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/use_monitor_breadcrumbs.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/use_monitor_breadcrumbs.test.tsx deleted file mode 100644 index 4473ba72f510f..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/use_monitor_breadcrumbs.test.tsx +++ /dev/null @@ -1,160 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ChromeBreadcrumb } from '@kbn/core/public'; -import React from 'react'; -import { Route } from 'react-router-dom'; -import { of } from 'rxjs'; -import { chromeServiceMock, uiSettingsServiceMock } from '@kbn/core/public/mocks'; -import { useMonitorBreadcrumb } from './use_monitor_breadcrumb'; -import { Ping, SyntheticsJourneyApiResponse } from '../../../../../../common/runtime_types'; -import { render } from '../../../utils/testing'; -import { OVERVIEW_ROUTE } from '../../../../../../common/constants'; - -describe('useMonitorBreadcrumbs', () => { - it('sets the given breadcrumbs for steps list view', () => { - let breadcrumbObj: ChromeBreadcrumb[] = []; - const getBreadcrumbs = () => { - return breadcrumbObj; - }; - - const core = { - chrome: { - ...chromeServiceMock.createStartContract(), - setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => { - breadcrumbObj = newBreadcrumbs; - }, - }, - uiSettings: { - ...uiSettingsServiceMock.createSetupContract(), - get(key: string, defaultOverride?: any): any { - return `MMM D, YYYY @ HH:mm:ss.SSS` || defaultOverride; - }, - get$(key: string, defaultOverride?: any): any { - return of(`MMM D, YYYY @ HH:mm:ss.SSS`) || of(defaultOverride); - }, - }, - }; - - const Component = () => { - useMonitorBreadcrumb({ - activeStep: { monitor: { id: 'test-monitor', check_group: 'fake-test-group' } } as Ping, - details: { - timestamp: '2021-01-04T11:25:19.104Z', - journey: { - monitor: { id: 'test-monitor', check_group: 'fake-test-group' }, - }, - } as SyntheticsJourneyApiResponse['details'], - }); - return <>Step Water Fall; - }; - - render( - - - , - { core } - ); - - expect(getBreadcrumbs()).toMatchInlineSnapshot(` - Array [ - Object { - "href": "", - "text": "Observability", - }, - Object { - "href": "/app/synthetics", - "onClick": [Function], - "text": "Synthetics", - }, - Object { - "href": "/app/uptime/monitor/dGVzdC1tb25pdG9y", - "onClick": [Function], - "text": "test-monitor", - }, - Object { - "href": "/app/uptime/journey/fake-test-group/steps", - "onClick": [Function], - "text": "Jan 4, 2021 6:25:19 AM", - }, - ] - `); - }); - - it('sets the given breadcrumbs for performance breakdown page', () => { - let breadcrumbObj: ChromeBreadcrumb[] = []; - const getBreadcrumbs = () => { - return breadcrumbObj; - }; - - const core = { - chrome: { - ...chromeServiceMock.createStartContract(), - setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => { - breadcrumbObj = newBreadcrumbs; - }, - }, - uiSettings: { - ...uiSettingsServiceMock.createSetupContract(), - get(key: string, defaultOverride?: any): any { - return `MMM D, YYYY @ HH:mm:ss.SSS` || defaultOverride; - }, - get$(key: string, defaultOverride?: any): any { - return of(`MMM D, YYYY @ HH:mm:ss.SSS`) || of(defaultOverride); - }, - }, - }; - - const Component = () => { - useMonitorBreadcrumb({ - activeStep: { monitor: { id: 'test-monitor', check_group: 'fake-test-group' } } as Ping, - details: { - timestamp: '2021-01-04T11:25:19.104Z', - journey: { - monitor: { id: 'test-monitor', check_group: 'fake-test-group' }, - }, - } as SyntheticsJourneyApiResponse['details'], - performanceBreakDownView: true, - }); - return <>Step Water Fall; - }; - - render( - - - , - { core } - ); - - expect(getBreadcrumbs()).toMatchInlineSnapshot(` - Array [ - Object { - "href": "", - "text": "Observability", - }, - Object { - "href": "/app/synthetics", - "onClick": [Function], - "text": "Synthetics", - }, - Object { - "href": "/app/uptime/monitor/dGVzdC1tb25pdG9y", - "onClick": [Function], - "text": "test-monitor", - }, - Object { - "href": "/app/uptime/journey/fake-test-group/steps", - "onClick": [Function], - "text": "Jan 4, 2021 6:25:19 AM", - }, - Object { - "text": "Performance breakdown", - }, - ] - `); - }); -}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_chart_container.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_chart_container.tsx index f63c4beddd855..908f1ca1ca07c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_chart_container.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_chart_container.tsx @@ -8,10 +8,9 @@ import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingChart, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useEffect } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; +import React, { useMemo } from 'react'; +import { useSelector } from 'react-redux'; import { networkEventsSelector } from '../../../../state/network_events/selectors'; -import { getNetworkEvents } from '../../../../state/network_events/actions'; import { JourneyStep } from '../../../../../../../common/runtime_types'; import { WaterfallChartWrapper } from './waterfall_chart_wrapper'; import { extractItems } from '../../common/network_data/data_formatting'; @@ -31,24 +30,12 @@ interface Props { } export const WaterfallChartContainer: React.FC = ({ checkGroup, stepIndex, activeStep }) => { - const dispatch = useDispatch(); - - useEffect(() => { - if (checkGroup && stepIndex) { - dispatch( - getNetworkEvents({ - checkGroup, - stepIndex, - }) - ); - } - }, [dispatch, stepIndex, checkGroup]); - const _networkEvents = useSelector(networkEventsSelector); const networkEvents = _networkEvents[checkGroup ?? '']?.[stepIndex]; + const hasEvents = networkEvents?.events?.length > 0; + const waterfallLoaded = networkEvents && !networkEvents.loading; const isWaterfallSupported = networkEvents?.isWaterfallSupported; - const hasEvents = networkEvents?.events?.length > 0; const { metrics } = useStepWaterfallMetrics({ checkGroup, @@ -56,6 +43,8 @@ export const WaterfallChartContainer: React.FC = ({ checkGroup, stepIndex hasNavigationRequest: networkEvents?.hasNavigationRequest, }); + const data = useMemo(() => extractItems(networkEvents?.events ?? []), [networkEvents?.events]); + return ( <> {!waterfallLoaded && ( @@ -84,7 +73,7 @@ export const WaterfallChartContainer: React.FC = ({ checkGroup, stepIndex )} {waterfallLoaded && hasEvents && isWaterfallSupported && ( { +export const TestRunDate = () => { const { data } = useJourneySteps(); let startedAt: string | ReactElement = useFormatTestRunAt(data?.details?.timestamp); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx index ef032262cbe72..c417a51dd8134 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx @@ -16,10 +16,10 @@ import { APP_WRAPPER_CLASS } from '@kbn/core/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useInspectorContext } from '@kbn/observability-plugin/public'; import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-plugin/public'; +import { getStepDetailsRoute } from './components/step_details_page/route_config'; import { getTestRunDetailsRoute } from './components/test_run_details/route_config'; import { getSettingsRouteConfig } from './components/settings/route_config'; import { TestRunDetails } from './components/test_run_details/test_run_details'; -import { StepTitle } from './components/step_details_page/step_title'; import { MonitorAddPageWithServiceAllowed } from './components/monitor_add_edit/monitor_add_page'; import { MonitorEditPageWithServiceAllowed } from './components/monitor_add_edit/monitor_edit_page'; import { MonitorDetailsPageTitle } from './components/monitor_details/monitor_details_page_title'; @@ -42,7 +42,6 @@ import { MONITOR_ERRORS_ROUTE, MONITOR_HISTORY_ROUTE, MONITOR_ROUTE, - STEP_DETAIL_ROUTE, OVERVIEW_ROUTE, TEST_RUN_DETAILS_ROUTE, } from '../../../common/constants'; @@ -56,7 +55,6 @@ import { MonitorDetailsLastRun } from './components/monitor_details/monitor_deta import { MonitorSummary } from './components/monitor_details/monitor_summary/monitor_summary'; import { MonitorHistory } from './components/monitor_details/monitor_history/monitor_history'; import { MonitorErrors } from './components/monitor_details/monitor_errors/monitor_errors'; -import { StepDetailPage } from './components/step_details_page/step_detail_page'; import { getErrorDetailsRouteConfig } from './components/error_details/route_config'; export type RouteProps = LazyObservabilityPageTemplateProps & { @@ -86,6 +84,7 @@ const getRoutes = ( ...getSettingsRouteConfig(history, syntheticsPath, baseTitle), getErrorDetailsRouteConfig(history, syntheticsPath, baseTitle), getTestRunDetailsRoute(history, syntheticsPath, baseTitle), + getStepDetailsRoute(history, syntheticsPath, baseTitle), { title: i18n.translate('xpack.synthetics.gettingStartedRoute.title', { defaultMessage: 'Synthetics Getting Started | {baseTitle}', @@ -269,24 +268,6 @@ const getRoutes = ( ], }, }, - { - title: i18n.translate('xpack.synthetics.stepDetailsRoute.title', { - defaultMessage: 'Step details | {baseTitle}', - values: { baseTitle }, - }), - path: STEP_DETAIL_ROUTE, - component: StepDetailPage, - dataTestSubj: 'syntheticsMonitorEditPage', - pageHeader: { - pageTitle: , - rightSideItems: [], - breadcrumbs: [ - { - text: , - }, - ], - }, - }, { title: i18n.translate('xpack.synthetics.testRunDetailsRoute.title', { defaultMessage: 'Test run details | {baseTitle}', diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/actions.ts index f391484d3920a..34853b3403fa5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/actions.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { createAction } from 'redux-actions'; +import { createAsyncAction } from '../utils/actions'; import { SyntheticsNetworkEventsApiResponse } from '../../../../../common/runtime_types'; export interface FetchNetworkEventsParams { @@ -18,10 +18,11 @@ export interface FetchNetworkEventsFailPayload { stepIndex: number; error: Error; } +type NetworkSuccessPayload = Pick & + SyntheticsNetworkEventsApiResponse; -export const getNetworkEvents = createAction('GET_NETWORK_EVENTS'); -export const getNetworkEventsSuccess = createAction< - Pick & SyntheticsNetworkEventsApiResponse ->('GET_NETWORK_EVENTS_SUCCESS'); -export const getNetworkEventsFail = - createAction('GET_NETWORK_EVENTS_FAIL'); +export const getNetworkEvents = createAsyncAction< + FetchNetworkEventsParams, + NetworkSuccessPayload, + FetchNetworkEventsFailPayload +>('GET_NETWORK_EVENTS'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/effects.ts index 1736619eb6185..efa8784570ea7 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/effects.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/effects.ts @@ -9,16 +9,11 @@ import type { Action } from 'redux-actions'; import { call, put, takeLatest } from 'redux-saga/effects'; import { fetchNetworkEvents } from './api'; import { SyntheticsNetworkEventsApiResponse } from '../../../../../common/runtime_types'; -import { - FetchNetworkEventsParams, - getNetworkEvents, - getNetworkEventsFail, - getNetworkEventsSuccess, -} from './actions'; +import { FetchNetworkEventsParams, getNetworkEvents } from './actions'; export function* fetchNetworkEventsEffect() { yield takeLatest( - getNetworkEvents, + getNetworkEvents.get, function* (action: Action): Generator { try { const response = (yield call( @@ -27,7 +22,7 @@ export function* fetchNetworkEventsEffect() { )) as SyntheticsNetworkEventsApiResponse; yield put( - getNetworkEventsSuccess({ + getNetworkEvents.success({ checkGroup: action.payload.checkGroup, stepIndex: action.payload.stepIndex, ...response, @@ -35,7 +30,7 @@ export function* fetchNetworkEventsEffect() { ); } catch (e) { yield put( - getNetworkEventsFail({ + getNetworkEvents.fail({ checkGroup: action.payload.checkGroup, stepIndex: action.payload.stepIndex, error: e, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/index.ts index a3a5cc00c9bcf..3a9972b17d61a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/index.ts @@ -5,18 +5,9 @@ * 2.0. */ -import { handleActions, Action } from 'redux-actions'; -import { - NetworkEvent, - SyntheticsNetworkEventsApiResponse, -} from '../../../../../common/runtime_types'; -import { - FetchNetworkEventsFailPayload, - FetchNetworkEventsParams, - getNetworkEvents, - getNetworkEventsFail, - getNetworkEventsSuccess, -} from './actions'; +import { createReducer } from '@reduxjs/toolkit'; +import { NetworkEvent } from '../../../../../common/runtime_types'; +import { getNetworkEvents } from './actions'; export interface NetworkEventsState { [checkGroup: string]: { @@ -33,19 +24,11 @@ export interface NetworkEventsState { const initialState: NetworkEventsState = {}; -type Payload = FetchNetworkEventsParams & - SyntheticsNetworkEventsApiResponse & - FetchNetworkEventsFailPayload & - string[]; - -export const networkEventsReducer = handleActions( - { - [String(getNetworkEvents)]: ( - state: NetworkEventsState, - { payload: { checkGroup, stepIndex } }: Action - ) => ({ - ...state, - [checkGroup]: state[checkGroup] +export const networkEventsReducer = createReducer(initialState, (builder) => { + builder + .addCase(getNetworkEvents.get, (state, action) => { + const { checkGroup, stepIndex } = action.payload; + state[checkGroup] = state[checkGroup] ? { [stepIndex]: state[checkGroup][stepIndex] ? { @@ -69,61 +52,44 @@ export const networkEventsReducer = handleActions( total: 0, isWaterfallSupported: true, }, - }, - }), + }; + }) + .addCase(getNetworkEvents.success, (state, action) => { + const { events, total, checkGroup, stepIndex, isWaterfallSupported, hasNavigationRequest } = + action.payload; - [String(getNetworkEventsSuccess)]: ( - state: NetworkEventsState, - { - payload: { - events, - total, - checkGroup, - stepIndex, - isWaterfallSupported, - hasNavigationRequest, - }, - }: Action - ) => { - return { - ...state, - [checkGroup]: state[checkGroup] - ? { - [stepIndex]: state[checkGroup][stepIndex] - ? { - ...state[checkGroup][stepIndex], - loading: false, - events, - total, - isWaterfallSupported, - hasNavigationRequest, - } - : { - loading: false, - events, - total, - isWaterfallSupported, - hasNavigationRequest, - }, - } - : { - [stepIndex]: { - loading: false, - events, - total, - isWaterfallSupported, - hasNavigationRequest, - }, + state[checkGroup] = state[checkGroup] + ? { + [stepIndex]: state[checkGroup][stepIndex] + ? { + ...state[checkGroup][stepIndex], + loading: false, + events, + total, + isWaterfallSupported, + hasNavigationRequest, + } + : { + loading: false, + events, + total, + isWaterfallSupported, + hasNavigationRequest, + }, + } + : { + [stepIndex]: { + loading: false, + events, + total, + isWaterfallSupported, + hasNavigationRequest, }, - }; - }, - - [String(getNetworkEventsFail)]: ( - state: NetworkEventsState, - { payload: { checkGroup, stepIndex, error } }: Action - ) => ({ - ...state, - [checkGroup]: state[checkGroup] + }; + }) + .addCase(getNetworkEvents.fail, (state, action) => { + const { checkGroup, stepIndex, error } = action.payload; + state[checkGroup] = state[checkGroup] ? { [stepIndex]: state[checkGroup][stepIndex] ? { @@ -150,8 +116,6 @@ export const networkEventsReducer = handleActions( error, isWaterfallSupported: true, }, - }, - }), - }, - initialState -); + }; + }); +}); 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 511dc0b0c9b99..09404a75e5906 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 @@ -37,9 +37,12 @@ export function formatTestRunAt(timestamp: string, format: string) { } export function useFormatTestRunAt(timestamp?: string) { - const format = useKibanaDateFormat(); + let format = useKibanaDateFormat(); if (!timestamp) { return ''; } + if (format.endsWith('.SSS')) { + format = format.replace('.SSS', ''); + } return formatTestRunAt(timestamp, format); }