mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
# Backport This will backport the following commits from `main` to `8.7`: - [[Synthetics] Error timeline date range (#151965)](https://github.com/elastic/kibana/pull/151965) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Shahzad","email":"shahzad31comp@gmail.com"},"sourceCommit":{"committedDate":"2023-03-01T15:46:25Z","message":"[Synthetics] Error timeline date range (#151965)","sha":"d051183adee6328ef86a8124b835ae69bfbae802","branchLabelMapping":{"^v8.8.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:uptime","release_note:skip","v8.7.0","v8.8.0"],"number":151965,"url":"https://github.com/elastic/kibana/pull/151965","mergeCommit":{"message":"[Synthetics] Error timeline date range (#151965)","sha":"d051183adee6328ef86a8124b835ae69bfbae802"}},"sourceBranch":"main","suggestedTargetBranches":["8.7"],"targetPullRequestStates":[{"branch":"8.7","label":"v8.7.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.8.0","labelRegex":"^v8.8.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/151965","number":151965,"mergeCommit":{"message":"[Synthetics] Error timeline date range (#151965)","sha":"d051183adee6328ef86a8124b835ae69bfbae802"}}]}] BACKPORT--> Co-authored-by: Shahzad <shahzad31comp@gmail.com>
This commit is contained in:
parent
054c846521
commit
3f3f616303
17 changed files with 321 additions and 105 deletions
|
@ -6,9 +6,8 @@
|
|||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
export const ErrorStateCodec = t.type({
|
||||
duration_ms: t.string,
|
||||
export const StateEndsCodec = t.type({
|
||||
duration_ms: t.union([t.string, t.number]),
|
||||
checks: t.number,
|
||||
ends: t.union([t.string, t.null]),
|
||||
started_at: t.string,
|
||||
|
@ -17,3 +16,16 @@ export const ErrorStateCodec = t.type({
|
|||
down: t.number,
|
||||
status: t.string,
|
||||
});
|
||||
|
||||
export const ErrorStateCodec = t.type({
|
||||
duration_ms: t.union([t.string, t.number]),
|
||||
checks: t.number,
|
||||
ends: t.union([StateEndsCodec, t.null]),
|
||||
started_at: t.string,
|
||||
id: t.string,
|
||||
up: t.number,
|
||||
down: t.number,
|
||||
status: t.string,
|
||||
});
|
||||
|
||||
export type ErrorState = t.TypeOf<typeof ErrorStateCodec>;
|
||||
|
|
|
@ -65,6 +65,6 @@ journey(`TestRunDetailsPage`, async ({ page, params }) => {
|
|||
|
||||
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');
|
||||
await page.waitForSelector('text=After 2.12 s');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { CSSProperties, ReactElement, useState } from 'react';
|
||||
import React, { CSSProperties, ReactElement, useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
EuiBasicTable,
|
||||
EuiBasicTableColumn,
|
||||
|
@ -62,25 +62,38 @@ export const BrowserStepsList = ({
|
|||
Record<string, ReactElement>
|
||||
>({});
|
||||
|
||||
const toggleDetails = (item: JourneyStep) => {
|
||||
const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap };
|
||||
if (itemIdToExpandedRowMapValues[item._id]) {
|
||||
delete itemIdToExpandedRowMapValues[item._id];
|
||||
} else {
|
||||
if (testNowMode) {
|
||||
itemIdToExpandedRowMapValues[item._id] = (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<StepTabs step={item} loading={false} stepsList={steps} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
} else {
|
||||
itemIdToExpandedRowMapValues[item._id] = <></>;
|
||||
}
|
||||
const toggleDetails = useCallback(
|
||||
(item: JourneyStep) => {
|
||||
setItemIdToExpandedRowMap((prevState) => {
|
||||
const itemIdToExpandedRowMapValues = { ...prevState };
|
||||
if (itemIdToExpandedRowMapValues[item._id]) {
|
||||
delete itemIdToExpandedRowMapValues[item._id];
|
||||
} else {
|
||||
if (testNowMode) {
|
||||
itemIdToExpandedRowMapValues[item._id] = (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<StepTabs step={item} loading={false} stepsList={steps} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
} else {
|
||||
itemIdToExpandedRowMapValues[item._id] = <></>;
|
||||
}
|
||||
}
|
||||
return itemIdToExpandedRowMapValues;
|
||||
});
|
||||
},
|
||||
[steps, testNowMode]
|
||||
);
|
||||
|
||||
const failedStep = stepEnds?.find((step) => step.synthetics.step?.status === 'failed');
|
||||
|
||||
useEffect(() => {
|
||||
if (failedStep && showExpand) {
|
||||
toggleDetails(failedStep);
|
||||
}
|
||||
setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues);
|
||||
};
|
||||
}, [failedStep, showExpand, toggleDetails]);
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<JourneyStep>> = [
|
||||
...(showExpand
|
||||
|
|
|
@ -8,7 +8,8 @@ import React from 'react';
|
|||
import { EuiDescriptionList } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import moment from 'moment';
|
||||
import moment, { Moment } from 'moment';
|
||||
import { useFindMyKillerState } from '../hooks/use_find_my_killer_state';
|
||||
import { useErrorFailedTests } from '../hooks/use_last_error_state';
|
||||
|
||||
export const ErrorDuration: React.FC = () => {
|
||||
|
@ -16,13 +17,41 @@ export const ErrorDuration: React.FC = () => {
|
|||
|
||||
const state = failedTests?.[0]?.state;
|
||||
|
||||
const duration = state ? moment().diff(moment(state?.started_at), 'minutes') : 0;
|
||||
const { killerState } = useFindMyKillerState();
|
||||
|
||||
return (
|
||||
<EuiDescriptionList listItems={[{ title: ERROR_DURATION, description: `${duration} min` }]} />
|
||||
);
|
||||
const endsAt = killerState?.timestamp ? moment(killerState?.timestamp) : moment();
|
||||
const startedAt = moment(state?.started_at);
|
||||
|
||||
const duration = state ? getErrorDuration(startedAt, endsAt) : 0;
|
||||
|
||||
return <EuiDescriptionList listItems={[{ title: ERROR_DURATION, description: duration }]} />;
|
||||
};
|
||||
|
||||
const ERROR_DURATION = i18n.translate('xpack.synthetics.errorDetails.errorDuration', {
|
||||
defaultMessage: 'Error duration',
|
||||
});
|
||||
|
||||
const getErrorDuration = (startedAt: Moment, endsAt: Moment) => {
|
||||
// const endsAt = state.ends ? moment(state.ends) : moment();
|
||||
// const startedAt = moment(state?.started_at);
|
||||
|
||||
const diffInDays = endsAt.diff(startedAt, 'days');
|
||||
if (diffInDays > 1) {
|
||||
return i18n.translate('xpack.synthetics.errorDetails.errorDuration.days', {
|
||||
defaultMessage: '{value} days',
|
||||
values: { value: diffInDays },
|
||||
});
|
||||
}
|
||||
const diffInHours = endsAt.diff(startedAt, 'hours');
|
||||
if (diffInHours > 1) {
|
||||
return i18n.translate('xpack.synthetics.errorDetails.errorDuration.hours', {
|
||||
defaultMessage: '{value} hours',
|
||||
values: { value: diffInHours },
|
||||
});
|
||||
}
|
||||
const diffInMinutes = endsAt.diff(startedAt, 'minutes');
|
||||
return i18n.translate('xpack.synthetics.errorDetails.errorDuration.mins', {
|
||||
defaultMessage: '{value} mins',
|
||||
values: { value: diffInMinutes },
|
||||
});
|
||||
};
|
||||
|
|
|
@ -5,8 +5,32 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { EuiLoadingContent } from '@elastic/eui';
|
||||
import moment from 'moment';
|
||||
import { Ping } from '../../../../../../common/runtime_types';
|
||||
import { MonitorFailedTests } from '../../monitor_details/monitor_errors/failed_tests';
|
||||
|
||||
export const ErrorTimeline = () => {
|
||||
return <MonitorFailedTests time={{ from: 'now-1h', to: 'now' }} />;
|
||||
export const ErrorTimeline = ({ lastTestRun }: { lastTestRun?: Ping }) => {
|
||||
if (!lastTestRun) {
|
||||
return <EuiLoadingContent lines={3} />;
|
||||
}
|
||||
const diff = moment(lastTestRun.monitor.timespan?.lt).diff(
|
||||
moment(lastTestRun.monitor.timespan?.gte),
|
||||
'minutes'
|
||||
);
|
||||
const startedAt = lastTestRun?.state?.started_at;
|
||||
|
||||
return (
|
||||
<MonitorFailedTests
|
||||
time={{
|
||||
from: moment(startedAt)
|
||||
.subtract(diff / 2, 'minutes')
|
||||
.toISOString(),
|
||||
to: moment(lastTestRun.timestamp)
|
||||
.add(diff / 2, 'minutes')
|
||||
.toISOString(),
|
||||
}}
|
||||
allowBrushing={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,15 +8,13 @@ import React, { ReactElement } from 'react';
|
|||
import { EuiDescriptionList } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useErrorFailedTests } from '../hooks/use_last_error_state';
|
||||
import { useFormatTestRunAt } from '../../../utils/monitor_test_result/test_time_formats';
|
||||
import { useFindMyKillerState } from '../hooks/use_find_my_killer_state';
|
||||
|
||||
export const ResolvedAt: React.FC = () => {
|
||||
const { failedTests } = useErrorFailedTests();
|
||||
const { killerState } = useFindMyKillerState();
|
||||
|
||||
const state = failedTests?.[0]?.state;
|
||||
|
||||
let endsAt: string | ReactElement = useFormatTestRunAt(state?.ends ?? '');
|
||||
let endsAt: string | ReactElement = useFormatTestRunAt(killerState?.timestamp);
|
||||
|
||||
if (!endsAt) {
|
||||
endsAt = 'N/A';
|
||||
|
|
|
@ -43,7 +43,7 @@ export function ErrorDetailsPage() {
|
|||
return (
|
||||
<div>
|
||||
<PanelWithTitle title={TIMELINE_LABEL}>
|
||||
<ErrorTimeline />
|
||||
<ErrorTimeline lastTestRun={lastTestRun} />
|
||||
</PanelWithTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
|
@ -71,13 +71,19 @@ export function ErrorDetailsPage() {
|
|||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1} style={{ height: 'fit-content' }}>
|
||||
<PanelWithTitle>
|
||||
{data?.details?.journey && failedStep && (
|
||||
<StepImage ping={data?.details?.journey} step={failedStep} isFailed={isFailedStep} />
|
||||
)}
|
||||
</PanelWithTitle>
|
||||
{data?.details?.journey && failedStep && (
|
||||
<>
|
||||
<PanelWithTitle>
|
||||
<StepImage
|
||||
ping={data?.details?.journey}
|
||||
step={failedStep}
|
||||
isFailed={isFailedStep}
|
||||
/>
|
||||
</PanelWithTitle>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<StepDurationPanel doBreakdown={false} />
|
||||
<EuiSpacer size="m" />
|
||||
<MonitorDetailsPanelContainer hideLocations />
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { useSelectedLocation } from '../../monitor_details/hooks/use_selected_location';
|
||||
import { useTestRunDetailsBreadcrumbs } from '../../test_run_details/hooks/use_test_run_details_breadcrumbs';
|
||||
import { useSelectedMonitor } from '../../monitor_details/hooks/use_selected_monitor';
|
||||
import { ConfigKey } from '../../../../../../common/runtime_types';
|
||||
|
@ -19,10 +20,14 @@ export const useErrorDetailsBreadcrumbs = (
|
|||
|
||||
const { monitor } = useSelectedMonitor();
|
||||
|
||||
const selectedLocation = useSelectedLocation();
|
||||
|
||||
const errorsBreadcrumbs = [
|
||||
{
|
||||
text: ERRORS_CRUMB,
|
||||
href: `${appPath}/monitor/${monitor?.[ConfigKey.CONFIG_ID]}/errors`,
|
||||
href: `${appPath}/monitor/${monitor?.[ConfigKey.CONFIG_ID]}/errors?locationId=${
|
||||
selectedLocation?.id
|
||||
}`,
|
||||
},
|
||||
...(extraCrumbs ?? []),
|
||||
];
|
||||
|
|
|
@ -57,7 +57,8 @@ export function useErrorFailedTests() {
|
|||
return useMemo(() => {
|
||||
const failedTests =
|
||||
data?.hits.hits?.map((doc) => {
|
||||
return doc._source as Ping;
|
||||
const source = doc._source as any;
|
||||
return { ...source, timestamp: source['@timestamp'] } as Ping;
|
||||
}) ?? [];
|
||||
|
||||
return {
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useMemo } from 'react';
|
||||
import { useReduxEsSearch } from '../../../hooks/use_redux_es_search';
|
||||
import { Ping } from '../../../../../../common/runtime_types';
|
||||
import {
|
||||
EXCLUDE_RUN_ONCE_FILTER,
|
||||
SUMMARY_FILTER,
|
||||
} from '../../../../../../common/constants/client_defaults';
|
||||
import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants';
|
||||
import { useSyntheticsRefreshContext } from '../../../contexts';
|
||||
import { useGetUrlParams } from '../../../hooks';
|
||||
|
||||
export function useFindMyKillerState() {
|
||||
const { lastRefresh } = useSyntheticsRefreshContext();
|
||||
|
||||
const { errorStateId, monitorId } = useParams<{ errorStateId: string; monitorId: string }>();
|
||||
|
||||
const { dateRangeStart, dateRangeEnd } = useGetUrlParams();
|
||||
|
||||
const { data, loading } = useReduxEsSearch(
|
||||
{
|
||||
index: SYNTHETICS_INDEX_PATTERN,
|
||||
|
||||
body: {
|
||||
// TODO: remove this once we have a better way to handle this mapping
|
||||
runtime_mappings: {
|
||||
'state.ends.id': {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
size: 1,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
SUMMARY_FILTER,
|
||||
EXCLUDE_RUN_ONCE_FILTER,
|
||||
{
|
||||
term: {
|
||||
'state.ends.id': errorStateId,
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
config_id: monitorId,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
sort: [{ '@timestamp': 'desc' }],
|
||||
},
|
||||
},
|
||||
[lastRefresh, monitorId, dateRangeStart, dateRangeEnd],
|
||||
{ name: 'getStateWhichEndTheState' }
|
||||
);
|
||||
|
||||
return useMemo(() => {
|
||||
const killerStates =
|
||||
data?.hits.hits?.map((doc) => {
|
||||
const source = doc._source as any;
|
||||
return { ...source, timestamp: source['@timestamp'] } as Ping;
|
||||
}) ?? [];
|
||||
|
||||
return {
|
||||
loading,
|
||||
killerState: killerStates?.[0],
|
||||
};
|
||||
}, [data, loading]);
|
||||
}
|
|
@ -39,7 +39,7 @@ export const getErrorDetailsRouteConfig = (
|
|||
),
|
||||
rightSideItems: [
|
||||
<ErrorDuration />,
|
||||
<MonitorDetailsLocation />,
|
||||
<MonitorDetailsLocation isDisabled={true} />,
|
||||
<ResolvedAt />,
|
||||
<ErrorStartedAt />,
|
||||
],
|
||||
|
|
|
@ -87,12 +87,15 @@ export function useMonitorErrors(monitorIdArg?: string) {
|
|||
},
|
||||
},
|
||||
},
|
||||
[lastRefresh, monitorId, monitorIdArg, dateRangeStart, dateRangeEnd],
|
||||
{ name: 'getMonitorErrors', isRequestReady: Boolean(selectedLocation?.label) }
|
||||
[lastRefresh, monitorId, monitorIdArg, dateRangeStart, dateRangeEnd, selectedLocation?.label],
|
||||
{
|
||||
name: `getMonitorErrors/${dateRangeStart}/${dateRangeEnd}`,
|
||||
isRequestReady: Boolean(selectedLocation?.label),
|
||||
}
|
||||
);
|
||||
|
||||
return useMemo(() => {
|
||||
const errorStates = (data?.aggregations?.errorStates.buckets ?? []).map((loc) => {
|
||||
const errorStates = data?.aggregations?.errorStates.buckets?.map((loc) => {
|
||||
return loc.summary.hits.hits?.[0]._source as PingState;
|
||||
});
|
||||
|
||||
|
|
|
@ -50,6 +50,10 @@ export const ErrorsList = ({
|
|||
|
||||
const selectedLocation = useSelectedLocation();
|
||||
|
||||
const lastTestRun = errorStates?.sort((a, b) => {
|
||||
return moment(b.state.started_at).valueOf() - moment(a.state.started_at).valueOf();
|
||||
})?.[0];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
field: 'item.state.started_at',
|
||||
|
@ -67,49 +71,56 @@ export const ErrorsList = ({
|
|||
/>
|
||||
);
|
||||
const isActive = isActiveState(item);
|
||||
if (!isActive) {
|
||||
if (!isActive || lastTestRun.state.id !== item.state.id) {
|
||||
return link;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="m" alignItems="center">
|
||||
<EuiFlexItem grow={false}>{link}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} className="eui-textNoWrap">
|
||||
{link}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge iconType="clock" iconSide="right">
|
||||
Active
|
||||
{ACTIVE_LABEL}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
},
|
||||
},
|
||||
...(isBrowserType
|
||||
? [
|
||||
{
|
||||
field: 'monitor.check_group',
|
||||
name: FAILED_STEP_LABEL,
|
||||
truncateText: true,
|
||||
sortable: (a: PingState) => {
|
||||
const failedStep = failedSteps.find(
|
||||
(step) => step.monitor.check_group === a.monitor.check_group
|
||||
);
|
||||
if (!failedStep) {
|
||||
return a.monitor.check_group;
|
||||
}
|
||||
return failedStep.synthetics?.step?.name;
|
||||
},
|
||||
render: (value: string, item: PingState) => {
|
||||
const failedStep = failedSteps.find((step) => step.monitor.check_group === value);
|
||||
if (!failedStep) {
|
||||
return <>--</>;
|
||||
}
|
||||
return (
|
||||
<EuiText size="s">
|
||||
{failedStep.synthetics?.step?.index}. {failedStep.synthetics?.step?.name}
|
||||
</EuiText>
|
||||
);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
field: 'monitor.check_group',
|
||||
name: !isBrowserType ? ERROR_MESSAGE_LABEL : FAILED_STEP_LABEL,
|
||||
truncateText: true,
|
||||
sortable: (a: PingState) => {
|
||||
const failedStep = failedSteps.find(
|
||||
(step) => step.monitor.check_group === a.monitor.check_group
|
||||
);
|
||||
if (!failedStep) {
|
||||
return a.monitor.check_group;
|
||||
}
|
||||
return failedStep.synthetics?.step?.name;
|
||||
},
|
||||
render: (value: string, item: PingState) => {
|
||||
if (!isBrowserType) {
|
||||
return <EuiText size="s">{item.error.message ?? '--'}</EuiText>;
|
||||
}
|
||||
const failedStep = failedSteps.find((step) => step.monitor.check_group === value);
|
||||
if (!failedStep) {
|
||||
return <>--</>;
|
||||
}
|
||||
return (
|
||||
<EuiText size="s">
|
||||
{failedStep.synthetics?.step?.index}. {failedStep.synthetics?.step?.name}
|
||||
</EuiText>
|
||||
);
|
||||
},
|
||||
field: 'error.message',
|
||||
name: ERROR_MESSAGE_LABEL,
|
||||
},
|
||||
{
|
||||
field: 'state.duration_ms',
|
||||
|
@ -157,6 +168,7 @@ export const ErrorsList = ({
|
|||
<div>
|
||||
<EuiSpacer />
|
||||
<EuiInMemoryTable
|
||||
tableLayout="auto"
|
||||
tableCaption={ERRORS_LIST_LABEL}
|
||||
loading={loading}
|
||||
items={errorStates}
|
||||
|
@ -216,3 +228,7 @@ const FAILED_STEP_LABEL = i18n.translate('xpack.synthetics.failedStep.label', {
|
|||
const TIMESTAMP_LABEL = i18n.translate('xpack.synthetics.timestamp.label', {
|
||||
defaultMessage: '@timestamp',
|
||||
});
|
||||
|
||||
const ACTIVE_LABEL = i18n.translate('xpack.synthetics.active.label', {
|
||||
defaultMessage: 'Active',
|
||||
});
|
||||
|
|
|
@ -15,7 +15,13 @@ import { useUrlParams } from '../../../hooks';
|
|||
import { useMonitorQueryId } from '../hooks/use_monitor_query_id';
|
||||
import { ClientPluginsStart } from '../../../../../plugin';
|
||||
|
||||
export const MonitorFailedTests = ({ time }: { time: { to: string; from: string } }) => {
|
||||
export const MonitorFailedTests = ({
|
||||
time,
|
||||
allowBrushing = true,
|
||||
}: {
|
||||
time: { to: string; from: string };
|
||||
allowBrushing?: boolean;
|
||||
}) => {
|
||||
const { observability } = useKibana<ClientPluginsStart>().services;
|
||||
|
||||
const { ExploratoryViewEmbeddable } = observability;
|
||||
|
@ -41,7 +47,8 @@ export const MonitorFailedTests = ({ time }: { time: { to: string; from: string
|
|||
{
|
||||
time,
|
||||
reportDefinitions: {
|
||||
...(monitorId ? { 'monitor.id': [monitorId] } : { 'state.id': [errorStateId] }),
|
||||
...(monitorId ? { 'monitor.id': [monitorId] } : {}),
|
||||
...(errorStateId ? { 'state.id': [errorStateId] } : {}),
|
||||
},
|
||||
dataType: 'synthetics',
|
||||
selectedMetricField: 'failed_tests',
|
||||
|
@ -49,21 +56,25 @@ export const MonitorFailedTests = ({ time }: { time: { to: string; from: string
|
|||
},
|
||||
]}
|
||||
onBrushEnd={({ range }) => {
|
||||
updateUrl({
|
||||
dateRangeStart: moment(range[0]).toISOString(),
|
||||
dateRangeEnd: moment(range[1]).toISOString(),
|
||||
});
|
||||
if (allowBrushing) {
|
||||
updateUrl({
|
||||
dateRangeStart: moment(range[0]).toISOString(),
|
||||
dateRangeEnd: moment(range[1]).toISOString(),
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow style={{ marginLeft: 10 }}>
|
||||
<EuiHealth color="danger">{FAILED_TESTS_LABEL}</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued" size="s">
|
||||
{BRUSH_LABEL}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
{allowBrushing && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued" size="s">
|
||||
{BRUSH_LABEL}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -23,9 +23,9 @@ import { ErrorsTabContent } from './errors_tab_content';
|
|||
export const MonitorErrors = () => {
|
||||
const { errorStates, loading, data } = useMonitorErrors();
|
||||
|
||||
const initialLoading = loading && !data;
|
||||
const initialLoading = !data;
|
||||
|
||||
const emptyState = !loading && errorStates.length === 0;
|
||||
const emptyState = !loading && errorStates && errorStates?.length === 0;
|
||||
|
||||
const redirect = useMonitorDetailsPage();
|
||||
if (redirect) {
|
||||
|
@ -39,7 +39,7 @@ export const MonitorErrors = () => {
|
|||
{initialLoading && <LoadingErrors />}
|
||||
{emptyState && <EmptyErrors />}
|
||||
<div style={{ visibility: initialLoading || emptyState ? 'collapse' : 'initial' }}>
|
||||
<ErrorsTabContent errorStates={errorStates} loading={loading} />
|
||||
<ErrorsTabContent errorStates={errorStates ?? []} loading={loading} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -9,16 +9,16 @@ import { formatTestDuration } from './test_time_formats';
|
|||
|
||||
describe('formatTestDuration', () => {
|
||||
it.each`
|
||||
duration | expected | isMilli
|
||||
${undefined} | ${'0 ms'} | ${undefined}
|
||||
${120_000_000} | ${'2 min'} | ${undefined}
|
||||
${6_200_000} | ${'6.2 s'} | ${false}
|
||||
${500_000} | ${'500 ms'} | ${undefined}
|
||||
${100} | ${'0 ms'} | ${undefined}
|
||||
${undefined} | ${'0 ms'} | ${true}
|
||||
${600_000} | ${'10 min'} | ${true}
|
||||
${6_200} | ${'6.2 s'} | ${true}
|
||||
${500} | ${'500 ms'} | ${true}
|
||||
duration | expected | isMilli
|
||||
${undefined} | ${'0 ms'} | ${undefined}
|
||||
${120_000_000} | ${'2 mins'} | ${undefined}
|
||||
${6_200_000} | ${'6.2 sec'} | ${false}
|
||||
${500_000} | ${'500 ms'} | ${undefined}
|
||||
${100} | ${'0 ms'} | ${undefined}
|
||||
${undefined} | ${'0 ms'} | ${true}
|
||||
${600_000} | ${'10 mins'} | ${true}
|
||||
${6_200} | ${'6.2 sec'} | ${true}
|
||||
${500} | ${'500 ms'} | ${true}
|
||||
`(
|
||||
'returns $expected when `duration` is $duration and `isMilli` $isMilli',
|
||||
({
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibanaDateFormat } from '../../../../hooks/use_kibana_date_format';
|
||||
|
||||
/**
|
||||
|
@ -16,19 +17,40 @@ import { useKibanaDateFormat } from '../../../../hooks/use_kibana_date_format';
|
|||
export const formatTestDuration = (duration = 0, isMilli = false) => {
|
||||
const secs = isMilli ? duration / 1e3 : duration / 1e6;
|
||||
|
||||
const hours = Math.floor(secs / 3600);
|
||||
|
||||
if (hours >= 1) {
|
||||
return i18n.translate('xpack.synthetics.errorDetails.errorDuration.hours', {
|
||||
defaultMessage: '{value} hours',
|
||||
values: { value: hours },
|
||||
});
|
||||
}
|
||||
|
||||
if (secs >= 60) {
|
||||
return `${parseFloat((secs / 60).toFixed(1))} min`;
|
||||
return i18n.translate('xpack.synthetics.errorDetails.errorDuration.minutes', {
|
||||
defaultMessage: '{value} mins',
|
||||
values: { value: parseFloat((secs / 60).toFixed(1)) },
|
||||
});
|
||||
}
|
||||
|
||||
if (secs >= 1) {
|
||||
return `${parseFloat(secs.toFixed(1))} s`;
|
||||
return i18n.translate('xpack.synthetics.errorDetails.errorDuration.seconds', {
|
||||
defaultMessage: '{value} sec',
|
||||
values: { value: parseFloat(secs.toFixed(1)) },
|
||||
});
|
||||
}
|
||||
|
||||
if (isMilli) {
|
||||
return `${duration.toFixed(0)} ms`;
|
||||
return i18n.translate('xpack.synthetics.errorDetails.errorDuration.milliseconds', {
|
||||
defaultMessage: '{value} ms',
|
||||
values: { value: duration.toFixed(0) },
|
||||
});
|
||||
}
|
||||
|
||||
return `${(duration / 1000).toFixed(0)} ms`;
|
||||
return i18n.translate('xpack.synthetics.errorDetails.errorDuration.microseconds', {
|
||||
defaultMessage: '{value} ms',
|
||||
values: { value: (duration / 1000).toFixed(0) },
|
||||
});
|
||||
};
|
||||
|
||||
export function formatTestRunAt(timestamp: string, format: string) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue