From 6ff08d343a191c8cbe0f5cf8c618cd9674f026a3 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Tue, 17 Jan 2023 12:17:46 +0100 Subject: [PATCH 1/7] added e2e --- .../common/constants/synthetics/rest_api.ts | 1 + .../common/runtime_types/ping/synthetics.ts | 4 + .../e2e/journeys/synthetics/index.ts | 1 + .../services/synthetics_services.ts | 11 +- .../synthetics/test_run_details.journey.ts | 64 ++++++++ .../common/components/monitor_status.tsx | 39 +++-- .../common/links/error_details_link.tsx | 74 +++++++++ .../common/links/step_details_link.tsx | 17 +- .../monitor_test_result/status_badge.tsx | 12 +- .../monitor_errors/errors_list.tsx | 27 ++-- .../monitor_summary/last_test_run.tsx | 14 +- .../test_run_details/components/step_info.tsx | 93 +++++++++++ .../step_screenshot_details.tsx | 7 +- .../test_run_details/test_run_details.tsx | 4 +- .../synthetics/state/browser_journey/api.ts | 4 +- .../server/queries/get_journey_details.ts | 150 ++++++++++++++++++ .../plugins/synthetics/server/routes/index.ts | 2 + .../server/routes/pings/journeys.ts | 80 ++++++++++ 18 files changed, 554 insertions(+), 50 deletions(-) create mode 100644 x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/error_details_link.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_info.tsx create mode 100644 x-pack/plugins/synthetics/server/queries/get_journey_details.ts create mode 100644 x-pack/plugins/synthetics/server/routes/pings/journeys.ts diff --git a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts index 4e41dd2190da3..d4a770f574fdd 100644 --- a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts +++ b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts @@ -14,4 +14,5 @@ export enum SYNTHETICS_API_URLS { PARAMS = `/synthetics/params`, SYNC_GLOBAL_PARAMS = `/synthetics/sync_global_params`, ENABLE_DEFAULT_ALERTING = `/synthetics/enable_default_alerting`, + JOURNEY = `/internal/synthetics/journey/{checkGroup}`, } diff --git a/x-pack/plugins/synthetics/common/runtime_types/ping/synthetics.ts b/x-pack/plugins/synthetics/common/runtime_types/ping/synthetics.ts index ae5dd0046200d..10fd7ecc6d2fe 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/ping/synthetics.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/ping/synthetics.ts @@ -7,6 +7,7 @@ import { isRight } from 'fp-ts/lib/Either'; import * as t from 'io-ts'; +import { ErrorStateCodec } from './error_state'; /** * This type has some overlap with the Ping type, but it helps avoid runtime type @@ -254,6 +255,9 @@ export const SyntheticsJourneyApiResponseType = t.intersection([ timestamp: t.string, checkGroup: t.string, }), + summary: t.type({ + state: ErrorStateCodec, + }), }), ]), t.null, diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts index 4b4869e02877f..c7232fefb6efd 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts @@ -21,3 +21,4 @@ export * from './alert_rules/default_status_alert.journey'; export * from './test_now_mode.journey'; export * from './data_retention.journey'; export * from './monitor_details_page/monitor_summary.journey'; +export * from './test_run_details.journey'; diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/synthetics_services.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/synthetics_services.ts index 67fb0d83c7f58..44b05520f5de2 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/synthetics_services.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/synthetics_services.ts @@ -35,7 +35,11 @@ export class SyntheticsServices { } } - async addTestMonitor(name: string, data: Record = { type: 'browser' }) { + async addTestMonitor( + name: string, + data: Record = { type: 'browser' }, + configId?: string + ) { const testData = { alert: { status: { enabled: true } }, locations: [{ id: 'us_central', isServiceManaged: true }], @@ -45,7 +49,10 @@ export class SyntheticsServices { }; try { const response = await axios.post( - this.kibanaUrl + '/internal/uptime/service/monitors', + this.kibanaUrl + + (configId + ? `/internal/uptime/service/monitors?id=${configId}` + : `/internal/uptime/service/monitors`), testData, { auth: { username: 'elastic', password: 'changeme' }, diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts new file mode 100644 index 0000000000000..3f8df4a46cbbd --- /dev/null +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts @@ -0,0 +1,64 @@ +/* + * 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 { journey, step, before, after } from '@elastic/synthetics'; +import { byTestId } from '@kbn/ux-plugin/e2e/journeys/utils'; +import { recordVideo } from '@kbn/observability-plugin/e2e/record_video'; +import { syntheticsAppPageProvider } from '../../page_objects/synthetics/synthetics_app'; +import { SyntheticsServices } from './services/synthetics_services'; + +journey(`TestRunDetailsPage`, async ({ page, params }) => { + recordVideo(page); + + page.setDefaultTimeout(60 * 1000); + const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl }); + + const services = new SyntheticsServices(params); + + before(async () => { + await services.cleaUp(); + await services.enableMonitorManagedViaApi(); + await services.addTestMonitor( + 'https://www.google.com', + { + type: 'browser', + urls: 'https://www.google.com', + custom_heartbeat_id: 'a47bfc4e-361a-4eb0-83f3-b5bb68781b5b', + locations: [ + { id: 'us_central', label: 'North America - US Central', isServiceManaged: true }, + ], + }, + 'a47bfc4e-361a-4eb0-83f3-b5bb68781b5b' + ); + }); + + after(async () => { + await services.cleaUp(); + }); + + step('Go to monitor summary page', async () => { + await syntheticsApp.navigateToOverview(true); + }); + + step('Monitor is as up in summary page', async () => { + await page.hover('text=https://www.google.com'); + await page.click('[aria-label="Open actions menu"]'); + await page.click('text=Go to monitor'); + await page.waitForSelector(byTestId('monitorLatestStatusUp')); + await page.click(byTestId('syntheticsMonitorHistoryTab')); + }); + + step('Go to test run page', async () => { + await page.click(byTestId('superDatePickerToggleQuickMenuButton')); + await page.click('text=Last 1 year'); + await page.click(byTestId('row-ab240846-8d22-11ed-8fac-52bb19a2321e')); + + await page.waitForSelector('text=Test run details'); + await page.waitForSelector('text=Go to https://www.google.com'); + await page.waitForSelector('text=After 2.1 s'); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_status.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_status.tsx index 271f748c471ab..13ef6cd63509f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_status.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_status.tsx @@ -9,20 +9,16 @@ import { EuiBadge, EuiDescriptionList, EuiLoadingSpinner } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { EncryptedSyntheticsMonitor } from '../../../../../../common/runtime_types'; -export const MonitorStatus = ({ - loading, - monitor, +const BadgeStatus = ({ status, - compressed = true, + loading, + isBrowserType, }: { - loading?: boolean; - compressed?: boolean; - monitor: EncryptedSyntheticsMonitor; status?: string; + loading?: boolean; + isBrowserType: boolean; }) => { - const isBrowserType = monitor.type === 'browser'; - - const badge = loading ? ( + return loading ? ( ) : !status ? ( @@ -37,12 +33,33 @@ export const MonitorStatus = ({ {isBrowserType ? FAILED_LABEL : DOWN_LABEL} ); +}; + +export const MonitorStatus = ({ + loading, + monitor, + status, + compressed = true, +}: { + loading?: boolean; + compressed?: boolean; + monitor: EncryptedSyntheticsMonitor; + status?: string; +}) => { + const isBrowserType = monitor.type === 'browser'; return ( + ), + }, + ]} /> ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/error_details_link.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/error_details_link.tsx new file mode 100644 index 0000000000000..342608865a50a --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/links/error_details_link.tsx @@ -0,0 +1,74 @@ +/* + * 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 { EuiLink, EuiButtonEmpty } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { useSelectedLocation } from '../../monitor_details/hooks/use_selected_location'; +import { useSyntheticsSettingsContext } from '../../../contexts'; +import { getErrorDetailsUrl } from '../../monitor_details/monitor_errors/errors_list'; + +export const ErrorDetailsLink = ({ + stateId, + configId, + label, +}: { + configId: string; + stateId: string; + label: string; +}) => { + const { basePath } = useSyntheticsSettingsContext(); + const selectedLocation = useSelectedLocation(); + + return ( + + {label ?? VIEW_DETAILS} + + ); +}; + +export const ErrorDetailsButton = ({ + stateId, + configId, + label, +}: { + configId: string; + stateId: string; + label?: string; +}) => { + const { basePath } = useSyntheticsSettingsContext(); + const selectedLocation = useSelectedLocation(); + + if (!selectedLocation) return null; + + return ( + + {label ?? VIEW_DETAILS} + + ); +}; + +const VIEW_DETAILS = i18n.translate('xpack.synthetics.monitor.step.viewErrorDetails', { + defaultMessage: 'View error details', +}); 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 f95984eafa08d..2418afb7e37a2 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 @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiButtonIcon } from '@elastic/eui'; +import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; import { useSelectedLocation } from '../../monitor_details/hooks/use_selected_location'; @@ -15,14 +15,29 @@ export const StepDetailsLinkIcon = ({ stepIndex, checkGroup, configId, + asButton, + label, }: { checkGroup: string; + label?: string; configId: string; stepIndex?: number; + asButton?: boolean; }) => { const { basePath } = useSyntheticsSettingsContext(); const selectedLocation = useSelectedLocation(); + if (asButton) { + return ( + + {label ?? VIEW_DETAILS} + + ); + } + return ( { + if (status === 'unknown') { + return ; + } + return ( {status === 'succeeded' ? COMPLETE_LABEL : status === 'failed' ? FAILED_LABEL : SKIPPED_LABEL} @@ -18,7 +22,7 @@ export const StatusBadge = ({ status }: { status: MonitorStatus }) => { ); }; -export const parseBadgeStatus = (status: string) => { +export const parseBadgeStatus = (status?: string) => { switch (status) { case 'succeeded': case 'success': @@ -32,7 +36,7 @@ export const parseBadgeStatus = (status: string) => { case 'skipped': return 'skipped'; default: - return 'skipped'; + return 'unknown'; } }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx index 48f6ea76b12ed..1d7af11262287 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx @@ -7,8 +7,9 @@ import { i18n } from '@kbn/i18n'; import React, { MouseEvent, useMemo, useState } from 'react'; -import { EuiBasicTable, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiBasicTable, EuiSpacer, EuiText } from '@elastic/eui'; import { useHistory, useParams } from 'react-router-dom'; +import { ErrorDetailsLink } from '../../common/links/error_details_link'; import { useSelectedLocation } from '../hooks/use_selected_location'; import { useKibanaDateFormat } from '../../../../../hooks/use_kibana_date_format'; import { Ping } from '../../../../../../common/runtime_types'; @@ -18,7 +19,6 @@ import { formatTestRunAt, } from '../../../utils/monitor_test_result/test_time_formats'; import { useMonitorErrors } from '../hooks/use_monitor_errors'; -import { useSyntheticsSettingsContext } from '../../../contexts'; export const ErrorsList = () => { const [pageIndex, setPageIndex] = useState(0); @@ -42,8 +42,6 @@ export const ErrorsList = () => { const isBrowserType = errorStates[0]?.monitor.type === 'browser'; - const { basePath } = useSyntheticsSettingsContext(); - const history = useHistory(); const format = useKibanaDateFormat(); @@ -57,16 +55,11 @@ export const ErrorsList = () => { sortable: true, render: (value: string, item: Ping) => { return ( - - {formatTestRunAt(item.state!.started_at, format)} - + ); }, }, @@ -152,16 +145,16 @@ export const ErrorsList = () => { export const getErrorDetailsUrl = ({ basePath, - monitorId, + configId, stateId, locationId, }: { stateId: string; basePath: string; - monitorId: string; + configId: string; locationId: string; }) => { - return `${basePath}/app/synthetics/monitor/${monitorId}/errors/${stateId}?locationId=${locationId}`; + return `${basePath}/app/synthetics/monitor/${configId}/errors/${stateId}?locationId=${locationId}`; }; const ERRORS_LIST_LABEL = i18n.translate('xpack.synthetics.errorsList.label', { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx index b3aa2e129dc81..150c68624c5ef 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React from 'react'; import { EuiButton, EuiButtonEmpty, @@ -31,14 +31,13 @@ import { Ping, SyntheticsJourneyApiResponse, } from '../../../../../../common/runtime_types'; -import { formatTestRunAt } from '../../../utils/monitor_test_result/test_time_formats'; +import { useFormatTestRunAt } from '../../../utils/monitor_test_result/test_time_formats'; import { useSyntheticsRefreshContext, useSyntheticsSettingsContext } from '../../../contexts'; import { BrowserStepsList } from '../../common/monitor_test_result/browser_steps_list'; import { SinglePingResult } from '../../common/monitor_test_result/single_ping_result'; import { parseBadgeStatus, StatusBadge } from '../../common/monitor_test_result/status_badge'; -import { useKibanaDateFormat } from '../../../../../hooks/use_kibana_date_format'; import { useJourneySteps } from '../hooks/use_journey_steps'; import { useSelectedMonitor } from '../hooks/use_selected_monitor'; import { useMonitorLatestPing } from '../hooks/use_monitor_latest_ping'; @@ -107,7 +106,7 @@ export const LastTestRunComponent = ({ color="danger" href={getErrorDetailsUrl({ basePath, - monitorId: monitor?.id!, + configId: monitor?.id!, locationId: selectedLocation!.id, stateId: latestPing.state?.id!, })} @@ -150,12 +149,7 @@ const PanelHeader = ({ const { monitorId } = useParams<{ monitorId: string }>(); - const format = useKibanaDateFormat(); - - const lastRunTimestamp = useMemo( - () => (latestPing?.timestamp ? formatTestRunAt(latestPing?.timestamp, format) : ''), - [latestPing?.timestamp, format] - ); + const lastRunTimestamp = useFormatTestRunAt(latestPing?.timestamp); const isBrowserMonitor = monitor?.[ConfigKey.MONITOR_TYPE] === DataStream.BROWSER; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_info.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_info.tsx new file mode 100644 index 0000000000000..74e5da6a47d6d --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_info.tsx @@ -0,0 +1,93 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiLoadingContent, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useParams } from 'react-router-dom'; +import { parseBadgeStatus, StatusBadge } from '../../common/monitor_test_result/status_badge'; +import { formatTestDuration } from '../../../utils/monitor_test_result/test_time_formats'; +import { ErrorDetailsButton } from '../../common/links/error_details_link'; +import { StepDetailsLinkIcon } from '../../common/links/step_details_link'; +import { JourneyStep } from '../../../../../../common/runtime_types'; + +export const StepMetaInfo = ({ + step, + stepIndex, + stateId, +}: { + step?: JourneyStep; + stepIndex: number; + stateId?: string; +}) => { + const { checkGroupId, monitorId } = useParams<{ checkGroupId: string; monitorId: string }>(); + + if (!step) { + return ( + + + + ); + } + + const isFailed = step.synthetics.step?.status === 'failed'; + + return ( + + +

{STEP_NAME}

+
+ {step?.synthetics.step?.name} + + + + + + + {AFTER_LABEL} + {formatTestDuration(step?.synthetics.step?.duration.us)} + + + + + {isFailed && stateId && ( + + + + )} + + + + +
+ ); +}; + +const STEP_NAME = i18n.translate('xpack.synthetics.testDetails.stepName', { + defaultMessage: 'Step name', +}); + +const AFTER_LABEL = i18n.translate('xpack.synthetics.testDetails.after', { + defaultMessage: 'After ', +}); + +const VIEW_PERFORMANCE = i18n.translate('xpack.synthetics.monitor.step.viewPerformanceBreakdown', { + defaultMessage: 'View performance breakdown', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_screenshot_details.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_screenshot_details.tsx index 1bbf20c7d5ccb..50d799d6b2863 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_screenshot_details.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_screenshot_details.tsx @@ -10,12 +10,15 @@ import React from 'react'; import { useParams } from 'react-router-dom'; import { JourneyStep } from '../../../../../common/runtime_types'; import { JourneyStepScreenshotContainer } from '../common/screenshot/journey_step_screenshot_container'; +import { StepMetaInfo } from './components/step_info'; export const StepScreenshotDetails = ({ stepIndex, step, + stateId, }: { stepIndex: number; + stateId?: string; step?: JourneyStep; }) => { const { checkGroupId } = useParams<{ checkGroupId: string }>(); @@ -23,7 +26,7 @@ export const StepScreenshotDetails = ({ return ( - + - {/* TODO: add image details*/} + ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx index 446857ca9b779..7f1bb3be1ab0a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx @@ -38,6 +38,8 @@ export const TestRunDetails = () => { const { monitorId } = useParams<{ monitorId: string }>(); const selectedLocation = useSelectedLocation(); + const stateId = stepsData?.details?.summary?.state?.id; + return ( <> @@ -72,7 +74,7 @@ export const TestRunDetails = () => { - + diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts index e0a8068e1d460..6759bee3b1fe3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts @@ -17,7 +17,7 @@ import { Ping, PingType, } from '../../../../../common/runtime_types'; -import { API_URLS } from '../../../../../common/constants'; +import { API_URLS, SYNTHETICS_API_URLS } from '../../../../../common/constants'; export interface FetchJourneyStepsParams { checkGroup: string; @@ -34,7 +34,7 @@ export async function fetchBrowserJourney( params: FetchJourneyStepsParams ): Promise { return apiService.get( - API_URLS.JOURNEY.replace('{checkGroup}', params.checkGroup), + SYNTHETICS_API_URLS.JOURNEY.replace('{checkGroup}', params.checkGroup), { syntheticEventTypes: params.syntheticEventTypes }, SyntheticsJourneyApiResponseType ); diff --git a/x-pack/plugins/synthetics/server/queries/get_journey_details.ts b/x-pack/plugins/synthetics/server/queries/get_journey_details.ts new file mode 100644 index 0000000000000..50fc3b1a628fa --- /dev/null +++ b/x-pack/plugins/synthetics/server/queries/get_journey_details.ts @@ -0,0 +1,150 @@ +/* + * 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 { JourneyStep, Ping, SyntheticsJourneyApiResponse } from '../../common/runtime_types'; +import { UMElasticsearchQueryFn } from '../legacy_uptime/lib/adapters'; + +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, + }, + }, + { + terms: { + 'synthetics.type': ['journey/start', 'heartbeat/summary'], + }, + }, + ] as QueryDslQueryContainer[], + }, + }, + size: 2, + }; + + const { body: thisJourney } = await uptimeEsClient.search( + { body: baseParams }, + 'getJourneyDetailsCurrentJourney' + ); + + 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 [previousJourneyPromise, nextJourneyPromise] = await Promise.all([ + uptimeEsClient.search({ body: previousParams }, 'getJourneyDetailsNextJourney'), + uptimeEsClient.search({ body: nextParams }, 'getJourneyDetailsPreviousJourney'), + ]); + + const { body: previousJourneyResult } = previousJourneyPromise; + const { body: nextJourneyResult } = nextJourneyPromise; + + 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; + + const summaryPing = thisJourney.hits.hits.find( + ({ _source: summarySource }) => + (summarySource as Ping).synthetics?.type === 'heartbeat/summary' + ) as { _source: Ping }; + + return { + timestamp: thisJourneySource['@timestamp'], + journey: thisJourneySource, + summary: summaryPing + ? { + state: summaryPing._source.state, + } + : undefined, + 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; + } +}; diff --git a/x-pack/plugins/synthetics/server/routes/index.ts b/x-pack/plugins/synthetics/server/routes/index.ts index a0583bd565848..5312074520ac9 100644 --- a/x-pack/plugins/synthetics/server/routes/index.ts +++ b/x-pack/plugins/synthetics/server/routes/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { createJourneyRoute } from './pings/journeys'; import { updateDefaultAlertingRoute } from './default_alerts/update_default_alert'; import { syncParamsSyntheticsParamsRoute } from './settings/sync_global_params'; import { editSyntheticsParamsRoute } from './settings/edit_param'; @@ -75,6 +76,7 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [ enableDefaultAlertingRoute, getDefaultAlertingRoute, updateDefaultAlertingRoute, + createJourneyRoute, ]; export const syntheticsAppStreamingApiRoutes: SyntheticsStreamingRouteFactory[] = [ diff --git a/x-pack/plugins/synthetics/server/routes/pings/journeys.ts b/x-pack/plugins/synthetics/server/routes/pings/journeys.ts new file mode 100644 index 0000000000000..9ef10fe6bebb2 --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/pings/journeys.ts @@ -0,0 +1,80 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { API_URLS, SYNTHETICS_API_URLS } from '../../../common/constants'; +import { UMServerLibs } from '../../legacy_uptime/uptime_server'; +import { UMRestApiRouteFactory } from '../../legacy_uptime/routes/types'; +import { getJourneyDetails } from '../../queries/get_journey_details'; + +export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ + method: 'GET', + path: SYNTHETICS_API_URLS.JOURNEY, + validate: { + params: schema.object({ + checkGroup: schema.string(), + }), + query: schema.object({ + // provides a filter for the types of synthetic events to include + // when fetching a journey's data + syntheticEventTypes: schema.maybe( + schema.oneOf([schema.arrayOf(schema.string()), schema.string()]) + ), + }), + }, + handler: async ({ uptimeEsClient, request, response }): Promise => { + const { checkGroup } = request.params; + const { syntheticEventTypes } = request.query; + + try { + const [result, details] = await Promise.all([ + await libs.requests.getJourneySteps({ + uptimeEsClient, + checkGroup, + syntheticEventTypes, + }), + await getJourneyDetails({ + uptimeEsClient, + checkGroup, + }), + ]); + + return { + checkGroup, + steps: result, + details, + }; + } catch (e: unknown) { + return response.custom({ statusCode: 500, body: { message: e } }); + } + }, +}); + +export const createJourneyFailedStepsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ + method: 'GET', + path: API_URLS.JOURNEY_FAILED_STEPS, + validate: { + query: schema.object({ + checkGroups: schema.arrayOf(schema.string()), + }), + }, + handler: async ({ uptimeEsClient, request, response }): Promise => { + const { checkGroups } = request.query; + try { + const result = await libs.requests.getJourneyFailedSteps({ + uptimeEsClient, + checkGroups, + }); + return { + checkGroups, + steps: result, + }; + } catch (e) { + return response.customError({ statusCode: 500, body: e }); + } + }, +}); From 7517f7e32b7f08ba25d8e4d12994e52b73e0402a Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Wed, 18 Jan 2023 10:44:47 +0100 Subject: [PATCH 2/7] added thresholds in object panels --- .../e2e/journeys/synthetics/index.ts | 1 + .../synthetics/step_details.journey.ts | 55 ++++++++ .../synthetics/synthetics_app.tsx | 18 +++ .../hooks/use_object_metrics.ts | 81 +++++++++-- .../hooks/use_prev_object_metrics.ts | 126 ++++++++++++++++++ .../step_details_page/step_detail_page.tsx | 2 +- .../step_metrics/step_metrics.tsx | 2 +- .../step_objects/color_palette.tsx | 53 +++++++- .../step_objects/object_count_list.tsx | 10 +- .../step_objects/object_weight_list.tsx | 10 +- .../network_timings_donut.tsx | 2 +- .../contexts/synthetics_refresh_context.tsx | 2 +- 12 files changed, 337 insertions(+), 25 deletions(-) create mode 100644 x-pack/plugins/synthetics/e2e/journeys/synthetics/step_details.journey.ts create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_prev_object_metrics.ts diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts index c7232fefb6efd..15dd3c2b87247 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts @@ -22,3 +22,4 @@ export * from './test_now_mode.journey'; export * from './data_retention.journey'; export * from './monitor_details_page/monitor_summary.journey'; export * from './test_run_details.journey'; +export * from './step_details.journey'; diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/step_details.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/step_details.journey.ts new file mode 100644 index 0000000000000..94172d2c2bde6 --- /dev/null +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/step_details.journey.ts @@ -0,0 +1,55 @@ +/* + * 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 { journey, step, before, after } from '@elastic/synthetics'; +import { recordVideo } from '@kbn/observability-plugin/e2e/record_video'; +import { syntheticsAppPageProvider } from '../../page_objects/synthetics/synthetics_app'; +import { SyntheticsServices } from './services/synthetics_services'; + +journey(`StepDetailsPage`, async ({ page, params }) => { + recordVideo(page); + + page.setDefaultTimeout(60 * 1000); + const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl }); + + const services = new SyntheticsServices(params); + + before(async () => { + await services.cleaUp(); + await services.enableMonitorManagedViaApi(); + await services.addTestMonitor( + 'https://www.google.com', + { + type: 'browser', + urls: 'https://www.google.com', + custom_heartbeat_id: 'a47bfc4e-361a-4eb0-83f3-b5bb68781b5b', + locations: [ + { id: 'us_central', label: 'North America - US Central', isServiceManaged: true }, + ], + }, + 'a47bfc4e-361a-4eb0-83f3-b5bb68781b5b' + ); + }); + + after(async () => { + await services.cleaUp(); + }); + + step('Go to step details page', async () => { + await syntheticsApp.navigateToStepDetails({ + stepIndex: 1, + checkGroup: 'ab240846-8d22-11ed-8fac-52bb19a2321e', + configId: 'a47bfc4e-361a-4eb0-83f3-b5bb68781b5b', + }); + }); + + step('it shows metrics', async () => { + await page.waitForSelector('text=558 KB'); + await page.waitForSelector('text=402 ms'); + await page.waitForSelector('text=521 ms'); + }); +}); diff --git a/x-pack/plugins/synthetics/e2e/page_objects/synthetics/synthetics_app.tsx b/x-pack/plugins/synthetics/e2e/page_objects/synthetics/synthetics_app.tsx index 1732c3f0e91b8..f9e57b952961c 100644 --- a/x-pack/plugins/synthetics/e2e/page_objects/synthetics/synthetics_app.tsx +++ b/x-pack/plugins/synthetics/e2e/page_objects/synthetics/synthetics_app.tsx @@ -50,6 +50,24 @@ export function syntheticsAppPageProvider({ page, kibanaUrl }: { page: Page; kib } }, + async navigateToStepDetails({ + configId, + stepIndex, + checkGroup, + doLogin = true, + }: { + checkGroup: string; + configId: string; + stepIndex: number; + doLogin?: boolean; + }) { + const stepDetails = `/monitor/${configId}/test-run/${checkGroup}/step/${stepIndex}?locationId=us_central`; + await page.goto(overview + stepDetails, { waitUntil: 'networkidle' }); + if (doLogin) { + await this.loginToKibana(); + } + }, + async waitForMonitorManagementLoadingToFinish() { while (true) { if ((await page.$(this.byTestId('uptimeLoader'))) === null) break; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_object_metrics.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_object_metrics.ts index fd19ec7675ec6..6cc622c8e19b5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_object_metrics.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_object_metrics.ts @@ -7,31 +7,59 @@ import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; +import { usePreviousObjectMetrics } from './use_prev_object_metrics'; import { MIME_FILTERS, MimeType, MimeTypesMap } from '../common/network_data/types'; import { networkEventsSelector } from '../../../state/network_events/selectors'; export const useObjectMetrics = () => { const { checkGroupId, stepIndex } = useParams<{ checkGroupId: string; stepIndex: string }>(); + const { mimeData: prevMimeData } = usePreviousObjectMetrics(); + const _networkEvents = useSelector(networkEventsSelector); const networkEvents = _networkEvents[checkGroupId ?? '']?.[Number(stepIndex)]; - const objectTypeCounts: Record = {}; - const objectTypeWeights: Record = {}; + const objectTypeCounts: Record = {}; + const objectTypeWeights: Record = {}; networkEvents?.events.forEach((event) => { if (event.mimeType) { - objectTypeCounts[MimeTypesMap[event.mimeType] ?? MimeType.Other] = - (objectTypeCounts[MimeTypesMap[event.mimeType] ?? MimeType.Other] ?? 0) + 1; - objectTypeWeights[MimeTypesMap[event.mimeType] ?? MimeType.Other] = - (objectTypeWeights[MimeTypesMap[event.mimeType] ?? MimeType.Other] ?? 0) + - (event.transferSize || 0); + const mimeType = MimeTypesMap[event.mimeType] ?? MimeType.Other; + + if (objectTypeCounts[mimeType]) { + objectTypeCounts[mimeType].value++; + } else { + objectTypeCounts[mimeType] = { value: 1, prevValue: 0 }; + } + + if (objectTypeWeights[mimeType]) { + objectTypeWeights[mimeType].value += event.transferSize || 0; + } else { + objectTypeWeights[mimeType] = { + value: event.transferSize || 0, + prevValue: 0, + }; + } } }); - const totalObjects = Object.values(objectTypeCounts).reduce((acc, val) => acc + val, 0); + const totalObjects = Object.values(objectTypeCounts).reduce((acc, val) => acc + val.value, 0); - const totalObjectsWeight = Object.values(objectTypeWeights).reduce((acc, val) => acc + val, 0); + const totalObjectsWeight = Object.values(objectTypeWeights).reduce( + (acc, val) => acc + val.value, + 0 + ); + + Object.keys(prevMimeData).forEach((mimeType) => { + const mimeTypeKey = MimeTypesMap[mimeType] ?? MimeType.Other; + if (objectTypeCounts[mimeTypeKey]) { + objectTypeCounts[mimeTypeKey].prevValue += prevMimeData[mimeType].count; + } + + if (objectTypeWeights[mimeTypeKey]) { + objectTypeWeights[mimeTypeKey].prevValue += prevMimeData[mimeType].weight; + } + }); return { loading: networkEvents?.loading ?? true, @@ -39,16 +67,41 @@ export const useObjectMetrics = () => { totalObjectsWeight: formatBytes(totalObjectsWeight), items: MIME_FILTERS.map(({ label, mimeType }) => ({ label, - count: objectTypeCounts[mimeType] ?? 0, - total: totalObjects, mimeType, - percent: ((objectTypeCounts[mimeType] ?? 0) / totalObjects) * 100, - weight: formatBytes(objectTypeWeights[mimeType] ?? 0), - weightPercent: ((objectTypeWeights[mimeType] ?? 0) / totalObjectsWeight) * 100, + total: totalObjects, + count: objectTypeCounts?.[mimeType]?.value ?? 0, + percent: ((objectTypeCounts?.[mimeType]?.value ?? 0) / totalObjects) * 100, + weight: formatBytes(objectTypeWeights[mimeType]?.value ?? 0), + weightPercent: ((objectTypeWeights[mimeType]?.value ?? 0) / totalObjectsWeight) * 100, + + countDelta: getDeltaPercent( + objectTypeCounts?.[mimeType]?.value ?? 0, + objectTypeCounts?.[mimeType]?.prevValue ?? 0 + ), + weightDelta: getWeightDeltaPercent( + objectTypeWeights?.[mimeType]?.value, + objectTypeWeights?.[mimeType]?.prevValue + ), })), }; }; +export const getWeightDeltaPercent = (current: number, previous: number) => { + if (previous === 0 || !previous) { + return 0; + } + + return (((current - previous) / previous) * 100).toFixed(0); +}; + +export const getDeltaPercent = (current: number, previous: number) => { + if (previous === 0) { + return 0; + } + + return (((current - previous) / previous) * 100).toFixed(0); +}; + export const formatBytes = (bytes: number, decimals = 0) => { if (bytes === 0) return '0 Bytes'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_prev_object_metrics.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_prev_object_metrics.ts new file mode 100644 index 0000000000000..18f81936aa557 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_prev_object_metrics.ts @@ -0,0 +1,126 @@ +/* + * 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 { useReduxEsSearch } from '../../../hooks/use_redux_es_search'; +import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; + +export const MONITOR_DURATION_US = 'monitor.duration.us'; +export const SYNTHETICS_CLS = 'browser.experience.cls'; +export const SYNTHETICS_LCP = 'browser.experience.lcp.us'; +export const SYNTHETICS_FCP = 'browser.experience.fcp.us'; +export const SYNTHETICS_ONLOAD_EVENT = 'browser.experience.load.us'; +export const SYNTHETICS_DCL = 'browser.experience.dcl.us'; +export const SYNTHETICS_STEP_NAME = 'synthetics.step.name.keyword'; +export const SYNTHETICS_STEP_DURATION = 'synthetics.step.duration.us'; + +export type PreviousObjectMetrics = ReturnType; + +export const usePreviousObjectMetrics = () => { + const { monitorId, stepIndex, checkGroupId } = useParams<{ + monitorId: string; + stepIndex: string; + checkGroupId: string; + }>(); + + const { data: prevObjectMetrics } = useReduxEsSearch( + { + index: SYNTHETICS_INDEX_PATTERN, + body: { + track_total_hits: false, + sort: [ + { + '@timestamp': { + order: 'desc', + }, + }, + ], + size: 0, + runtime_mappings: { + 'synthetics.payload.transfer_size': { + type: 'long', + }, + }, + query: { + bool: { + filter: [ + { + term: { + config_id: monitorId, + }, + }, + { + term: { + 'synthetics.type': 'journey/network_info', + }, + }, + { + term: { + 'synthetics.step.index': stepIndex, + }, + }, + { + range: { + '@timestamp': { + gte: 'now-24h/h', + lte: 'now', + }, + }, + }, + ], + must_not: [ + { + term: { + 'monitor.check_group': { + value: checkGroupId, + }, + }, + }, + ], + }, + }, + aggs: { + testRuns: { + cardinality: { + field: 'monitor.check_group', + }, + }, + objectCounts: { + terms: { + field: 'http.response.mime_type', + size: 500, + }, + aggs: { + weight: { + sum: { + field: 'synthetics.payload.transfer_size', + }, + }, + }, + }, + }, + }, + }, + [stepIndex, monitorId, checkGroupId], + { + name: 'previousObjectMetrics', + } + ); + + const mimeData: Record = {}; + + const testRuns = prevObjectMetrics?.aggregations?.testRuns?.value ?? 0; + + prevObjectMetrics?.aggregations?.objectCounts?.buckets?.forEach((bucket) => { + mimeData[bucket.key] = { + weight: bucket.weight.value ? bucket.weight.value / testRuns : 0, + count: bucket.doc_count / testRuns, + }; + }); + + return { mimeData }; +}; 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 dfb72e3315cbd..e190a45bd38ba 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 @@ -89,7 +89,7 @@ export const StepDetailPage = () => { - + diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_metrics/step_metrics.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_metrics/step_metrics.tsx index 3845fdf2def59..1539c98111c5f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_metrics/step_metrics.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_metrics/step_metrics.tsx @@ -134,7 +134,7 @@ const StatThreshold = ({ {title} {isSame ? ( - + ) : ( )} 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 7d43f7d1dd26f..c3f2bc2e9d70c 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 @@ -6,9 +6,17 @@ */ import React, { useState, useEffect } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingContent } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiLoadingContent, + EuiIcon, + EuiToolTip, +} from '@elastic/eui'; import { useTheme } from '@kbn/observability-plugin/public'; import styled from 'styled-components'; +import { i18n } from '@kbn/i18n'; import { colourPalette } from '../common/network_data/data_formatting'; export const ColorPalette = ({ @@ -17,17 +25,43 @@ export const ColorPalette = ({ percent, value, loading, + delta, + hasAnyThresholdBreach, labelWidth = 40, - valueWidth = 60, + valueWidth = 65, }: { label: string; mimeType: string; percent: number; + delta: number; value: string; loading: boolean; + hasAnyThresholdBreach: boolean; labelWidth?: number; valueWidth?: number; }) => { + const getToolTipContent = () => { + return i18n.translate('xpack.synthetics.stepDetails.palette.tooltip', { + defaultMessage: 'Value is {deltaLabel} compared to previous steps in last 24 hours.', + values: { + deltaLabel: + Math.abs(delta) === 0 + ? i18n.translate('xpack.synthetics.stepDetails.palette.tooltip.noChange', { + defaultMessage: 'same', + }) + : delta > 0 + ? i18n.translate('xpack.synthetics.stepDetails.palette.increased', { + defaultMessage: '{delta}% higher', + values: { delta }, + }) + : i18n.translate('xpack.synthetics.stepDetails.palette.decreased', { + defaultMessage: '{delta}% lower', + values: { delta }, + }), + }, + }); + }; + return ( @@ -49,6 +83,21 @@ export const ColorPalette = ({ {value} + {hasAnyThresholdBreach && ( + + + {Math.abs(delta) > 5 ? ( + 5 ? 'sortUp' : 'sortDown'} + size="m" + color={delta > 5 ? 'danger' : 'success'} + /> + ) : ( + + )} + + + )} ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/object_count_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/object_count_list.tsx index bbfec1d56fc2f..ec4555da24dc0 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/object_count_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/object_count_list.tsx @@ -14,6 +14,10 @@ import { useObjectMetrics } from '../hooks/use_object_metrics'; export const ObjectCountList = () => { const objectMetrics = useObjectMetrics(); + const hasAnyThresholdBreach = objectMetrics.items.some( + ({ countDelta }) => Math.abs(Number(countDelta)) > 5 + ); + return ( <> @@ -28,17 +32,19 @@ export const ObjectCountList = () => { - +
- {objectMetrics.items.map(({ label, mimeType, percent, count }) => ( + {objectMetrics.items.map(({ label, mimeType, percent, count, countDelta }) => ( diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/object_weight_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/object_weight_list.tsx index c787fcc0711ba..186f92b9782df 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/object_weight_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/object_weight_list.tsx @@ -13,7 +13,9 @@ import { useObjectMetrics } from '../hooks/use_object_metrics'; export const ObjectWeightList = () => { const objectMetrics = useObjectMetrics(); - + const hasAnyThresholdBreach = objectMetrics.items.some( + ({ weightDelta }) => Math.abs(Number(weightDelta)) > 5 + ); return ( <> @@ -29,16 +31,18 @@ export const ObjectWeightList = () => { - +
- {objectMetrics.items.map(({ label, mimeType, weightPercent, weight }) => ( + {objectMetrics.items.map(({ label, mimeType, weightPercent, weight, weightDelta }) => ( {' '} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_timing_breakdown/network_timings_donut.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_timing_breakdown/network_timings_donut.tsx index 439cc300afe4f..e9d58427e8826 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_timing_breakdown/network_timings_donut.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_timing_breakdown/network_timings_donut.tsx @@ -51,7 +51,7 @@ export const NetworkTimingsDonut = () => {

{TIMINGS_BREAKDOWN}

- + void; } -export const APP_DEFAULT_REFRESH_INTERVAL = 1000 * 30; +export const APP_DEFAULT_REFRESH_INTERVAL = 1000 * 300; const defaultContext: SyntheticsRefreshContext = { lastRefresh: 0, From 0c6afe4df6085b8197d98a580ef05f5eb1a8ebc6 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Wed, 18 Jan 2023 11:52:23 +0100 Subject: [PATCH 3/7] add text color --- .../step_details_page/step_objects/color_palette.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 c3f2bc2e9d70c..af34db1ff2bf7 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 @@ -62,6 +62,8 @@ export const ColorPalette = ({ }); }; + const hasDelta = Math.abs(delta) > 5; + return ( @@ -79,6 +81,7 @@ export const ColorPalette = ({ size="s" style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }} className="eui-textRight" + color={!hasDelta ? 'default' : delta > 5 ? 'danger' : 'success'} > {value} @@ -86,7 +89,7 @@ export const ColorPalette = ({ {hasAnyThresholdBreach && ( - {Math.abs(delta) > 5 ? ( + {hasDelta ? ( 5 ? 'sortUp' : 'sortDown'} size="m" From 8dddf7c9d29abf6f4cb4935c318e14be5078dc44 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Wed, 18 Jan 2023 17:05:10 +0100 Subject: [PATCH 4/7] PR feedback --- .../network_timings_breakdown.tsx | 11 +++++++++++ .../step_objects/color_palette.tsx | 17 ++++++++++------- .../server/queries/get_journey_details.ts | 5 +++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/network_timings_breakdown.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/network_timings_breakdown.tsx index 27ec3e6deffbb..cd9a89c7e5391 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/network_timings_breakdown.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/network_timings_breakdown.tsx @@ -11,6 +11,8 @@ import { ReportTypes } from '@kbn/observability-plugin/public'; import { EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ClientPluginsStart } from '../../../../plugin'; +import { useSelectedLocation } from '../monitor_details/hooks/use_selected_location'; +import { LoadingState } from '../monitors_page/overview/overview/monitor_detail_flyout'; export const NetworkTimingsBreakdown = ({ monitorId }: { monitorId: string }) => { const { observability } = useKibana().services; @@ -19,6 +21,11 @@ export const NetworkTimingsBreakdown = ({ monitorId }: { monitorId: string }) => const { stepIndex } = useParams<{ checkGroupId: string; stepIndex: string }>(); + const selectedLocation = useSelectedLocation(); + if (!selectedLocation) { + return ; + } + return ( <> @@ -47,6 +54,10 @@ export const NetworkTimingsBreakdown = ({ monitorId }: { monitorId: string }) => field: 'synthetics.step.index', values: [stepIndex], }, + { + field: 'observer.geo.name', + values: [selectedLocation.label], + }, ], }, ]} 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 af34db1ff2bf7..274d0ecbc6d0d 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 @@ -62,7 +62,14 @@ export const ColorPalette = ({ }); }; - const hasDelta = Math.abs(delta) > 5; + const getColor = () => { + if (Math.abs(delta) < 5) { + return 'default'; + } + return delta > 5 ? 'danger' : 'success'; + }; + + const hasDelta = Math.abs(delta) > 0; return ( @@ -81,7 +88,7 @@ export const ColorPalette = ({ size="s" style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }} className="eui-textRight" - color={!hasDelta ? 'default' : delta > 5 ? 'danger' : 'success'} + color={getColor()} > {value} @@ -90,11 +97,7 @@ export const ColorPalette = ({ {hasDelta ? ( - 5 ? 'sortUp' : 'sortDown'} - size="m" - color={delta > 5 ? 'danger' : 'success'} - /> + 0 ? 'sortUp' : 'sortDown'} size="m" color={getColor()} /> ) : ( )} diff --git a/x-pack/plugins/synthetics/server/queries/get_journey_details.ts b/x-pack/plugins/synthetics/server/queries/get_journey_details.ts index bacc3cf64ae48..f5b67f8e0e6a4 100644 --- a/x-pack/plugins/synthetics/server/queries/get_journey_details.ts +++ b/x-pack/plugins/synthetics/server/queries/get_journey_details.ts @@ -65,6 +65,11 @@ export const getJourneyDetails: UMElasticsearchQueryFn< 'monitor.id': thisJourneySource.monitor.id, }, }, + { + term: { + 'observer.geo.name': thisJourneySource.observer?.geo?.name, + }, + }, { term: { 'synthetics.type': 'journey/start', From 376cc9f0097e4cd30883f6c429d22e3afcdeebd1 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Wed, 18 Jan 2023 17:07:05 +0100 Subject: [PATCH 5/7] rvert --- .../apps/synthetics/contexts/synthetics_refresh_context.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx index 96af6e1391b16..b6ee5e1616c5c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx @@ -13,7 +13,7 @@ interface SyntheticsRefreshContext { refreshApp: () => void; } -export const APP_DEFAULT_REFRESH_INTERVAL = 1000 * 300; +export const APP_DEFAULT_REFRESH_INTERVAL = 1000 * 30; const defaultContext: SyntheticsRefreshContext = { lastRefresh: 0, From 66e358e08ea183834202952c08f0a791be23c848 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Thu, 19 Jan 2023 20:37:25 +0100 Subject: [PATCH 6/7] PR feedback --- .../hooks/use_prev_object_metrics.ts | 17 ++++++++++++++++- .../step_objects/color_palette.tsx | 2 +- .../synthetics/hooks/use_redux_es_search.ts | 8 ++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_prev_object_metrics.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_prev_object_metrics.ts index 18f81936aa557..1a5d31415decb 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_prev_object_metrics.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_prev_object_metrics.ts @@ -6,6 +6,8 @@ */ import { useParams } from 'react-router-dom'; +import moment from 'moment'; +import { useJourneySteps } from '../../monitor_details/hooks/use_journey_steps'; import { useReduxEsSearch } from '../../../hooks/use_redux_es_search'; import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; @@ -27,6 +29,10 @@ export const usePreviousObjectMetrics = () => { checkGroupId: string; }>(); + const { data } = useJourneySteps(); + + const timestamp = data?.details?.timestamp; + const { data: prevObjectMetrics } = useReduxEsSearch( { index: SYNTHETICS_INDEX_PATTERN, @@ -48,6 +54,14 @@ export const usePreviousObjectMetrics = () => { query: { bool: { filter: [ + { + range: { + '@timestamp': { + lte: timestamp ?? 'now', + gte: moment(timestamp).subtract(1, 'day').toISOString(), + }, + }, + }, { term: { config_id: monitorId, @@ -107,7 +121,8 @@ export const usePreviousObjectMetrics = () => { }, [stepIndex, monitorId, checkGroupId], { - name: 'previousObjectMetrics', + name: `previousObjectMetrics/${monitorId}/${checkGroupId}/${stepIndex}/`, + isRequestReady: !!timestamp, } ); 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 274d0ecbc6d0d..8bc892c254e27 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 @@ -42,7 +42,7 @@ export const ColorPalette = ({ }) => { const getToolTipContent = () => { return i18n.translate('xpack.synthetics.stepDetails.palette.tooltip', { - defaultMessage: 'Value is {deltaLabel} compared to previous steps in last 24 hours.', + defaultMessage: 'Value is {deltaLabel} compared to steps in previous 24 hours.', values: { deltaLabel: Math.abs(delta) === 0 diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_redux_es_search.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_redux_es_search.ts index 72715c596e82a..a74af36221a31 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_redux_es_search.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_redux_es_search.ts @@ -23,9 +23,9 @@ export const useReduxEsSearch = < >( params: TParams, fnDeps: any[], - options: { inspector?: IInspectorInfo; name: string } + options: { inspector?: IInspectorInfo; name: string; isRequestReady?: boolean } ) => { - const { name } = options ?? {}; + const { name, isRequestReady = true } = options ?? {}; const { addInspectorRequest } = useInspectorContext(); @@ -35,11 +35,11 @@ export const useReduxEsSearch = < const results = useSelector(selectEsQueryResult); useEffect(() => { - if (params.index) { + if (params.index && isRequestReady) { dispatch(executeEsQueryAction.get({ params, name, addInspectorRequest })); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [addInspectorRequest, dispatch, name, JSON.stringify(params)]); + }, [addInspectorRequest, dispatch, name, JSON.stringify(params), isRequestReady]); return useMemo(() => { return { From 06085eb2caa67522a2817b485dcc4852c8633114 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Tue, 24 Jan 2023 09:47:37 +0100 Subject: [PATCH 7/7] update --- .../components/step_details_page/step_objects/color_palette.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8bc892c254e27..78fbc568a0983 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 @@ -41,7 +41,7 @@ export const ColorPalette = ({ valueWidth?: number; }) => { const getToolTipContent = () => { - return i18n.translate('xpack.synthetics.stepDetails.palette.tooltip', { + return i18n.translate('xpack.synthetics.stepDetails.palette.tooltip.label', { defaultMessage: 'Value is {deltaLabel} compared to steps in previous 24 hours.', values: { deltaLabel: