Skip to content

Commit

Permalink
[Synthetics] Test run detail page header (elastic#148470)
Browse files Browse the repository at this point in the history
  • Loading branch information
shahzad31 authored and jennypavlova committed Jan 13, 2023
1 parent c56a73a commit 987c23b
Show file tree
Hide file tree
Showing 23 changed files with 344 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const MonitorStatus = ({
);
};

const STATUS_LABEL = i18n.translate('xpack.synthetics.monitorStatus.statusLabel', {
export const STATUS_LABEL = i18n.translate('xpack.synthetics.monitorStatus.statusLabel', {
defaultMessage: 'Status',
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,47 @@
* 2.0.
*/

import { useFetcher } from '@kbn/observability-plugin/public';
import { useParams } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { useEffect } from 'react';
import { isStepEnd } from '../../common/monitor_test_result/browser_steps_list';
import { JourneyStep, SyntheticsJourneyApiResponse } from '../../../../../../common/runtime_types';
import { fetchJourneySteps } from '../../../state';
import {
fetchJourneyAction,
selectBrowserJourney,
selectBrowserJourneyLoading,
} from '../../../state';

export const useJourneySteps = (checkGroup?: string, lastRefresh?: number) => {
const { stepIndex } = useParams<{ stepIndex: string }>();
const { checkGroupId: urlCheckGroup } = useParams<{ checkGroupId: string }>();

const checkGroupId = checkGroup ?? urlCheckGroup;

const { data, loading } = useFetcher(() => {
if (!checkGroupId) {
return Promise.resolve(null);
}
const journeyData = useSelector(selectBrowserJourney(checkGroupId));
const loading = useSelector(selectBrowserJourneyLoading(checkGroupId));

const dispatch = useDispatch();

return fetchJourneySteps({ checkGroup: checkGroupId });
}, [checkGroupId, lastRefresh]);
useEffect(() => {
dispatch(fetchJourneyAction.get({ checkGroup: checkGroupId }));
}, [checkGroupId, dispatch, lastRefresh]);

const isFailed =
data?.steps.some(
journeyData?.steps.some(
(step) =>
step.synthetics?.step?.status === 'failed' || step.synthetics?.step?.status === 'skipped'
) ?? false;

const failedStep = data?.steps.find((step) => step.synthetics?.step?.status === 'failed');

const stepEnds: JourneyStep[] = (data?.steps ?? []).filter(isStepEnd);

const stepEnds: JourneyStep[] = (journeyData?.steps ?? []).filter(isStepEnd);
const failedStep = journeyData?.steps.find((step) => step.synthetics?.step?.status === 'failed');
const stepLabels = stepEnds.map((stepEnd) => stepEnd?.synthetics?.step?.name ?? '');

const currentStep = stepIndex
? data?.steps.find((step) => step.synthetics?.step?.index === Number(stepIndex))
? journeyData?.steps.find((step) => step.synthetics?.step?.index === Number(stepIndex))
: undefined;

return {
data: data as SyntheticsJourneyApiResponse,
data: journeyData as SyntheticsJourneyApiResponse,
loading: loading ?? false,
isFailed,
stepEnds,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
*/
import React from 'react';

import { MonitorStatus } from '../common/components/monitor_status';
import { EuiDescriptionList, EuiLoadingContent } from '@elastic/eui';
import { MonitorStatus, STATUS_LABEL } from '../common/components/monitor_status';
import { useSelectedMonitor } from './hooks/use_selected_monitor';
import { useMonitorLatestPing } from './hooks/use_monitor_latest_ping';

Expand All @@ -16,7 +17,13 @@ export const MonitorDetailsStatus = () => {
const { monitor } = useSelectedMonitor();

if (!monitor) {
return null;
return (
<EuiDescriptionList
align="left"
compressed={false}
listItems={[{ title: STATUS_LABEL, description: <EuiLoadingContent lines={1} /> }]}
/>
);
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiTitle } from '@elast
import React from 'react';
import { useSelector } from 'react-redux';
import { i18n } from '@kbn/i18n';
import { OverviewErrorsSparklines } from './overview_errors_sparklines';
import { OverviewErrorsCount } from './overview_errors_count';
import { ErrorsLink } from '../../../../common/links/view_errors';
import { MonitorErrorSparklines } from '../../../../monitor_details/monitor_summary/monitor_error_sparklines';
import { MonitorErrorsCount } from '../../../../monitor_details/monitor_summary/monitor_errors_count';
import { selectOverviewStatus } from '../../../../../state';

export function OverviewErrors() {
Expand All @@ -25,10 +25,10 @@ export function OverviewErrors() {
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="xl">
<EuiFlexItem grow={false}>
<MonitorErrorsCount from="now-6h/h" to="now" monitorId={status?.enabledIds ?? []} />
<OverviewErrorsCount from="now-6h/h" to="now" monitorId={status?.enabledIds ?? []} />
</EuiFlexItem>
<EuiFlexItem grow={true}>
<MonitorErrorSparklines from="now-6h/h" to="now" monitorId={status?.enabledIds ?? []} />
<OverviewErrorsSparklines from="now-6h/h" to="now" monitorId={status?.enabledIds ?? []} />
</EuiFlexItem>
<EuiFlexItem grow={false} css={{ alignSelf: 'center' }}>
<ErrorsLink disabled={true} />
Expand Down
Original file line number Diff line number Diff line change
@@ -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 { useKibana } from '@kbn/kibana-react-plugin/public';
import React, { useMemo } from 'react';
import { ReportTypes } from '@kbn/observability-plugin/public';
import { EuiLoadingContent } from '@elastic/eui';
import { ClientPluginsStart } from '../../../../../../../plugin';

interface MonitorErrorsCountProps {
from: string;
to: string;
locationLabel?: string;
monitorId: string[];
}

export const OverviewErrorsCount = ({
monitorId,
from,
to,
locationLabel,
}: MonitorErrorsCountProps) => {
const { observability } = useKibana<ClientPluginsStart>().services;

const { ExploratoryViewEmbeddable } = observability;

const time = useMemo(() => ({ from, to }), [from, to]);

if (!monitorId) {
return <EuiLoadingContent lines={3} />;
}

return (
<ExploratoryViewEmbeddable
align="left"
customHeight="70px"
reportType={ReportTypes.SINGLE_METRIC}
attributes={[
{
time,
reportDefinitions: {
'monitor.id': monitorId,
...(locationLabel ? { 'observer.geo.name': [locationLabel] } : {}),
},
dataType: 'synthetics',
selectedMetricField: 'monitor_errors',
name: 'synthetics-series-1',
},
]}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 { useKibana } from '@kbn/kibana-react-plugin/public';
import React, { useMemo } from 'react';
import { useEuiTheme } from '@elastic/eui';
import { ClientPluginsStart } from '../../../../../../../plugin';

interface Props {
from: string;
to: string;
monitorId: string[];
}
export const OverviewErrorsSparklines = ({ from, to, monitorId }: Props) => {
const { observability } = useKibana<ClientPluginsStart>().services;

const { ExploratoryViewEmbeddable } = observability;

const { euiTheme } = useEuiTheme();

const time = useMemo(() => ({ from, to }), [from, to]);

return (
<ExploratoryViewEmbeddable
reportType="kpi-over-time"
axisTitlesVisibility={{ x: false, yRight: false, yLeft: false }}
legendIsVisible={false}
hideTicks={true}
attributes={[
{
time,
seriesType: 'area',
reportDefinitions: {
'monitor.id': monitorId,
},
dataType: 'synthetics',
selectedMetricField: 'monitor_errors',
name: 'Monitor errors',
color: euiTheme.colors.danger,
operationType: 'unique_count',
},
]}
/>
);
};
Original file line number Diff line number Diff line change
@@ -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 React, { ReactElement } from 'react';
import { EuiDescriptionList, EuiLoadingContent } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useJourneySteps } from '../../monitor_details/hooks/use_journey_steps';
import { useFormatTestRunAt } from '../../../utils/monitor_test_result/test_time_formats';

export const TestRunDate: React.FC = () => {
const { data } = useJourneySteps();

let startedAt: string | ReactElement = useFormatTestRunAt(data?.details?.timestamp);

if (!startedAt) {
startedAt = <EuiLoadingContent lines={1} />;
}

return <EuiDescriptionList listItems={[{ title: ERROR_DURATION, description: startedAt }]} />;
};

const ERROR_DURATION = i18n.translate('xpack.synthetics.testDetails.date', {
defaultMessage: 'Date',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
import { useHistory } from 'react-router-dom';
import { OutPortal } from 'react-reverse-portal';
import { RouteProps } from '../../routes';
import { MonitorDetailsStatus } from '../monitor_details/monitor_details_status';
import { MonitorDetailsLocation } from '../monitor_details/monitor_details_location';
import { TestRunDate } from './components/test_run_date';
import { TEST_RUN_DETAILS_ROUTE } from '../../../../../common/constants';
import { TestRunDetails } from './test_run_details';
import { MonitorDetailsLinkPortalNode } from '../monitor_add_edit/portals';

export const getTestRunDetailsRoute = (
history: ReturnType<typeof useHistory>,
syntheticsPath: string,
baseTitle: string
): RouteProps => {
return {
title: i18n.translate('xpack.synthetics.testRunDetailsRoute.title', {
defaultMessage: 'Test run details | {baseTitle}',
values: { baseTitle },
}),
path: TEST_RUN_DETAILS_ROUTE,
component: TestRunDetails,
dataTestSubj: 'syntheticsMonitorTestRunDetailsPage',
pageHeader: {
breadcrumbs: [
{
text: <OutPortal node={MonitorDetailsLinkPortalNode} />,
},
],
pageTitle: (
<FormattedMessage
id="xpack.synthetics.testRunDetailsRoute.page.title"
defaultMessage="Test run details"
/>
),
rightSideItems: [<TestRunDate />, <MonitorDetailsStatus />, <MonitorDetailsLocation />],
},
};
};
Loading

0 comments on commit 987c23b

Please sign in to comment.