From 92a62b3c853850c587931b4cc870773f5b9f1591 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Mon, 17 Oct 2022 15:59:10 +0200 Subject: [PATCH 01/12] added screenshot --- .../common/runtime_types/ping/ping.ts | 3 +- .../browser_steps_list.tsx | 2 +- .../journey_step_image_popover.tsx | 10 +- .../monitor_details_portal.tsx | 8 +- .../monitor_summary/last_ten_test_runs.tsx | 8 +- .../components/last_successful_screenshot.tsx | 47 +++++ .../step_screenshot_display.test.tsx | 91 ++++++++ .../screenshot/step_screenshot_display.tsx | 197 ++++++++++++++++++ .../components/step_image.tsx | 58 ++++++ .../hooks/use_step_detail_page.ts | 68 ++++++ .../step_details_page/step_detail_page.tsx | 70 +++++++ .../step_details_page/step_title.tsx | 26 +++ .../contexts/synthetics_theme_context.tsx | 4 +- .../public/apps/synthetics/routes.tsx | 21 ++ .../__mocks__/synthetics_store.mock.ts | 1 + .../synthetics/utils/testing/rtl_helpers.tsx | 1 + .../components/monitor/monitor_title.test.tsx | 2 + .../monitor/ping_list/ping_list.test.tsx | 4 + .../monitor_status.bar.test.tsx | 1 + .../columns/monitor_status_column.test.tsx | 9 + .../monitor_list_drawer.test.tsx.snap | 5 + .../monitor_status_list.test.tsx | 6 + .../state/reducers/monitor_status.test.ts | 2 + .../requests/get_monitor_availability.test.ts | 7 + .../server/requests/get_journey_details.ts | 130 ++++++++++++ 25 files changed, 769 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/last_successful_screenshot.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.test.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/step_image.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_detail_page.ts create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_title.tsx create mode 100644 x-pack/plugins/synthetics/server/requests/get_journey_details.ts diff --git a/x-pack/plugins/synthetics/common/runtime_types/ping/ping.ts b/x-pack/plugins/synthetics/common/runtime_types/ping/ping.ts index d903a855018d7..929fed2a647c3 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/ping/ping.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/ping/ping.ts @@ -93,12 +93,12 @@ export const MonitorType = t.intersection([ id: t.string, status: t.string, type: t.string, + check_group: t.string, }), t.partial({ duration: t.type({ us: t.number, }), - check_group: t.string, ip: t.string, name: t.string, timespan: t.type({ @@ -266,6 +266,7 @@ export const makePing = (f: { status: f.status || 'up', duration: { us: f.duration || 100000 }, name: f.name, + check_group: 'myCheckGroup', }, ...(f.location ? { observer: { geo: { name: f.location } } } : {}), ...(f.url ? { url: { full: f.url } } : {}), 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 81ecb7dc60027..ebc6cc265dfa5 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 @@ -103,7 +103,7 @@ export const BrowserStepsList = ({ steps, error, loading, showStepNumber = false aria-label={VIEW_DETAILS} title={VIEW_DETAILS} size="s" - href={`${basePath}/app/uptime/journey/${item.monitor.check_group}/step/${item.synthetics?.step?.index}`} + href={`${basePath}/app/synthetics/journey/${item.monitor.check_group}/step/${item.synthetics?.step?.index}`} target="_self" iconType="apmTrace" /> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx index 48ff7237223fb..18f9955c4e471 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx @@ -9,10 +9,12 @@ import React from 'react'; import { css } from '@emotion/react'; import { EuiImage, EuiPopover, useEuiTheme } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useRouteMatch } from 'react-router-dom'; import { ScreenshotRefImageData } from '../../../../../../common/runtime_types'; import { useCompositeImage } from '../../../hooks/use_composite_image'; import { EmptyThumbnail, thumbnailStyle } from './empty_thumbnail'; +import { STEP_DETAIL_ROUTE } from '../../../../../../common/constants'; const POPOVER_IMG_HEIGHT = 360; const POPOVER_IMG_WIDTH = 640; @@ -39,7 +41,7 @@ const ScreenshotThumbnail: React.FC ) : ( @@ -158,12 +160,16 @@ export const JourneyStepImagePopover: React.FC = ({ const isImageLoading = isLoading || (!!imgRef && !imageData); + const isStepDetailPage = useRouteMatch(STEP_DETAIL_ROUTE)?.isExact; + + const thumbnailS = isStepDetailPage ? null : thumbnailStyle; + return ( - {name} - + + {name} + ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_ten_test_runs.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_ten_test_runs.tsx index fc01a5d9164ee..2e944a3e3570f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_ten_test_runs.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_ten_test_runs.tsx @@ -77,7 +77,9 @@ export const LastTenTestRuns = () => { align: 'left', field: 'timestamp', name: SCREENSHOT_LABEL, - render: (_timestamp: string, item) => , + render: (_timestamp: string, item) => ( + + ), }, ] : []) as Array>), @@ -164,8 +166,8 @@ export const LastTenTestRuns = () => { ); }; -const JourneyScreenshot = ({ ping }: { ping: Ping }) => { - const { data: stepsData, loading: stepsLoading } = useJourneySteps(ping?.monitor?.check_group); +export const JourneyScreenshot = ({ checkGroupId }: { checkGroupId: string }) => { + const { data: stepsData, loading: stepsLoading } = useJourneySteps(checkGroupId); const stepEnds: JourneyStep[] = (stepsData?.steps ?? []).filter(isStepEnd); const stepLabels = stepEnds.map((stepEnd) => stepEnd?.synthetics?.step?.name ?? ''); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/last_successful_screenshot.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/last_successful_screenshot.tsx new file mode 100644 index 0000000000000..b6acf3caba169 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/last_successful_screenshot.tsx @@ -0,0 +1,47 @@ +/* + * 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 { useFetcher } from '@kbn/observability-plugin/public'; +import { EuiSpacer } from '@elastic/eui'; +import React from 'react'; +import { useParams } from 'react-router-dom'; +import { fetchLastSuccessfulCheck } from '../../../state'; +import { StepScreenshotDisplay } from './screenshot/step_screenshot_display'; +import { JourneyStep, Ping } from '../../../../../../common/runtime_types'; + +export const LastSuccessfulScreenshot = ({ step }: { step: JourneyStep }) => { + const { stepIndex } = useParams<{ checkGroupId: string; stepIndex: string }>(); + + const { data } = useFetcher(() => { + return fetchLastSuccessfulCheck({ + timestamp: step['@timestamp'], + monitorId: step.monitor.id, + stepIndex: Number(stepIndex), + location: step.observer?.geo?.name, + }); + }, [step._id, step['@timestamp']]); + + const lastSuccessfulCheck: Ping | undefined = data; + + if (!lastSuccessfulCheck) { + return null; + } + + return ( + <> + + + + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.test.tsx new file mode 100644 index 0000000000000..7a275527de3c6 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.test.tsx @@ -0,0 +1,91 @@ +/* + * 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 { StepScreenshotDisplay } from './step_screenshot_display'; +import * as observabilityPublic from '@kbn/observability-plugin/public'; +import { mockRef } from '../../../../utils/testing/__mocks__/screenshot_ref.mock'; +import { render } from '../../../../utils/testing'; +import '../../../../utils/testing/__mocks__/use_composite_image.mock'; +jest.mock('@kbn/observability-plugin/public'); + +jest.mock('react-use/lib/useIntersection', () => () => ({ + isIntersecting: true, +})); + +describe('StepScreenshotDisplayProps', () => { + beforeAll(() => { + jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ + data: null, + status: observabilityPublic.FETCH_STATUS.SUCCESS, + refetch: () => {}, + }); + }); + + afterAll(() => { + (observabilityPublic.useFetcher as any).mockClear(); + }); + it('displays screenshot thumbnail when present', () => { + const { getByAltText } = render( + + ); + + expect(getByAltText('Screenshot for step with name "STEP_NAME"')).toBeInTheDocument(); + }); + + it('uses alternative text when step name not available', () => { + const { getByAltText } = render( + + ); + + expect(getByAltText('Screenshot')).toBeInTheDocument(); + }); + + it('displays No Image message when screenshot does not exist', () => { + const { getByTestId } = render( + + ); + expect(getByTestId('stepScreenshotImageUnavailable')).toBeInTheDocument(); + }); + + it('displays screenshot thumbnail for ref', () => { + jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ + status: observabilityPublic.FETCH_STATUS.SUCCESS, + data: { ...mockRef }, + refetch: () => null, + }); + + const { getByAltText } = render( + + ); + + expect(getByAltText('Screenshot for step with name "STEP_NAME"')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.tsx new file mode 100644 index 0000000000000..1dbfe9595cc44 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.tsx @@ -0,0 +1,197 @@ +/* + * 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, + EuiIcon, + EuiImage, + EuiLoadingSpinner, + EuiText, +} from '@elastic/eui'; +import styled from 'styled-components'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useEffect, useMemo, useRef, useState, FC } from 'react'; +import useIntersection from 'react-use/lib/useIntersection'; +import { useFetcher } from '@kbn/observability-plugin/public'; +import { getJourneyScreenshot } from '../../../../state'; +import { useCompositeImage } from '../../../../hooks'; +import { + useSyntheticsRefreshContext, + useSyntheticsSettingsContext, + useSyntheticsThemeContext, +} from '../../../../contexts'; +import { + isScreenshotRef as isAScreenshotRef, + ScreenshotRefImageData, +} from '../../../../../../../common/runtime_types'; + +interface StepScreenshotDisplayProps { + isFullScreenshot: boolean; + isScreenshotRef: boolean; + checkGroup?: string; + stepIndex?: number; + stepName?: string; + lazyLoad?: boolean; +} + +const IMAGE_MAX_WIDTH = 640; + +const StepImage = styled(EuiImage)` + &&& { + figcaption { + display: none; + } + objectFit: 'cover', + objectPosition: 'center top', + } +`; + +const BaseStepImage = ({ + stepIndex, + stepName, + url, +}: Pick & { url?: string }) => { + if (!url) return ; + return ( + + ); +}; + +type ComposedStepImageProps = Pick & { + url: string | undefined; + imgRef: ScreenshotRefImageData; + setUrl: React.Dispatch; +}; + +const ComposedStepImage = ({ + stepIndex, + stepName, + url, + imgRef, + setUrl, +}: ComposedStepImageProps) => { + useCompositeImage(imgRef, setUrl, url); + if (!url) return ; + return ; +}; + +export const StepScreenshotDisplay: FC = ({ + checkGroup, + isFullScreenshot: isScreenshotBlob, + isScreenshotRef, + stepIndex, + stepName, + lazyLoad = true, +}) => { + const containerRef = useRef(null); + const { + colors: { lightestShade: pageBackground }, + } = useSyntheticsThemeContext(); + + const { basePath } = useSyntheticsSettingsContext(); + + const intersection = useIntersection(containerRef, { + root: null, + rootMargin: '0px', + threshold: 1, + }); + const { lastRefresh } = useSyntheticsRefreshContext(); + + const [hasIntersected, setHasIntersected] = useState(false); + const isIntersecting = intersection?.isIntersecting; + useEffect(() => { + if (hasIntersected === false && isIntersecting === true) { + setHasIntersected(true); + } + }, [hasIntersected, isIntersecting, setHasIntersected]); + + const imgSrc = basePath + `/internal/uptime/journey/screenshot/${checkGroup}/${stepIndex}`; + + // When loading a legacy screenshot, set `url` to full-size screenshot path. + // Otherwise, we first need to composite the image. + const [url, setUrl] = useState(isScreenshotBlob ? imgSrc : undefined); + + // when the image is a composite, we need to fetch the data since we cannot specify a blob URL + const { data: screenshotRef } = useFetcher(() => { + if (isScreenshotRef) { + return getJourneyScreenshot(imgSrc); + } + }, [basePath, checkGroup, imgSrc, stepIndex, isScreenshotRef, lastRefresh]); + + const refDimensions = useMemo(() => { + if (isAScreenshotRef(screenshotRef)) { + const { height, width } = screenshotRef.ref.screenshotRef.screenshot_ref; + return { height, width }; + } + }, [screenshotRef]); + + const shouldRenderImage = hasIntersected || !lazyLoad; + return ( +
+ {shouldRenderImage && isScreenshotBlob && ( + + )} + {shouldRenderImage && isScreenshotRef && isAScreenshotRef(screenshotRef) && ( + + )} + {!isScreenshotBlob && !isScreenshotRef && ( + + + + + + + + + + + + + )} +
+ ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/step_image.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/step_image.tsx new file mode 100644 index 0000000000000..c07c3d12c4237 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/step_image.tsx @@ -0,0 +1,58 @@ +/* + * 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 { EuiButtonGroup, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { LastSuccessfulScreenshot } from './last_successful_screenshot'; +import { JourneyStep } from '../../../../../../common/runtime_types'; +import { JourneyScreenshot } from '../../monitor_details/monitor_summary/last_ten_test_runs'; + +export const StepImage = ({ ping }: { ping: JourneyStep }) => { + const toggleButtonsDisabled = [ + { + id: `received`, + label: 'Received', + }, + { + id: `expected`, + label: 'Expected', + }, + ]; + + const [idSelected, setIdSelected] = useState(`received`); + + const onChangeDisabled = (optionId: string) => { + setIdSelected(optionId); + }; + + return ( + <> + +

Screenshot

+
+ +
+ {idSelected === 'received' ? ( + + ) : ( + + )} + + + + onChangeDisabled(id)} + buttonSize="s" + isFullWidth + /> +
+ + ); +}; 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 new file mode 100644 index 0000000000000..4c79502ce665f --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_detail_page.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { 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<{ + checkGroupId: string; + stepIndex: string; + }>(); + + const stepIndex = Number(stepIndexString); + + const { data: journey } = 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`; + + return { + checkGroupId, + journey, + stepIndex, + ...memoized, + handleNextStepHref, + handlePreviousStepHref, + handleNextRunHref, + handlePreviousRunHref, + }; +}; 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 new file mode 100644 index 0000000000000..0cae084fbfbcf --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx @@ -0,0 +1,70 @@ +/* + * 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 { useParams } from 'react-router-dom'; +import { useTrackPageview } from '@kbn/observability-plugin/public'; +import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel } from '@elastic/eui'; +import { StepImage } from './components/step_image'; +import { useJourneySteps } from '../monitor_details/hooks/use_journey_steps'; +import { MonitorDetailsLinkPortal } from '../monitor_add_edit/monitor_details_portal'; + +export const StepDetailPage = () => { + const { checkGroupId } = useParams<{ checkGroupId: string; stepIndex: string }>(); + + useTrackPageview({ app: 'synthetics', path: 'stepDetail' }); + useTrackPageview({ app: 'synthetics', path: 'stepDetail', delay: 15000 }); + + const { data } = useJourneySteps(checkGroupId); + + return ( + <> + {data?.details?.journey && ( + + )} + + + + {data?.details?.journey && } + + + + + + + {/* TODO: Add breakdown of network timings donut*/} + + + {/* TODO: Add breakdown of network events*/} + + + + + + + + {/* TODO: Add step metrics*/} + + + + + + {/* TODO: Add breakdown of object list*/} + + {/* TODO: Add breakdown of object weight*/} + + + + + + {/* TODO: Add breakdown of network events*/} + + ); +}; 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 new file mode 100644 index 0000000000000..5cb758e4b3d37 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_title.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useParams } from 'react-router-dom'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { useJourneySteps } from '../monitor_details/hooks/use_journey_steps'; + +export const StepTitle = () => { + const { checkGroupId, stepIndex } = useParams<{ checkGroupId: string; stepIndex: string }>(); + + const { data } = useJourneySteps(checkGroupId); + + const currentStep = data?.steps.find((step) => step.synthetics.step?.index === Number(stepIndex)); + + return ( + + {currentStep?.synthetics?.step?.name} + + + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_theme_context.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_theme_context.tsx index 32844de9d04c2..1415b1076cdd9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_theme_context.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_theme_context.tsx @@ -6,7 +6,7 @@ */ import { euiLightVars, euiDarkVars } from '@kbn/ui-theme'; -import React, { createContext, useMemo } from 'react'; +import React, { createContext, useContext, useMemo } from 'react'; import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; import { DARK_THEME, LIGHT_THEME, PartialTheme, Theme } from '@elastic/charts'; @@ -96,3 +96,5 @@ export const SyntheticsThemeContextProvider: React.FC = ({ return ; }; + +export const useSyntheticsThemeContext = () => useContext(SyntheticsThemeContext); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx index 92ec0afce43ff..6626575eea958 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx @@ -23,6 +23,7 @@ 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 { StepTitle } from './components/step_details_page/step_title'; import { MonitorAddPage } from './components/monitor_add_edit/monitor_add_page'; import { MonitorEditPage } from './components/monitor_add_edit/monitor_edit_page'; import { MonitorDetailsPageTitle } from './components/monitor_details/monitor_details_page_title'; @@ -45,6 +46,7 @@ import { MONITOR_ERRORS_ROUTE, MONITOR_HISTORY_ROUTE, MONITOR_ROUTE, + STEP_DETAIL_ROUTE, OVERVIEW_ROUTE, } from '../../../common/constants'; import { PLUGIN } from '../../../common/constants/plugin'; @@ -57,6 +59,7 @@ 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'; type RouteProps = LazyObservabilityPageTemplateProps & { path: string; @@ -283,6 +286,24 @@ const getRoutes = ( ], }, }, + { + title: i18n.translate('xpack.synthetics.stepDetailsRoute.title', { + defaultMessage: 'Step details | {baseTitle}', + values: { baseTitle }, + }), + path: STEP_DETAIL_ROUTE, + component: () => , + dataTestSubj: 'syntheticsMonitorEditPage', + pageHeader: { + pageTitle: , + rightSideItems: [], + breadcrumbs: [ + { + text: , + }, + ], + }, + }, ]; }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts index 6031707c1fd19..535097db38081 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts @@ -102,6 +102,7 @@ export const mockState: SyntheticsAppState = { syntheticsEnablement: { loading: false, error: null, enablement: null }, monitorDetails: getMonitorDetailsMockSlice(), browserJourney: getBrowserJourneyMockSlice(), + networkEvents: {}, }; function getBrowserJourneyMockSlice() { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx index 55ae549a032b3..3168c1b07ee33 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx @@ -95,6 +95,7 @@ const createMockStore = () => { const mockAppUrls: Record = { uptime: '/app/uptime', + synthetics: '/app/synthetics', observability: '/app/observability', '/home#/tutorial/uptimeMonitors': '/home#/tutorial/uptimeMonitors', }; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/monitor_title.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/monitor_title.test.tsx index f9e3572ead511..e4594e8c60630 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/monitor_title.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/monitor_title.test.tsx @@ -42,6 +42,7 @@ describe('MonitorTitle component', () => { id: defaultMonitorId, status: 'up', type: 'http', + check_group: 'test-group', }, url: { full: 'https://www.elastic.co/', @@ -58,6 +59,7 @@ describe('MonitorTitle component', () => { id: 'browser', status: 'up', type: 'browser', + check_group: 'test-group', }, url: { full: 'https://www.elastic.co/', diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/ping_list.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/ping_list.test.tsx index ddb33e4dd5fea..f0e60b2902828 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/ping_list.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/ping_list.test.tsx @@ -32,6 +32,7 @@ describe('PingList component', () => { name: '', status: 'down', type: 'tcp', + check_group: 'test-group', }, }, { @@ -47,6 +48,7 @@ describe('PingList component', () => { name: '', status: 'down', type: 'tcp', + check_group: 'test-group', }, }, ]; @@ -120,6 +122,7 @@ describe('PingList component', () => { "type": "io", }, "monitor": Object { + "check_group": "test-group", "duration": Object { "us": 1430, }, @@ -160,6 +163,7 @@ describe('PingList component', () => { "type": "io", }, "monitor": Object { + "check_group": "test-group", "id": "auto-tcp-0X81440A68E839814D", "ip": "255.255.255.0", "name": "", diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/status_details/monitor_status.bar.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/status_details/monitor_status.bar.test.tsx index 640d207fbb138..af5df91160026 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/status_details/monitor_status.bar.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/status_details/monitor_status.bar.test.tsx @@ -28,6 +28,7 @@ describe('MonitorStatusBar component', () => { id: 'id1', status: 'up', type: 'http', + check_group: 'test-group', }, url: { full: 'https://www.example.com/', diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_status_column.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_status_column.test.tsx index a69ebb3d349fd..7869125014b02 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_status_column.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_status_column.test.tsx @@ -37,6 +37,7 @@ describe('MonitorListStatusColumn', () => { id: 'myMonitor', type: 'icmp', duration: { us: 123 }, + check_group: 'test-group', }, observer: { geo: { @@ -58,6 +59,7 @@ describe('MonitorListStatusColumn', () => { id: 'myMonitor', type: 'icmp', duration: { us: 123 }, + check_group: 'test-group', }, observer: { geo: { @@ -79,6 +81,7 @@ describe('MonitorListStatusColumn', () => { id: 'myMonitor', type: 'icmp', duration: { us: 123 }, + check_group: 'test-group', }, observer: { geo: { @@ -103,6 +106,7 @@ describe('MonitorListStatusColumn', () => { id: 'myMonitor', type: 'icmp', duration: { us: 123 }, + check_group: 'test-group', }, observer: { geo: { @@ -124,6 +128,7 @@ describe('MonitorListStatusColumn', () => { id: 'myMonitor', type: 'icmp', duration: { us: 123 }, + check_group: 'test-group', }, observer: { geo: { @@ -145,6 +150,7 @@ describe('MonitorListStatusColumn', () => { id: 'myMonitor', type: 'icmp', duration: { us: 123 }, + check_group: 'test-group', }, observer: { geo: { @@ -169,6 +175,7 @@ describe('MonitorListStatusColumn', () => { id: 'myMonitor', type: 'icmp', duration: { us: 123 }, + check_group: 'test-group', }, observer: { geo: { @@ -190,6 +197,7 @@ describe('MonitorListStatusColumn', () => { id: 'myMonitor', type: 'icmp', duration: { us: 123 }, + check_group: 'test-group', }, observer: { geo: { @@ -211,6 +219,7 @@ describe('MonitorListStatusColumn', () => { id: 'myMonitor', type: 'icmp', duration: { us: 123 }, + check_group: 'test-group', }, observer: { geo: { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/__snapshots__/monitor_list_drawer.test.tsx.snap b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/__snapshots__/monitor_list_drawer.test.tsx.snap index a07a55df6dbfa..0c037492bbc6b 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/__snapshots__/monitor_list_drawer.test.tsx.snap +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/__snapshots__/monitor_list_drawer.test.tsx.snap @@ -111,6 +111,7 @@ exports[`MonitorListDrawer component renders a MonitorListDrawer when there are Object { "docId": "foo", "monitor": Object { + "check_group": "myCheckGroup", "duration": Object { "us": 121, }, @@ -125,6 +126,7 @@ exports[`MonitorListDrawer component renders a MonitorListDrawer when there are Object { "docId": "foo-0", "monitor": Object { + "check_group": "myCheckGroup", "duration": Object { "us": 100000, }, @@ -139,6 +141,7 @@ exports[`MonitorListDrawer component renders a MonitorListDrawer when there are Object { "docId": "foo-1", "monitor": Object { + "check_group": "myCheckGroup", "duration": Object { "us": 1, }, @@ -153,6 +156,7 @@ exports[`MonitorListDrawer component renders a MonitorListDrawer when there are Object { "docId": "foo-2", "monitor": Object { + "check_group": "myCheckGroup", "duration": Object { "us": 2, }, @@ -289,6 +293,7 @@ exports[`MonitorListDrawer component renders a MonitorListDrawer when there is o Object { "docId": "foo", "monitor": Object { + "check_group": "myCheckGroup", "duration": Object { "us": 121, }, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/monitor_status_list.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/monitor_status_list.test.tsx index f1a9d1b2629a6..7318fc5188af8 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/monitor_status_list.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/monitor_status_list.test.tsx @@ -29,6 +29,7 @@ describe('MonitorStatusList component', () => { id: 'myMonitor', type: 'icmp', duration: { us: 123 }, + check_group: 'test-group', }, observer: { geo: {}, @@ -44,6 +45,7 @@ describe('MonitorStatusList component', () => { id: 'myMonitor', type: 'icmp', duration: { us: 123 }, + check_group: 'test-group', }, observer: { geo: {}, @@ -59,6 +61,7 @@ describe('MonitorStatusList component', () => { id: 'myUpMonitor', type: 'icmp', duration: { us: 234 }, + check_group: 'test-group', }, observer: { geo: { @@ -165,6 +168,7 @@ describe('MonitorStatusList component', () => { id: 'myMonitor', type: 'icmp', duration: { us: 234 }, + check_group: 'test-group', }, observer: { geo: { @@ -182,6 +186,7 @@ describe('MonitorStatusList component', () => { id: 'myMonitor', type: 'icmp', duration: { us: 234 }, + check_group: 'test-group', }, observer: { geo: { @@ -199,6 +204,7 @@ describe('MonitorStatusList component', () => { id: 'myMonitor', type: 'icmp', duration: { us: 234 }, + check_group: 'test-group', }, observer: { geo: { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_status.test.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_status.test.ts index f6c637e5fdb1b..c7675d9607772 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_status.test.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_status.test.ts @@ -25,6 +25,7 @@ describe('selectedFiltersReducer', () => { us: 1, }, type: 'browser', + check_group: 'test-group', }, }, }; @@ -42,6 +43,7 @@ describe('selectedFiltersReducer', () => { us: 1, }, type: 'browser', + check_group: 'test-group', }, }; expect( diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_monitor_availability.test.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_monitor_availability.test.ts index d84c4025d5dd0..7069cfce17740 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_monitor_availability.test.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_monitor_availability.test.ts @@ -411,6 +411,7 @@ describe('monitor availability', () => { "monitorInfo": Object { "docId": "myDocId", "monitor": Object { + "check_group": "myCheckGroup", "duration": Object { "us": 100000, }, @@ -432,6 +433,7 @@ describe('monitor availability', () => { "monitorInfo": Object { "docId": "myDocId", "monitor": Object { + "check_group": "myCheckGroup", "duration": Object { "us": 100000, }, @@ -453,6 +455,7 @@ describe('monitor availability', () => { "monitorInfo": Object { "docId": "myDocId", "monitor": Object { + "check_group": "myCheckGroup", "duration": Object { "us": 100000, }, @@ -542,6 +545,7 @@ describe('monitor availability', () => { "monitorInfo": Object { "docId": "myDocId", "monitor": Object { + "check_group": "myCheckGroup", "duration": Object { "us": 100000, }, @@ -563,6 +567,7 @@ describe('monitor availability', () => { "monitorInfo": Object { "docId": "myDocId", "monitor": Object { + "check_group": "myCheckGroup", "duration": Object { "us": 100000, }, @@ -584,6 +589,7 @@ describe('monitor availability', () => { "monitorInfo": Object { "docId": "myDocId", "monitor": Object { + "check_group": "myCheckGroup", "duration": Object { "us": 100000, }, @@ -605,6 +611,7 @@ describe('monitor availability', () => { "monitorInfo": Object { "docId": "myDocId", "monitor": Object { + "check_group": "myCheckGroup", "duration": Object { "us": 100000, }, diff --git a/x-pack/plugins/synthetics/server/requests/get_journey_details.ts b/x-pack/plugins/synthetics/server/requests/get_journey_details.ts new file mode 100644 index 0000000000000..7d90d2ec86885 --- /dev/null +++ b/x-pack/plugins/synthetics/server/requests/get_journey_details.ts @@ -0,0 +1,130 @@ +/* + * 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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { UMElasticsearchQueryFn } from '../legacy_uptime/lib/adapters'; +import { JourneyStep, SyntheticsJourneyApiResponse } from '../../common/runtime_types'; + +export interface GetJourneyDetails { + checkGroup: string; +} + +export const getJourneyDetails: UMElasticsearchQueryFn< + GetJourneyDetails, + SyntheticsJourneyApiResponse['details'] +> = async ({ uptimeEsClient, checkGroup }) => { + const baseParams = { + query: { + bool: { + filter: [ + { + term: { + 'monitor.check_group': checkGroup, + }, + }, + { + term: { + 'synthetics.type': 'journey/start', + }, + }, + ] as QueryDslQueryContainer[], + }, + }, + size: 1, + }; + + const { body: thisJourney } = await uptimeEsClient.search({ body: baseParams }); + + if (thisJourney.hits.hits.length > 0) { + const { _id, _source } = thisJourney.hits.hits[0]; + const thisJourneySource = Object.assign({ _id }, _source) as JourneyStep; + + const baseSiblingParams = { + query: { + bool: { + filter: [ + { + term: { + 'monitor.id': thisJourneySource.monitor.id, + }, + }, + { + term: { + 'synthetics.type': 'journey/start', + }, + }, + ] as QueryDslQueryContainer[], + }, + }, + _source: ['@timestamp', 'monitor.check_group'], + size: 1, + }; + + const previousParams = { + ...baseSiblingParams, + query: { + bool: { + filter: [ + ...baseSiblingParams.query.bool.filter, + { + range: { + '@timestamp': { + lt: thisJourneySource['@timestamp'], + }, + }, + }, + ], + }, + }, + sort: [{ '@timestamp': { order: 'desc' as const } }], + }; + + const nextParams = { + ...baseSiblingParams, + query: { + bool: { + filter: [ + ...baseSiblingParams.query.bool.filter, + { + range: { + '@timestamp': { + gt: thisJourneySource['@timestamp'], + }, + }, + }, + ], + }, + }, + sort: [{ '@timestamp': { order: 'asc' as const } }], + }; + + const { body: previousJourneyResult } = await uptimeEsClient.search({ body: previousParams }); + const { body: nextJourneyResult } = await uptimeEsClient.search({ body: nextParams }); + const previousJourney: any = + previousJourneyResult?.hits?.hits.length > 0 ? previousJourneyResult?.hits?.hits[0] : null; + const nextJourney: any = + nextJourneyResult?.hits?.hits.length > 0 ? nextJourneyResult?.hits?.hits[0] : null; + return { + timestamp: thisJourneySource['@timestamp'], + journey: thisJourneySource, + previous: previousJourney + ? { + checkGroup: previousJourney._source.monitor.check_group, + timestamp: previousJourney._source['@timestamp'], + } + : undefined, + next: nextJourney + ? { + checkGroup: nextJourney._source.monitor.check_group, + timestamp: nextJourney._source['@timestamp'], + } + : undefined, + } as SyntheticsJourneyApiResponse['details']; + } else { + return null; + } +}; From 70e271047793f965c311276efb6c8efcd143b3c8 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Tue, 18 Oct 2022 12:15:45 +0200 Subject: [PATCH 02/12] fix type --- .../synthetics/utils/testing/__mocks__/synthetics_store.mock.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts index 535097db38081..6031707c1fd19 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts @@ -102,7 +102,6 @@ export const mockState: SyntheticsAppState = { syntheticsEnablement: { loading: false, error: null, enablement: null }, monitorDetails: getMonitorDetailsMockSlice(), browserJourney: getBrowserJourneyMockSlice(), - networkEvents: {}, }; function getBrowserJourneyMockSlice() { From 71703883a0c97e0f62d01b39baeaf533f09cb8e2 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Tue, 18 Oct 2022 12:46:51 +0200 Subject: [PATCH 03/12] test fix --- .../components/step_image.tsx | 40 +++++++++++++------ .../hooks/use_step_details_breadcrumbs.ts | 28 +++++++++++++ .../step_details_page/step_detail_page.tsx | 21 +++++++++- 3 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_details_breadcrumbs.ts diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/step_image.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/step_image.tsx index c07c3d12c4237..266f929efdc31 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/step_image.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/step_image.tsx @@ -7,19 +7,20 @@ import React, { useState } from 'react'; import { EuiButtonGroup, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { LastSuccessfulScreenshot } from './last_successful_screenshot'; import { JourneyStep } from '../../../../../../common/runtime_types'; import { JourneyScreenshot } from '../../monitor_details/monitor_summary/last_ten_test_runs'; export const StepImage = ({ ping }: { ping: JourneyStep }) => { - const toggleButtonsDisabled = [ + const toggleButtons = [ { id: `received`, - label: 'Received', + label: RECEIVED_LABEL, }, { id: `expected`, - label: 'Expected', + label: EXPECTED_LABEL, }, ]; @@ -32,7 +33,7 @@ export const StepImage = ({ ping }: { ping: JourneyStep }) => { return ( <> -

Screenshot

+

{SCREENSHOT_LABEL}

@@ -43,16 +44,29 @@ export const StepImage = ({ ping }: { ping: JourneyStep }) => { )} - - onChangeDisabled(id)} - buttonSize="s" - isFullWidth - /> + {ping.monitor.status === 'down' && ( + onChangeDisabled(id)} + buttonSize="s" + isFullWidth + /> + )}
); }; + +const SCREENSHOT_LABEL = i18n.translate('xpack.synthetics.stepDetails.screenshot', { + defaultMessage: 'Screenshot', +}); + +const EXPECTED_LABEL = i18n.translate('xpack.synthetics.stepDetails.expected', { + defaultMessage: 'Expected', +}); + +const RECEIVED_LABEL = i18n.translate('xpack.synthetics.stepDetails.received', { + defaultMessage: 'Received', +}); 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 new file mode 100644 index 0000000000000..b42417083cc3a --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_details_breadcrumbs.ts @@ -0,0 +1,28 @@ +/* + * 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 { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs'; +import { MONITORS_ROUTE } from '../../../../../../common/constants'; +import { PLUGIN } from '../../../../../../common/constants/plugin'; + +export const useStepDetailsBreadcrumbs = (extraCrumbs?: Array<{ text: string; href?: string }>) => { + const kibana = useKibana(); + const appPath = kibana.services.application?.getUrlForApp(PLUGIN.SYNTHETICS_PLUGIN_ID) ?? ''; + + useBreadcrumbs([ + { + text: MONITOR_MANAGEMENT_CRUMB, + href: `${appPath}/${MONITORS_ROUTE}`, + }, + ...(extraCrumbs ?? []), + ]); +}; + +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/step_detail_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx index 0cae084fbfbcf..f4ce9c10f125e 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 @@ -8,10 +8,17 @@ import React from 'react'; import { useParams } from 'react-router-dom'; import { useTrackPageview } from '@kbn/observability-plugin/public'; -import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiPanel, + EuiLoadingSpinner, +} from '@elastic/eui'; import { StepImage } from './components/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'; export const StepDetailPage = () => { const { checkGroupId } = useParams<{ checkGroupId: string; stepIndex: string }>(); @@ -19,7 +26,17 @@ export const StepDetailPage = () => { useTrackPageview({ app: 'synthetics', path: 'stepDetail' }); useTrackPageview({ app: 'synthetics', path: 'stepDetail', delay: 15000 }); - const { data } = useJourneySteps(checkGroupId); + const { data, loading } = useJourneySteps(checkGroupId); + + useStepDetailsBreadcrumbs([{ text: data?.details?.journey.monitor.name ?? '' }]); + + if (loading) { + return ( +
+ +
+ ); + } return ( <> From b9bf384cc1ebbb4b43b45b23e07919c3f5db1d36 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Tue, 18 Oct 2022 13:33:51 +0200 Subject: [PATCH 04/12] update --- .../server/requests/get_journey_details.ts | 130 ------------------ 1 file changed, 130 deletions(-) delete mode 100644 x-pack/plugins/synthetics/server/requests/get_journey_details.ts diff --git a/x-pack/plugins/synthetics/server/requests/get_journey_details.ts b/x-pack/plugins/synthetics/server/requests/get_journey_details.ts deleted file mode 100644 index 7d90d2ec86885..0000000000000 --- a/x-pack/plugins/synthetics/server/requests/get_journey_details.ts +++ /dev/null @@ -1,130 +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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { UMElasticsearchQueryFn } from '../legacy_uptime/lib/adapters'; -import { JourneyStep, SyntheticsJourneyApiResponse } from '../../common/runtime_types'; - -export interface GetJourneyDetails { - checkGroup: string; -} - -export const getJourneyDetails: UMElasticsearchQueryFn< - GetJourneyDetails, - SyntheticsJourneyApiResponse['details'] -> = async ({ uptimeEsClient, checkGroup }) => { - const baseParams = { - query: { - bool: { - filter: [ - { - term: { - 'monitor.check_group': checkGroup, - }, - }, - { - term: { - 'synthetics.type': 'journey/start', - }, - }, - ] as QueryDslQueryContainer[], - }, - }, - size: 1, - }; - - const { body: thisJourney } = await uptimeEsClient.search({ body: baseParams }); - - if (thisJourney.hits.hits.length > 0) { - const { _id, _source } = thisJourney.hits.hits[0]; - const thisJourneySource = Object.assign({ _id }, _source) as JourneyStep; - - const baseSiblingParams = { - query: { - bool: { - filter: [ - { - term: { - 'monitor.id': thisJourneySource.monitor.id, - }, - }, - { - term: { - 'synthetics.type': 'journey/start', - }, - }, - ] as QueryDslQueryContainer[], - }, - }, - _source: ['@timestamp', 'monitor.check_group'], - size: 1, - }; - - const previousParams = { - ...baseSiblingParams, - query: { - bool: { - filter: [ - ...baseSiblingParams.query.bool.filter, - { - range: { - '@timestamp': { - lt: thisJourneySource['@timestamp'], - }, - }, - }, - ], - }, - }, - sort: [{ '@timestamp': { order: 'desc' as const } }], - }; - - const nextParams = { - ...baseSiblingParams, - query: { - bool: { - filter: [ - ...baseSiblingParams.query.bool.filter, - { - range: { - '@timestamp': { - gt: thisJourneySource['@timestamp'], - }, - }, - }, - ], - }, - }, - sort: [{ '@timestamp': { order: 'asc' as const } }], - }; - - const { body: previousJourneyResult } = await uptimeEsClient.search({ body: previousParams }); - const { body: nextJourneyResult } = await uptimeEsClient.search({ body: nextParams }); - const previousJourney: any = - previousJourneyResult?.hits?.hits.length > 0 ? previousJourneyResult?.hits?.hits[0] : null; - const nextJourney: any = - nextJourneyResult?.hits?.hits.length > 0 ? nextJourneyResult?.hits?.hits[0] : null; - return { - timestamp: thisJourneySource['@timestamp'], - journey: thisJourneySource, - previous: previousJourney - ? { - checkGroup: previousJourney._source.monitor.check_group, - timestamp: previousJourney._source['@timestamp'], - } - : undefined, - next: nextJourney - ? { - checkGroup: nextJourney._source.monitor.check_group, - timestamp: nextJourney._source['@timestamp'], - } - : undefined, - } as SyntheticsJourneyApiResponse['details']; - } else { - return null; - } -}; From f6d8f90e02a41ab31a0e81aecfba7cbf8c02afa2 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Tue, 18 Oct 2022 17:32:48 +0200 Subject: [PATCH 05/12] data --- .../monitor_test_result/empty_thumbnail.tsx | 2 +- .../journey_step_image_popover.tsx | 16 +- .../journey_step_screenshot_with_label.tsx | 2 +- .../common/screenshot/empty_image.tsx | 102 +++++++++ .../common/screenshot/journey_screenshot.tsx | 39 ++++ ...journey_step_screenshot_container.test.tsx | 2 +- .../journey_step_screenshot_container.tsx | 16 +- .../hooks/use_journey_steps.tsx | 27 ++- .../monitor_summary/last_ten_test_runs.tsx | 35 +--- .../last_successful_screenshot.tsx | 29 ++- .../step_screenshot_display.test.tsx | 91 -------- .../screenshot/step_screenshot_display.tsx | 197 ------------------ .../components/step_image.tsx | 28 ++- .../step_details_page/step_detail_page.tsx | 11 +- 14 files changed, 242 insertions(+), 355 deletions(-) create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_image.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot.tsx rename x-pack/plugins/synthetics/public/apps/synthetics/components/common/{monitor_test_result => screenshot}/journey_step_screenshot_container.test.tsx (98%) rename x-pack/plugins/synthetics/public/apps/synthetics/components/common/{monitor_test_result => screenshot}/journey_step_screenshot_container.tsx (89%) rename x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/{ => screenshot}/last_successful_screenshot.tsx (55%) delete mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.test.tsx delete mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.tsx index bfc5851ade619..38ab5c66acbae 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.tsx @@ -15,7 +15,7 @@ export const THUMBNAIL_HEIGHT = 64; export const thumbnailStyle = css` padding: 0; - margin: 0; + margin: auto; width: ${THUMBNAIL_WIDTH}px; height: ${THUMBNAIL_HEIGHT}px; object-fit: contain; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx index 18f9955c4e471..732e74fb98521 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx @@ -10,6 +10,7 @@ import { css } from '@emotion/react'; import { EuiImage, EuiPopover, useEuiTheme } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useRouteMatch } from 'react-router-dom'; +import { EmptyImage } from '../screenshot/empty_image'; import { ScreenshotRefImageData } from '../../../../../../common/runtime_types'; import { useCompositeImage } from '../../../hooks/use_composite_image'; @@ -24,6 +25,7 @@ interface ScreenshotImageProps { imageCaption: JSX.Element; isStepFailed: boolean; isLoading: boolean; + asThumbnail?: boolean; } const ScreenshotThumbnail: React.FC = ({ @@ -32,6 +34,7 @@ const ScreenshotThumbnail: React.FC { return imageData ? ( - ) : ( + ) : asThumbnail ? ( + ) : ( + ); }; /** @@ -90,6 +95,7 @@ export interface StepImagePopoverProps { isImagePopoverOpen: boolean; isStepFailed: boolean; isLoading: boolean; + asThumbnail?: boolean; } const JourneyStepImage: React.FC< @@ -106,6 +112,7 @@ const JourneyStepImage: React.FC< setImageData, isStepFailed, isLoading, + asThumbnail, }) => { if (imgSrc) { return ( @@ -115,6 +122,7 @@ const JourneyStepImage: React.FC< imageData={imageData} isStepFailed={isStepFailed} isLoading={isLoading} + asThumbnail={asThumbnail} /> ); } else if (imgRef) { @@ -141,6 +149,7 @@ export const JourneyStepImagePopover: React.FC = ({ isImagePopoverOpen, isStepFailed, isLoading, + asThumbnail, }) => { const { euiTheme } = useEuiTheme(); @@ -186,6 +195,7 @@ export const JourneyStepImagePopover: React.FC = ({ imageData={imageData} isStepFailed={isStepFailed} isLoading={isImageLoading} + asThumbnail={asThumbnail} /> } isOpen={isImagePopoverOpen} @@ -201,12 +211,14 @@ export const JourneyStepImagePopover: React.FC = ({ object-fit: contain; `} /> - ) : ( + ) : asThumbnail ? ( + ) : ( + )}
); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_with_label.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_with_label.tsx index 12dcd4db95f00..f153aa07f2f4f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_with_label.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_with_label.tsx @@ -8,7 +8,7 @@ import React, { CSSProperties } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiText, useEuiTheme } from '@elastic/eui'; import { JourneyStep } from '../../../../../../common/runtime_types'; -import { JourneyStepScreenshotContainer } from './journey_step_screenshot_container'; +import { JourneyStepScreenshotContainer } from '../screenshot/journey_step_screenshot_container'; import { getTextColorForMonitorStatus, parseBadgeStatus } from './status_badge'; interface Props { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_image.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_image.tsx new file mode 100644 index 0000000000000..57295b3e1daf2 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_image.tsx @@ -0,0 +1,102 @@ +/* + * 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 { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import { + useEuiTheme, + useEuiBackgroundColor, + EuiIcon, + EuiLoadingContent, + EuiText, +} from '@elastic/eui'; + +export const IMAGE_WIDTH = 360; +export const IMAGE_HEIGHT = 203; + +export const imageStyle = css` + padding: 0; + margin: auto; + width: ${IMAGE_WIDTH}px; + height: ${IMAGE_HEIGHT}px; + object-fit: contain; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; +`; + +export const EmptyImage = ({ + isLoading = false, + width = IMAGE_WIDTH, + height = IMAGE_HEIGHT, +}: { + isLoading: boolean; + width?: number; + height?: number; +}) => { + const { euiTheme } = useEuiTheme(); + + return ( +
+ {isLoading ? ( + + ) : ( +
+ + {IMAGE_UN_AVAILABLE} +
+ )} +
+ ); +}; + +export const SCREENSHOT_LOADING_ARIA_LABEL = i18n.translate( + 'xpack.synthetics.monitor.step.screenshot.ariaLabel', + { + defaultMessage: 'Step screenshot is being loaded.', + } +); + +export const SCREENSHOT_NOT_AVAILABLE = i18n.translate( + 'xpack.synthetics.monitor.step.screenshot.notAvailable', + { + defaultMessage: 'Step screenshot is not available.', + } +); + +export const IMAGE_UN_AVAILABLE = i18n.translate( + 'xpack.synthetics.monitor.step.screenshot.unAvailable', + { + defaultMessage: 'Image unavailable', + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot.tsx new file mode 100644 index 0000000000000..9199c3f7db7e8 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { useJourneySteps } from '../../monitor_details/hooks/use_journey_steps'; +import { parseBadgeStatus } from '../monitor_test_result/status_badge'; +import { JourneyStepScreenshotContainer } from './journey_step_screenshot_container'; + +export const JourneyScreenshot = ({ checkGroupId }: { checkGroupId: string }) => { + const { loading: stepsLoading, stepEnds } = useJourneySteps(checkGroupId); + const stepLabels = stepEnds.map((stepEnd) => stepEnd?.synthetics?.step?.name ?? ''); + + const lastSignificantStep = useMemo(() => { + const copy = [...stepEnds]; + // Sort desc by timestamp + copy.sort( + (stepA, stepB) => + Number(new Date(stepB['@timestamp'])) - Number(new Date(stepA['@timestamp'])) + ); + return copy.find( + (stepEnd) => parseBadgeStatus(stepEnd?.synthetics?.step?.status ?? 'skipped') !== 'skipped' + ); + }, [stepEnds]); + + return ( + + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.test.tsx similarity index 98% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.test.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.test.tsx index 4c95fade23d1a..61219b57ae86d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.test.tsx @@ -14,7 +14,7 @@ import * as observabilityPublic from '@kbn/observability-plugin/public'; import { getShortTimeStamp } from '../../../utils/monitor_test_result/timestamp'; import '../../../utils/testing/__mocks__/use_composite_image.mock'; import { mockRef } from '../../../utils/testing/__mocks__/screenshot_ref.mock'; -import * as retrieveHooks from './use_retrieve_step_image'; +import * as retrieveHooks from '../monitor_test_result/use_retrieve_step_image'; jest.mock('@kbn/observability-plugin/public'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.tsx similarity index 89% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.tsx index 6c2653f7efaa6..24045d5ac458a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.tsx @@ -10,6 +10,7 @@ import { css } from '@emotion/react'; import useIntersection from 'react-use/lib/useIntersection'; import { i18n } from '@kbn/i18n'; +import { EmptyImage } from './empty_image'; import { isScreenshotImageBlob, isScreenshotRef, @@ -18,10 +19,10 @@ import { import { SyntheticsSettingsContext } from '../../../contexts'; -import { useRetrieveStepImage } from './use_retrieve_step_image'; -import { ScreenshotOverlayFooter } from './screenshot_overlay_footer'; -import { JourneyStepImagePopover } from './journey_step_image_popover'; -import { EmptyThumbnail } from './empty_thumbnail'; +import { useRetrieveStepImage } from '../monitor_test_result/use_retrieve_step_image'; +import { ScreenshotOverlayFooter } from '../monitor_test_result/screenshot_overlay_footer'; +import { JourneyStepImagePopover } from '../monitor_test_result/journey_step_image_popover'; +import { EmptyThumbnail } from '../monitor_test_result/empty_thumbnail'; interface Props { checkGroup?: string; @@ -29,6 +30,7 @@ interface Props { stepStatus?: string; initialStepNo?: number; allStepsLoaded?: boolean; + asThumbnail?: boolean; retryFetchOnRevisit?: boolean; // Set to `true` fro "Run Once" / "Test Now" modes } @@ -39,6 +41,7 @@ export const JourneyStepScreenshotContainer = ({ allStepsLoaded, initialStepNo = 1, retryFetchOnRevisit = false, + asThumbnail = true, }: Props) => { const [stepNumber, setStepNumber] = useState(initialStepNo); const [isImagePopoverOpen, setIsImagePopoverOpen] = useState(false); @@ -135,9 +138,12 @@ export const JourneyStepScreenshotContainer = ({ isImagePopoverOpen={isImagePopoverOpen} isStepFailed={stepStatus === 'failed'} isLoading={Boolean(loading)} + asThumbnail={asThumbnail} /> - ) : ( + ) : asThumbnail ? ( + ) : ( + )} ); 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 a6d2070d0e96a..13f3d24594707 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 @@ -6,10 +6,14 @@ */ import { useFetcher } from '@kbn/observability-plugin/public'; -import { SyntheticsJourneyApiResponse } from '../../../../../../common/runtime_types'; +import { useParams } from 'react-router-dom'; +import { isStepEnd } from '../../common/monitor_test_result/browser_steps_list'; +import { JourneyStep, SyntheticsJourneyApiResponse } from '../../../../../../common/runtime_types'; import { fetchJourneySteps } from '../../../state'; export const useJourneySteps = (checkGroup: string | undefined) => { + const { stepIndex } = useParams<{ stepIndex: string }>(); + const { data, loading } = useFetcher(() => { if (!checkGroup) { return Promise.resolve(null); @@ -18,5 +22,24 @@ export const useJourneySteps = (checkGroup: string | undefined) => { return fetchJourneySteps({ checkGroup }); }, [checkGroup]); - return { data: data as SyntheticsJourneyApiResponse, loading: loading ?? false }; + const isFailed = + data?.steps.some( + (step) => + step.synthetics?.step?.status === 'failed' || step.synthetics?.step?.status === 'skipped' + ) ?? false; + + const stepEnds: JourneyStep[] = (data?.steps ?? []).filter(isStepEnd); + + const stepLabels = stepEnds.map((stepEnd) => stepEnd?.synthetics?.step?.name ?? ''); + + return { + data: data as SyntheticsJourneyApiResponse, + loading: loading ?? false, + isFailed, + stepEnds, + stepLabels, + currentStep: stepIndex + ? data?.steps.find((step) => step.synthetics?.step?.index === Number(stepIndex)) + : undefined, + }; }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_ten_test_runs.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_ten_test_runs.tsx index 2e944a3e3570f..b5a14b59994a1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_ten_test_runs.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_ten_test_runs.tsx @@ -23,7 +23,7 @@ import { import { Criteria } from '@elastic/eui/src/components/basic_table/basic_table'; import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; -import { ConfigKey, DataStream, JourneyStep, Ping } from '../../../../../../common/runtime_types'; +import { ConfigKey, DataStream, Ping } from '../../../../../../common/runtime_types'; import { formatTestDuration, formatTestRunAt, @@ -33,11 +33,9 @@ import { useSyntheticsSettingsContext } from '../../../contexts/synthetics_setti import { sortPings } from '../../../utils/monitor_test_result/sort_pings'; import { selectPingsLoading, selectMonitorRecentPings, selectPingsError } from '../../../state'; import { parseBadgeStatus, StatusBadge } from '../../common/monitor_test_result/status_badge'; -import { isStepEnd } from '../../common/monitor_test_result/browser_steps_list'; -import { JourneyStepScreenshotContainer } from '../../common/monitor_test_result/journey_step_screenshot_container'; import { useSelectedMonitor } from '../hooks/use_selected_monitor'; -import { useJourneySteps } from '../hooks/use_journey_steps'; +import { JourneyScreenshot } from '../../common/screenshot/journey_screenshot'; type SortableField = 'timestamp' | 'monitor.status' | 'monitor.duration.us'; @@ -166,35 +164,6 @@ export const LastTenTestRuns = () => { ); }; -export const JourneyScreenshot = ({ checkGroupId }: { checkGroupId: string }) => { - const { data: stepsData, loading: stepsLoading } = useJourneySteps(checkGroupId); - const stepEnds: JourneyStep[] = (stepsData?.steps ?? []).filter(isStepEnd); - const stepLabels = stepEnds.map((stepEnd) => stepEnd?.synthetics?.step?.name ?? ''); - - const lastSignificantStep = useMemo(() => { - const copy = [...stepEnds]; - // Sort desc by timestamp - copy.sort( - (stepA, stepB) => - Number(new Date(stepB['@timestamp'])) - Number(new Date(stepA['@timestamp'])) - ); - return copy.find( - (stepEnd) => parseBadgeStatus(stepEnd?.synthetics?.step?.status ?? 'skipped') !== 'skipped' - ); - }, [stepEnds]); - - return ( - - ); -}; - const TestDetailsLink = ({ isBrowserMonitor, timestamp, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/last_successful_screenshot.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/last_successful_screenshot.tsx similarity index 55% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/last_successful_screenshot.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/last_successful_screenshot.tsx index b6acf3caba169..6bef50955743b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/last_successful_screenshot.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/last_successful_screenshot.tsx @@ -9,14 +9,15 @@ import { useFetcher } from '@kbn/observability-plugin/public'; import { EuiSpacer } from '@elastic/eui'; import React from 'react'; import { useParams } from 'react-router-dom'; -import { fetchLastSuccessfulCheck } from '../../../state'; -import { StepScreenshotDisplay } from './screenshot/step_screenshot_display'; -import { JourneyStep, Ping } from '../../../../../../common/runtime_types'; +import { fetchLastSuccessfulCheck } from '../../../../state'; +import { JourneyStep } from '../../../../../../../common/runtime_types'; +import { EmptyImage } from '../../../common/screenshot/empty_image'; +import { JourneyStepScreenshotContainer } from '../../../common/screenshot/journey_step_screenshot_container'; export const LastSuccessfulScreenshot = ({ step }: { step: JourneyStep }) => { const { stepIndex } = useParams<{ checkGroupId: string; stepIndex: string }>(); - const { data } = useFetcher(() => { + const { data, loading } = useFetcher(() => { return fetchLastSuccessfulCheck({ timestamp: step['@timestamp'], monitorId: step.monitor.id, @@ -25,21 +26,19 @@ export const LastSuccessfulScreenshot = ({ step }: { step: JourneyStep }) => { }); }, [step._id, step['@timestamp']]); - const lastSuccessfulCheck: Ping | undefined = data; - - if (!lastSuccessfulCheck) { - return null; + if (loading || !data) { + return ; } return ( <> - diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.test.tsx deleted file mode 100644 index 7a275527de3c6..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.test.tsx +++ /dev/null @@ -1,91 +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 { StepScreenshotDisplay } from './step_screenshot_display'; -import * as observabilityPublic from '@kbn/observability-plugin/public'; -import { mockRef } from '../../../../utils/testing/__mocks__/screenshot_ref.mock'; -import { render } from '../../../../utils/testing'; -import '../../../../utils/testing/__mocks__/use_composite_image.mock'; -jest.mock('@kbn/observability-plugin/public'); - -jest.mock('react-use/lib/useIntersection', () => () => ({ - isIntersecting: true, -})); - -describe('StepScreenshotDisplayProps', () => { - beforeAll(() => { - jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ - data: null, - status: observabilityPublic.FETCH_STATUS.SUCCESS, - refetch: () => {}, - }); - }); - - afterAll(() => { - (observabilityPublic.useFetcher as any).mockClear(); - }); - it('displays screenshot thumbnail when present', () => { - const { getByAltText } = render( - - ); - - expect(getByAltText('Screenshot for step with name "STEP_NAME"')).toBeInTheDocument(); - }); - - it('uses alternative text when step name not available', () => { - const { getByAltText } = render( - - ); - - expect(getByAltText('Screenshot')).toBeInTheDocument(); - }); - - it('displays No Image message when screenshot does not exist', () => { - const { getByTestId } = render( - - ); - expect(getByTestId('stepScreenshotImageUnavailable')).toBeInTheDocument(); - }); - - it('displays screenshot thumbnail for ref', () => { - jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ - status: observabilityPublic.FETCH_STATUS.SUCCESS, - data: { ...mockRef }, - refetch: () => null, - }); - - const { getByAltText } = render( - - ); - - expect(getByAltText('Screenshot for step with name "STEP_NAME"')).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.tsx deleted file mode 100644 index 1dbfe9595cc44..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/step_screenshot_display.tsx +++ /dev/null @@ -1,197 +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, - EuiIcon, - EuiImage, - EuiLoadingSpinner, - EuiText, -} from '@elastic/eui'; -import styled from 'styled-components'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useEffect, useMemo, useRef, useState, FC } from 'react'; -import useIntersection from 'react-use/lib/useIntersection'; -import { useFetcher } from '@kbn/observability-plugin/public'; -import { getJourneyScreenshot } from '../../../../state'; -import { useCompositeImage } from '../../../../hooks'; -import { - useSyntheticsRefreshContext, - useSyntheticsSettingsContext, - useSyntheticsThemeContext, -} from '../../../../contexts'; -import { - isScreenshotRef as isAScreenshotRef, - ScreenshotRefImageData, -} from '../../../../../../../common/runtime_types'; - -interface StepScreenshotDisplayProps { - isFullScreenshot: boolean; - isScreenshotRef: boolean; - checkGroup?: string; - stepIndex?: number; - stepName?: string; - lazyLoad?: boolean; -} - -const IMAGE_MAX_WIDTH = 640; - -const StepImage = styled(EuiImage)` - &&& { - figcaption { - display: none; - } - objectFit: 'cover', - objectPosition: 'center top', - } -`; - -const BaseStepImage = ({ - stepIndex, - stepName, - url, -}: Pick & { url?: string }) => { - if (!url) return ; - return ( - - ); -}; - -type ComposedStepImageProps = Pick & { - url: string | undefined; - imgRef: ScreenshotRefImageData; - setUrl: React.Dispatch; -}; - -const ComposedStepImage = ({ - stepIndex, - stepName, - url, - imgRef, - setUrl, -}: ComposedStepImageProps) => { - useCompositeImage(imgRef, setUrl, url); - if (!url) return ; - return ; -}; - -export const StepScreenshotDisplay: FC = ({ - checkGroup, - isFullScreenshot: isScreenshotBlob, - isScreenshotRef, - stepIndex, - stepName, - lazyLoad = true, -}) => { - const containerRef = useRef(null); - const { - colors: { lightestShade: pageBackground }, - } = useSyntheticsThemeContext(); - - const { basePath } = useSyntheticsSettingsContext(); - - const intersection = useIntersection(containerRef, { - root: null, - rootMargin: '0px', - threshold: 1, - }); - const { lastRefresh } = useSyntheticsRefreshContext(); - - const [hasIntersected, setHasIntersected] = useState(false); - const isIntersecting = intersection?.isIntersecting; - useEffect(() => { - if (hasIntersected === false && isIntersecting === true) { - setHasIntersected(true); - } - }, [hasIntersected, isIntersecting, setHasIntersected]); - - const imgSrc = basePath + `/internal/uptime/journey/screenshot/${checkGroup}/${stepIndex}`; - - // When loading a legacy screenshot, set `url` to full-size screenshot path. - // Otherwise, we first need to composite the image. - const [url, setUrl] = useState(isScreenshotBlob ? imgSrc : undefined); - - // when the image is a composite, we need to fetch the data since we cannot specify a blob URL - const { data: screenshotRef } = useFetcher(() => { - if (isScreenshotRef) { - return getJourneyScreenshot(imgSrc); - } - }, [basePath, checkGroup, imgSrc, stepIndex, isScreenshotRef, lastRefresh]); - - const refDimensions = useMemo(() => { - if (isAScreenshotRef(screenshotRef)) { - const { height, width } = screenshotRef.ref.screenshotRef.screenshot_ref; - return { height, width }; - } - }, [screenshotRef]); - - const shouldRenderImage = hasIntersected || !lazyLoad; - return ( -
- {shouldRenderImage && isScreenshotBlob && ( - - )} - {shouldRenderImage && isScreenshotRef && isAScreenshotRef(screenshotRef) && ( - - )} - {!isScreenshotBlob && !isScreenshotRef && ( - - - - - - - - - - - - - )} -
- ); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/step_image.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/step_image.tsx index 266f929efdc31..a08ba79444ccb 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/step_image.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/step_image.tsx @@ -8,11 +8,21 @@ import React, { useState } from 'react'; import { EuiButtonGroup, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { LastSuccessfulScreenshot } from './last_successful_screenshot'; +import { LastSuccessfulScreenshot } from './screenshot/last_successful_screenshot'; import { JourneyStep } from '../../../../../../common/runtime_types'; -import { JourneyScreenshot } from '../../monitor_details/monitor_summary/last_ten_test_runs'; +import { JourneyStepScreenshotContainer } from '../../common/screenshot/journey_step_screenshot_container'; -export const StepImage = ({ ping }: { ping: JourneyStep }) => { +export const StepImage = ({ + step, + ping, + isFailed, + stepLabels, +}: { + ping: JourneyStep; + step: JourneyStep; + isFailed?: boolean; + stepLabels?: string[]; +}) => { const toggleButtons = [ { id: `received`, @@ -38,13 +48,21 @@ export const StepImage = ({ ping }: { ping: JourneyStep }) => {
{idSelected === 'received' ? ( - + ) : ( )} - {ping.monitor.status === 'down' && ( + {isFailed && ( { useTrackPageview({ app: 'synthetics', path: 'stepDetail' }); useTrackPageview({ app: 'synthetics', path: 'stepDetail', delay: 15000 }); - const { data, loading } = useJourneySteps(checkGroupId); + const { data, loading, isFailed, currentStep, stepLabels } = useJourneySteps(checkGroupId); useStepDetailsBreadcrumbs([{ text: data?.details?.journey.monitor.name ?? '' }]); @@ -49,7 +49,14 @@ export const StepDetailPage = () => { - {data?.details?.journey && } + {data?.details?.journey && currentStep && ( + + )} From 8da95453b3870696325c694fff15c57a556d1ce8 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Thu, 20 Oct 2022 17:15:11 +0200 Subject: [PATCH 06/12] tr test --- x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx b/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx index fc365abd823b9..a702ddab6b564 100644 --- a/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx +++ b/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx @@ -5,6 +5,7 @@ * 2.0. */ import { expect, Page } from '@elastic/synthetics'; +import { TIMEOUT_60_SEC } from '@kbn/observability-plugin/e2e/utils'; import { FormMonitorType } from '../../common/runtime_types/monitor_management'; import { loginPageProvider } from './login'; import { utilsPageProvider } from './utils'; @@ -82,7 +83,7 @@ export function syntheticsAppPageProvider({ page, kibanaUrl }: { page: Page; kib async navigateToEditMonitor() { await this.clickByTestSubj('syntheticsMonitorListActions'); - await page.click('text=Edit'); + await page.click('text=Edit', TIMEOUT_60_SEC); await this.findByText('Edit monitor'); }, From 06f800c445f0df7378d229415ca8762e2298aff1 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Mon, 24 Oct 2022 16:44:46 +0200 Subject: [PATCH 07/12] fix default mode --- .../journey_step_image_popover.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx index 732e74fb98521..fdfeff06cfa0a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx @@ -34,7 +34,7 @@ const ScreenshotThumbnail: React.FC { return imageData ? ( { if (imgSrc) { return ( @@ -149,7 +149,7 @@ export const JourneyStepImagePopover: React.FC = ({ isImagePopoverOpen, isStepFailed, isLoading, - asThumbnail, + asThumbnail = true, }) => { const { euiTheme } = useEuiTheme(); @@ -212,11 +212,7 @@ export const JourneyStepImagePopover: React.FC = ({ `} /> ) : asThumbnail ? ( - + ) : ( )} From afbcd02e271deb10affe6dc8b1b3f7fa6a69fb40 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Mon, 24 Oct 2022 16:53:18 +0200 Subject: [PATCH 08/12] PR feedback fix --- .../common/monitor_test_result/journey_step_image_popover.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx index fdfeff06cfa0a..dda8c5343eb08 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx @@ -71,6 +71,7 @@ const RecomposedScreenshotImage: React.FC< setImageData, isStepFailed, isLoading, + asThumbnail, }) => { // initially an undefined URL value is passed to the image display, and a loading spinner is rendered. // `useCompositeImage` will call `setImageData` when the image is composited, and the updated `imageData` will display. @@ -83,6 +84,7 @@ const RecomposedScreenshotImage: React.FC< imageData={imageData} isStepFailed={isStepFailed} isLoading={isLoading} + asThumbnail={asThumbnail} /> ); }; @@ -135,6 +137,7 @@ const JourneyStepImage: React.FC< setImageData={setImageData} isStepFailed={isStepFailed} isLoading={isLoading} + asThumbnail={asThumbnail} /> ); } From 80e909ab541ddc8b935eb260a3e10a0cd346646d Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Mon, 24 Oct 2022 18:16:32 +0200 Subject: [PATCH 09/12] test --- x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx b/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx index a702ddab6b564..66d086fcea9d7 100644 --- a/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx +++ b/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx @@ -5,7 +5,6 @@ * 2.0. */ import { expect, Page } from '@elastic/synthetics'; -import { TIMEOUT_60_SEC } from '@kbn/observability-plugin/e2e/utils'; import { FormMonitorType } from '../../common/runtime_types/monitor_management'; import { loginPageProvider } from './login'; import { utilsPageProvider } from './utils'; @@ -83,7 +82,7 @@ export function syntheticsAppPageProvider({ page, kibanaUrl }: { page: Page; kib async navigateToEditMonitor() { await this.clickByTestSubj('syntheticsMonitorListActions'); - await page.click('text=Edit', TIMEOUT_60_SEC); + await page.click('text=Edit', { timeout: 2 * 60 * 1000 }); await this.findByText('Edit monitor'); }, From 5ebc15c3053a728950b38b7c83b0cf04d2e2a3f0 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Tue, 25 Oct 2022 10:07:41 +0200 Subject: [PATCH 10/12] update test --- .../synthetics/e2e/journeys/data_view_permissions.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts b/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts index 5aa7ee96f25b1..648337218979c 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts @@ -48,10 +48,10 @@ journey('DataViewPermissions', async ({ page, params }) => { step('Click explore data button', async () => { await page.click(byTestId('uptimeExploreDataButton')); await waitForLoadingToFinish({ page }); - await page.waitForSelector(`text=${permissionError}`, TIMEOUT_60_SEC); - expect(await page.$(`text=${permissionError}`)).toBeTruthy(); }); -}); -const permissionError = - "Unable to create Data View. You don't have the required permission, please contact your admin."; + step('it renders for viewer user as well', async () => { + await page.waitForSelector(`text=browser`, TIMEOUT_60_SEC); + expect(await page.$(`text=Monitor duration`)).toBeTruthy(); + }); +}); From d393733442a69951494b4bea2250bfa3aebb30e5 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Mon, 31 Oct 2022 11:50:16 +0100 Subject: [PATCH 11/12] fix pr feedback --- .../components/screenshot/last_successful_screenshot.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/last_successful_screenshot.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/last_successful_screenshot.tsx index 6bef50955743b..3874d29197d3c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/last_successful_screenshot.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/screenshot/last_successful_screenshot.tsx @@ -39,6 +39,7 @@ export const LastSuccessfulScreenshot = ({ step }: { step: JourneyStep }) => { allStepsLoaded={true} stepLabels={[]} retryFetchOnRevisit={false} + asThumbnail={false} /> From bf52e2112ef153a53ebfc0abe20d30811e8d6e57 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 31 Oct 2022 14:46:21 +0000 Subject: [PATCH 12/12] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../monitor_details/monitor_summary/test_runs_table.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx index ff4d1990fd307..891d1694de4bc 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx @@ -37,7 +37,6 @@ import { parseBadgeStatus, StatusBadge } from '../../common/monitor_test_result/ import { useKibanaDateFormat } from '../../../../../hooks/use_kibana_date_format'; import { useSelectedMonitor } from '../hooks/use_selected_monitor'; import { useMonitorPings } from '../hooks/use_monitor_pings'; -import { useJourneySteps } from '../hooks/use_journey_steps'; import { JourneyScreenshot } from '../../common/screenshot/journey_screenshot'; type SortableField = 'timestamp' | 'monitor.status' | 'monitor.duration.us';