[Synthetics] Test run detail page skeleton (#144715)

This commit is contained in:
Shahzad 2022-11-09 11:44:23 +01:00 committed by GitHub
parent c9f0f3e3d4
commit 098b5db77b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 338 additions and 158 deletions

View file

@ -431,7 +431,7 @@ describe('Lens Attribute', () => {
isVisible: true,
showSingleSeries: true,
position: 'right',
legendSize: 'large',
legendSize: 'auto',
shouldTruncate: false,
},
preferredSeriesType: 'line',

View file

@ -1060,7 +1060,7 @@ export class LensAttributes {
isVisible: true,
showSingleSeries: true,
position: 'right',
legendSize: LegendSize.LARGE,
legendSize: LegendSize.AUTO,
shouldTruncate: false,
},
valueLabels: 'hide',

View file

@ -66,7 +66,7 @@ export const testMobileKPIAttr = {
isVisible: true,
showSingleSeries: true,
position: 'right',
legendSize: 'large',
legendSize: 'auto',
shouldTruncate: false,
},
valueLabels: 'hide',

View file

@ -325,7 +325,7 @@ export const sampleAttribute = {
isVisible: true,
position: 'right',
showSingleSeries: true,
legendSize: 'large',
legendSize: 'auto',
shouldTruncate: false,
},
preferredSeriesType: 'line',

View file

@ -145,7 +145,7 @@ export const sampleAttributeCoreWebVital = {
showSingleSeries: true,
position: 'right',
shouldTruncate: false,
legendSize: 'large',
legendSize: 'auto',
},
preferredSeriesType: 'line',
tickLabelsVisibilitySettings: {

View file

@ -98,7 +98,7 @@ export const sampleAttributeKpi = {
isVisible: true,
showSingleSeries: true,
position: 'right',
legendSize: 'large',
legendSize: 'auto',
shouldTruncate: false,
},
preferredSeriesType: 'line',

View file

@ -325,7 +325,7 @@ export const sampleAttributeWithReferenceLines = {
isVisible: true,
position: 'right',
showSingleSeries: true,
legendSize: 'large',
legendSize: 'auto',
shouldTruncate: false,
},
preferredSeriesType: 'line',

View file

@ -33,6 +33,8 @@ export const STEP_DETAIL_ROUTE = '/journey/:checkGroupId/step/:stepIndex';
export const SYNTHETIC_CHECK_STEPS_ROUTE = '/journey/:checkGroupId/steps';
export const TEST_RUN_DETAILS_ROUTE = '/monitor/:monitorId/test-run/:checkGroupId';
export const MAPPING_ERROR_ROUTE = '/mapping-error';
export const ERROR_DETAILS_ROUTE = '/error-details/:errorStateId';

View file

@ -0,0 +1,45 @@
/*
* 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 { EuiLink, EuiText, useEuiTheme } from '@elastic/eui';
import { Ping } from '../../../../../../common/runtime_types';
import { useSyntheticsSettingsContext } from '../../../contexts';
import { useKibanaDateFormat } from '../../../../../hooks/use_kibana_date_format';
import { formatTestRunAt } from '../../../utils/monitor_test_result/test_time_formats';
export const TestDetailsLink = ({
isBrowserMonitor,
timestamp,
ping,
}: {
isBrowserMonitor: boolean;
timestamp: string;
ping: Ping;
}) => {
const { euiTheme } = useEuiTheme();
const { basePath } = useSyntheticsSettingsContext();
const format = useKibanaDateFormat();
const timestampText = (
<EuiText size="s" css={{ fontWeight: euiTheme.font.weight.medium }}>
{formatTestRunAt(timestamp, format)}
</EuiText>
);
return isBrowserMonitor ? (
<EuiLink
href={`${basePath}/app/synthetics/monitor/${ping?.config_id ?? ''}/test-run/${
ping.monitor.check_group
}`}
>
{timestampText}
</EuiLink>
) : (
timestampText
);
};

View file

@ -16,11 +16,11 @@ import {
} from '@elastic/eui';
import { EuiThemeComputed } from '@elastic/eui/src/services/theme/types';
import { JourneyStepScreenshotContainer } from '../screenshot/journey_step_screenshot_container';
import { useSyntheticsSettingsContext } from '../../../contexts/synthetics_settings_context';
import { JourneyStep } from '../../../../../../common/runtime_types';
import { StatusBadge, parseBadgeStatus, getTextColorForMonitorStatus } from './status_badge';
import { JourneyStepScreenshotWithLabel } from './journey_step_screenshot_with_label';
import { StepDurationText } from './step_duration_text';
interface Props {
@ -28,13 +28,20 @@ interface Props {
error?: Error;
loading: boolean;
showStepNumber: boolean;
compressed?: boolean;
}
export function isStepEnd(step: JourneyStep) {
return step.synthetics?.type === 'step/end';
}
export const BrowserStepsList = ({ steps, error, loading, showStepNumber = false }: Props) => {
export const BrowserStepsList = ({
steps,
error,
loading,
showStepNumber = false,
compressed = true,
}: Props) => {
const { euiTheme } = useEuiTheme();
const stepEnds: JourneyStep[] = steps.filter(isStepEnd);
const stepLabels = stepEnds.map((stepEnd) => stepEnd?.synthetics?.step?.name ?? '');
@ -56,13 +63,15 @@ export const BrowserStepsList = ({ steps, error, loading, showStepNumber = false
{
align: 'left',
field: 'timestamp',
name: STEP_LABEL,
render: (_timestamp: string, item) => (
<JourneyStepScreenshotWithLabel
step={item}
stepLabels={stepLabels}
compactView={true}
name: SCREENSHOT_LABEL,
render: (_timestamp: string, step) => (
<JourneyStepScreenshotContainer
checkGroup={step.monitor.check_group}
initialStepNo={step.synthetics?.step?.index}
stepStatus={step.synthetics.payload?.status}
allStepsLoaded={true}
stepLabels={stepLabels}
retryFetchOnRevisit={false}
/>
),
mobileOptions: {
@ -73,10 +82,27 @@ export const BrowserStepsList = ({ steps, error, loading, showStepNumber = false
</strong>
</EuiText>
),
header: STEP_LABEL,
header: SCREENSHOT_LABEL,
enlarge: true,
},
},
{
field: 'synthetics.step.name',
name: STEP_NAME,
render: (stepName: string, item) => {
const status = parseBadgeStatus(item.synthetics.step?.status ?? '');
const textColor = euiTheme.colors[
getTextColorForMonitorStatus(status)
] as CSSProperties['color'];
return (
<EuiText color={textColor} size="m">
{stepName}
</EuiText>
);
},
},
{
field: 'synthetics.step.status',
name: RESULT_LABEL,
@ -114,7 +140,7 @@ export const BrowserStepsList = ({ steps, error, loading, showStepNumber = false
return (
<>
<EuiBasicTable
compressed={true}
compressed={compressed}
loading={loading}
columns={columns}
error={error?.message}
@ -164,8 +190,12 @@ const RESULT_LABEL = i18n.translate('xpack.synthetics.monitor.result.label', {
defaultMessage: 'Result',
});
const STEP_LABEL = i18n.translate('xpack.synthetics.monitor.step.label', {
defaultMessage: 'Step',
const SCREENSHOT_LABEL = i18n.translate('xpack.synthetics.monitor.screenshot.label', {
defaultMessage: 'Screenshot',
});
const STEP_NAME = i18n.translate('xpack.synthetics.monitor.stepName.label', {
defaultMessage: 'Step name',
});
const STEP_DURATION = i18n.translate('xpack.synthetics.monitor.step.duration.label', {

View file

@ -1,50 +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, { CSSProperties } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiText, useEuiTheme } from '@elastic/eui';
import { JourneyStep } from '../../../../../../common/runtime_types';
import { JourneyStepScreenshotContainer } from '../screenshot/journey_step_screenshot_container';
import { getTextColorForMonitorStatus, parseBadgeStatus } from './status_badge';
interface Props {
step: JourneyStep;
stepLabels?: string[];
allStepsLoaded?: boolean;
compactView?: boolean;
}
export const JourneyStepScreenshotWithLabel = ({
step,
stepLabels = [],
compactView,
allStepsLoaded,
}: Props) => {
const { euiTheme } = useEuiTheme();
const status = parseBadgeStatus(step.synthetics.step?.status ?? '');
const textColor = euiTheme.colors[getTextColorForMonitorStatus(status)] as CSSProperties['color'];
return (
<EuiFlexGroup alignItems="center" gutterSize="s" wrap>
<EuiFlexItem grow={false}>
<JourneyStepScreenshotContainer
checkGroup={step.monitor.check_group}
initialStepNo={step.synthetics?.step?.index}
stepStatus={step.synthetics.payload?.status}
allStepsLoaded={allStepsLoaded}
stepLabels={stepLabels}
retryFetchOnRevisit={false}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ minWidth: 80 }}>
<EuiText color={textColor} size={compactView ? 's' : 'm'}>
{step.synthetics?.step?.name}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -11,16 +11,19 @@ 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) => {
export const useJourneySteps = (checkGroup?: string) => {
const { stepIndex } = useParams<{ stepIndex: string }>();
const { checkGroupId: urlCheckGroup } = useParams<{ checkGroupId: string }>();
const checkGroupId = checkGroup ?? urlCheckGroup;
const { data, loading } = useFetcher(() => {
if (!checkGroup) {
if (!checkGroupId) {
return Promise.resolve(null);
}
return fetchJourneySteps({ checkGroup });
}, [checkGroup]);
return fetchJourneySteps({ checkGroup: checkGroupId });
}, [checkGroupId]);
const isFailed =
data?.steps.some(

View file

@ -16,6 +16,8 @@ import {
EuiLink,
EuiLoadingContent,
useEuiTheme,
EuiTitle,
EuiPanel,
} from '@elastic/eui';
import { capitalize } from 'lodash';
import { i18n } from '@kbn/i18n';
@ -54,44 +56,49 @@ export const MonitorDetailsPanel = () => {
`;
return (
<div css={wrapperStyle}>
<EuiSpacer size="s" />
<EuiDescriptionList type="column" compressed={true}>
<EuiDescriptionListTitle>{ENABLED_LABEL}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
{monitor && (
<MonitorEnabled
initialLoading={loading}
id={monitorId}
monitor={monitor}
reloadPage={() => {
dispatch(getMonitorAction.get({ monitorId }));
}}
/>
)}
</EuiDescriptionListDescription>
<EuiDescriptionListTitle>{MONITOR_TYPE_LABEL}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<EuiBadge>{capitalize(monitor?.type)}</EuiBadge>
</EuiDescriptionListDescription>
<EuiDescriptionListTitle>{FREQUENCY_LABEL}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>Every 10 mins</EuiDescriptionListDescription>
<EuiDescriptionListTitle>{LOCATIONS_LABEL}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<LocationsStatus />
</EuiDescriptionListDescription>
<EuiDescriptionListTitle>{URL_LABEL}</EuiDescriptionListTitle>
<EuiDescriptionListDescription style={{ wordBreak: 'break-all' }}>
<EuiLink href={latestPing?.url?.full} external>
{latestPing?.url?.full}
</EuiLink>
</EuiDescriptionListDescription>
<EuiDescriptionListTitle>{TAGS_LABEL}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
{monitor && <MonitorTags tags={monitor[ConfigKey.TAGS]} />}
</EuiDescriptionListDescription>
</EuiDescriptionList>
</div>
<EuiPanel hasShadow={false} hasBorder paddingSize="m">
<EuiTitle size="xs">
<h3>{MONITOR_DETAILS_LABEL}</h3>
</EuiTitle>
<div css={wrapperStyle}>
<EuiSpacer size="s" />
<EuiDescriptionList type="column" compressed={true}>
<EuiDescriptionListTitle>{ENABLED_LABEL}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
{monitor && (
<MonitorEnabled
initialLoading={loading}
id={monitorId}
monitor={monitor}
reloadPage={() => {
dispatch(getMonitorAction.get({ monitorId }));
}}
/>
)}
</EuiDescriptionListDescription>
<EuiDescriptionListTitle>{MONITOR_TYPE_LABEL}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<EuiBadge>{capitalize(monitor?.type)}</EuiBadge>
</EuiDescriptionListDescription>
<EuiDescriptionListTitle>{FREQUENCY_LABEL}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>Every 10 mins</EuiDescriptionListDescription>
<EuiDescriptionListTitle>{LOCATIONS_LABEL}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<LocationsStatus />
</EuiDescriptionListDescription>
<EuiDescriptionListTitle>{URL_LABEL}</EuiDescriptionListTitle>
<EuiDescriptionListDescription style={{ wordBreak: 'break-all' }}>
<EuiLink href={latestPing?.url?.full} external>
{latestPing?.url?.full}
</EuiLink>
</EuiDescriptionListDescription>
<EuiDescriptionListTitle>{TAGS_LABEL}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
{monitor && <MonitorTags tags={monitor[ConfigKey.TAGS]} />}
</EuiDescriptionListDescription>
</EuiDescriptionList>
</div>
</EuiPanel>
);
};
@ -120,3 +127,7 @@ const MONITOR_TYPE_LABEL = i18n.translate(
defaultMessage: 'Monitor type',
}
);
const MONITOR_DETAILS_LABEL = i18n.translate('xpack.synthetics.detailsPanel.monitorDetails', {
defaultMessage: 'Monitor details',
});

View file

@ -46,12 +46,7 @@ export const MonitorSummary = () => {
<>
<EuiFlexGroup gutterSize="m">
<EuiFlexItem grow={1}>
<EuiPanel hasShadow={false} hasBorder paddingSize="m">
<EuiTitle size="xs">
<h3>{MONITOR_DETAILS_LABEL}</h3>
</EuiTitle>
<MonitorDetailsPanel />
</EuiPanel>
<MonitorDetailsPanel />
</EuiFlexItem>
<EuiFlexItem grow={2}>
<EuiPanel hasShadow={false} hasBorder paddingSize="m" css={{ height: 120 }}>
@ -135,10 +130,6 @@ export const MonitorSummary = () => {
);
};
const MONITOR_DETAILS_LABEL = i18n.translate('xpack.synthetics.detailsPanel.monitorDetails', {
defaultMessage: 'Monitor details',
});
const SUMMARY_LABEL = i18n.translate('xpack.synthetics.detailsPanel.summary', {
defaultMessage: 'Summary',
});

View file

@ -11,10 +11,12 @@ import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ReportTypes } from '@kbn/observability-plugin/public';
import { i18n } from '@kbn/i18n';
import { Position } from '@elastic/charts/dist/utils/common';
import { useMonitorQueryId } from '../hooks/use_monitor_query_id';
import { useSelectedMonitor } from '../hooks/use_selected_monitor';
import { ClientPluginsStart } from '../../../../../plugin';
export const StepDurationPanel = () => {
export const StepDurationPanel = ({ legendPosition }: { legendPosition?: Position }) => {
const { observability } = useKibana<ClientPluginsStart>().services;
const { ExploratoryViewEmbeddable } = observability;
@ -44,6 +46,7 @@ export const StepDurationPanel = () => {
axisTitlesVisibility={{ yLeft: false, yRight: false, x: false }}
customHeight={'300px'}
reportType={ReportTypes.KPI}
legendPosition={legendPosition}
attributes={[
{
name: DURATION_BY_STEP_LABEL,

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useMemo, useState } from 'react';
import React, { MouseEvent, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { i18n } from '@kbn/i18n';
import {
@ -14,27 +14,24 @@ import {
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiLink,
EuiPanel,
EuiText,
EuiTitle,
useEuiTheme,
} from '@elastic/eui';
import { Criteria } from '@elastic/eui/src/components/basic_table/basic_table';
import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types';
import { useHistory, useParams } from 'react-router-dom';
import { MONITOR_TYPES } from '../../../../../../common/constants';
import { TestDetailsLink } from '../../common/links/test_details_link';
import { ConfigKey, DataStream, Ping } from '../../../../../../common/runtime_types';
import {
formatTestDuration,
formatTestRunAt,
} from '../../../utils/monitor_test_result/test_time_formats';
import { formatTestDuration } from '../../../utils/monitor_test_result/test_time_formats';
import { useSyntheticsSettingsContext } from '../../../contexts/synthetics_settings_context';
import { sortPings } from '../../../utils/monitor_test_result/sort_pings';
import { selectPingsError } from '../../../state';
import { parseBadgeStatus, StatusBadge } from '../../common/monitor_test_result/status_badge';
import { useKibanaDateFormat } from '../../../../../hooks/use_kibana_date_format';
import { useSelectedMonitor } from '../hooks/use_selected_monitor';
import { useMonitorPings } from '../hooks/use_monitor_pings';
import { JourneyScreenshot } from '../../common/screenshot/journey_screenshot';
@ -48,6 +45,8 @@ interface TestRunsTableProps {
}
export const TestRunsTable = ({ paginable = true, from, to }: TestRunsTableProps) => {
const history = useHistory();
const { monitorId } = useParams<{ monitorId: string }>();
const { basePath } = useSyntheticsSettingsContext();
const [page, setPage] = useState({ index: 0, size: 10 });
@ -139,6 +138,24 @@ export const TestRunsTable = ({ paginable = true, from, to }: TestRunsTableProps
},
];
const getRowProps = (item: Ping) => {
if (item.monitor.type !== MONITOR_TYPES.BROWSER) {
return {};
}
return {
height: '85px',
'data-test-subj': `row-${item.monitor.check_group}`,
onClick: (evt: MouseEvent) => {
const targetElem = evt.target as HTMLElement;
// we dont want to capture image click event
if (targetElem.tagName !== 'IMG' && targetElem.tagName !== 'path') {
history.push(`/monitor/${monitorId}/test-run/${item.monitor.check_group}`);
}
},
};
};
const historyIdParam =
monitor?.[ConfigKey.CUSTOM_HEARTBEAT_ID] ?? monitor?.[ConfigKey.MONITOR_QUERY_ID];
return (
@ -183,6 +200,7 @@ export const TestRunsTable = ({ paginable = true, from, to }: TestRunsTableProps
tableLayout={'auto'}
sorting={sorting}
onChange={handleTableChange}
rowProps={getRowProps}
pagination={
paginable
? {
@ -198,34 +216,6 @@ export const TestRunsTable = ({ paginable = true, from, to }: TestRunsTableProps
);
};
const TestDetailsLink = ({
isBrowserMonitor,
timestamp,
ping,
}: {
isBrowserMonitor: boolean;
timestamp: string;
ping: Ping;
}) => {
const { euiTheme } = useEuiTheme();
const { basePath } = useSyntheticsSettingsContext();
const format = useKibanaDateFormat();
const timestampText = (
<EuiText size="s" css={{ fontWeight: euiTheme.font.weight.medium }}>
{formatTestRunAt(timestamp, format)}
</EuiText>
);
return isBrowserMonitor ? (
<EuiLink href={`${basePath}/app/uptime/journey/${ping?.monitor?.check_group ?? ''}/steps`}>
{timestampText}
</EuiLink>
) : (
timestampText
);
};
const TEST_RUNS = i18n.translate('xpack.synthetics.monitorDetails.summary.testRuns', {
defaultMessage: 'Test Runs',
});

View file

@ -0,0 +1,37 @@
/*
* 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 { useSelectedMonitor } from '../../monitor_details/hooks/use_selected_monitor';
import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs';
import { MONITOR_ROUTE, MONITORS_ROUTE } from '../../../../../../common/constants';
import { PLUGIN } from '../../../../../../common/constants/plugin';
export const useTestRunDetailsBreadcrumbs = (
extraCrumbs?: Array<{ text: string; href?: string }>
) => {
const kibana = useKibana();
const appPath = kibana.services.application?.getUrlForApp(PLUGIN.SYNTHETICS_PLUGIN_ID) ?? '';
const { monitor } = useSelectedMonitor();
useBreadcrumbs([
{
text: MONITOR_MANAGEMENT_CRUMB,
href: `${appPath}${MONITORS_ROUTE}`,
},
{
text: monitor?.name ?? '',
href: `${appPath}${MONITOR_ROUTE.replace(':monitorId', monitor?.id ?? '')}`,
},
...(extraCrumbs ?? []),
]);
};
const MONITOR_MANAGEMENT_CRUMB = i18n.translate('xpack.synthetics.monitorsPage.monitorsMCrumb', {
defaultMessage: 'Monitors',
});

View file

@ -0,0 +1,43 @@
/*
* 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, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
import moment from 'moment';
import { MonitorDetailsPanel } from '../monitor_details/monitor_summary/monitor_details_panel';
import { useJourneySteps } from '../monitor_details/hooks/use_journey_steps';
import { StepDurationPanel } from '../monitor_details/monitor_summary/step_duration_panel';
import { TestRunSteps } from './test_run_steps';
import { useTestRunDetailsBreadcrumbs } from './hooks/use_test_run_details_breadcrumbs';
export const TestRunDetails = () => {
const { data: stepsData, loading: stepsLoading } = useJourneySteps();
useTestRunDetailsBreadcrumbs([
{ text: stepsData ? moment(stepsData.details?.timestamp).format('LLL') : '' },
]);
return (
<EuiFlexGroup gutterSize="m">
<EuiFlexItem grow={2}>
<EuiPanel hasShadow={false} hasBorder>
<EuiTitle size="xs">
{/* TODO: Add step detail panel*/}
<h3>Step 1 of {stepsData?.steps.length}</h3>
</EuiTitle>
</EuiPanel>
<EuiSpacer size="m" />
<TestRunSteps isLoading={stepsLoading} steps={stepsData?.steps ?? []} />
</EuiFlexItem>
<EuiFlexItem grow={1}>
<StepDurationPanel legendPosition="bottom" />
<EuiSpacer size="m" />
<MonitorDetailsPanel />
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -0,0 +1,56 @@
/*
* 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, EuiPanel, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { formatTestDuration } from '../../utils/monitor_test_result/test_time_formats';
import { JourneyStep } from '../../../../../common/runtime_types';
import { BrowserStepsList, isStepEnd } from '../common/monitor_test_result/browser_steps_list';
export const TestRunSteps = ({
isLoading,
steps,
}: {
isLoading: boolean;
steps: JourneyStep[];
}) => {
const totalDuration = steps
.filter(isStepEnd)
.reduce((acc, step) => acc + (step.synthetics?.step?.duration.us ?? 0), 0);
return (
<EuiPanel hasShadow={false} hasBorder>
<EuiFlexGroup>
<EuiFlexItem grow={true}>
<EuiTitle size="xs">
<h2>{STEPS_EXECUTED}</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{TOTAL_DURATION}
{formatTestDuration(totalDuration)}
</EuiFlexItem>
</EuiFlexGroup>
<BrowserStepsList
steps={steps}
loading={isLoading}
showStepNumber={true}
compressed={false}
/>
</EuiPanel>
);
};
const STEPS_EXECUTED = i18n.translate('xpack.synthetics.testDetails.stepExecuted', {
defaultMessage: 'Steps executed',
});
const TOTAL_DURATION = i18n.translate('xpack.synthetics.testDetails.totalDuration', {
defaultMessage: 'Total duration: ',
});

View file

@ -24,6 +24,7 @@ import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useInspectorContext } from '@kbn/observability-plugin/public';
import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-plugin/public';
import { getSettingsRouteConfig } from './components/settings/route_config';
import { TestRunDetails } from './components/test_run_details/test_run_details';
import { ErrorDetailsPage } from './components/error_details/error_details_page';
import { StepTitle } from './components/step_details_page/step_title';
import { MonitorAddPage } from './components/monitor_add_edit/monitor_add_page';
@ -51,6 +52,7 @@ import {
ERROR_DETAILS_ROUTE,
STEP_DETAIL_ROUTE,
OVERVIEW_ROUTE,
TEST_RUN_DETAILS_ROUTE,
} from '../../../common/constants';
import { PLUGIN } from '../../../common/constants/plugin';
import { MonitorPage } from './components/monitors_page/monitor_page';
@ -325,6 +327,23 @@ const getRoutes = (
),
},
},
{
title: i18n.translate('xpack.synthetics.testRunDetailsRoute.title', {
defaultMessage: 'Test run details | {baseTitle}',
values: { baseTitle },
}),
path: TEST_RUN_DETAILS_ROUTE,
component: TestRunDetails,
dataTestSubj: 'syntheticsMonitorTestRunDetailsPage',
pageHeader: {
pageTitle: (
<FormattedMessage
id="xpack.synthetics.testRunDetailsRoute.page.title"
defaultMessage="Test run details"
/>
),
},
},
];
};