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.9`: - [[Synthetics] Fix active error state (#160818)](https://github.com/elastic/kibana/pull/160818) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Justin Kambic","email":"jk@elastic.co"},"sourceCommit":{"committedDate":"2023-07-13T14:49:53Z","message":"[Synthetics] Fix active error state (#160818)\n\nCo-authored-by: Shahzad <shahzad31comp@gmail.com>","sha":"f142cd25f4b19ce1524ec466c481cbe128d1c426","branchLabelMapping":{"^v8.10.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:fix","Team:uptime","v8.9.0","v8.10.0"],"number":160818,"url":"https://github.com/elastic/kibana/pull/160818","mergeCommit":{"message":"[Synthetics] Fix active error state (#160818)\n\nCo-authored-by: Shahzad <shahzad31comp@gmail.com>","sha":"f142cd25f4b19ce1524ec466c481cbe128d1c426"}},"sourceBranch":"main","suggestedTargetBranches":["8.9"],"targetPullRequestStates":[{"branch":"8.9","label":"v8.9.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.10.0","labelRegex":"^v8.10.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/160818","number":160818,"mergeCommit":{"message":"[Synthetics] Fix active error state (#160818)\n\nCo-authored-by: Shahzad <shahzad31comp@gmail.com>","sha":"f142cd25f4b19ce1524ec466c481cbe128d1c426"}}]}] BACKPORT--> Co-authored-by: Justin Kambic <jk@elastic.co>
This commit is contained in:
parent
e72edd29a1
commit
69248aff08
5 changed files with 66 additions and 99 deletions
|
@ -4,7 +4,6 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import moment from 'moment';
|
||||
import { useTimeZone } from '@kbn/observability-shared-plugin/public';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useMemo } from 'react';
|
||||
|
@ -49,11 +48,6 @@ export function useMonitorErrors(monitorIdArg?: string) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'state.up': 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
config_id: monitorIdArg ?? monitorId,
|
||||
|
@ -69,7 +63,7 @@ export function useMonitorErrors(monitorIdArg?: string) {
|
|||
},
|
||||
sort: [{ 'state.started_at': 'desc' }],
|
||||
aggs: {
|
||||
errorStates: {
|
||||
states: {
|
||||
terms: {
|
||||
field: 'state.id',
|
||||
size: 10000,
|
||||
|
@ -84,6 +78,13 @@ export function useMonitorErrors(monitorIdArg?: string) {
|
|||
},
|
||||
},
|
||||
},
|
||||
latest: {
|
||||
top_hits: {
|
||||
size: 1,
|
||||
_source: ['monitor.status'],
|
||||
sort: [{ '@timestamp': 'desc' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -95,27 +96,35 @@ export function useMonitorErrors(monitorIdArg?: string) {
|
|||
);
|
||||
|
||||
return useMemo(() => {
|
||||
const errorStates = data?.aggregations?.errorStates.buckets?.map((loc) => {
|
||||
return loc.summary.hits.hits?.[0]._source as PingState;
|
||||
});
|
||||
const defaultValues = { upStates: [], errorStates: [] };
|
||||
// re-bucket states into error/up
|
||||
// including the `up` states is useful for determining error duration
|
||||
const { errorStates, upStates } =
|
||||
data?.aggregations?.states.buckets.reduce<{
|
||||
upStates: PingState[];
|
||||
errorStates: PingState[];
|
||||
}>((prev, cur) => {
|
||||
const source = cur.summary.hits.hits?.[0]._source as PingState | undefined;
|
||||
if (source?.state.up === 0) {
|
||||
prev.errorStates.push(source as PingState);
|
||||
} else if (!!source?.state.up && source.state.up >= 1) {
|
||||
prev.upStates.push(source as PingState);
|
||||
}
|
||||
return prev;
|
||||
}, defaultValues) ?? defaultValues;
|
||||
|
||||
const hasActiveError: boolean =
|
||||
errorStates?.some((errorState) => isActiveState(errorState)) || false;
|
||||
data?.aggregations?.latest.hits.hits.length === 1 &&
|
||||
(data?.aggregations?.latest.hits.hits[0]._source as { monitor: { status: string } }).monitor
|
||||
.status === 'down' &&
|
||||
!!errorStates?.length;
|
||||
|
||||
return {
|
||||
errorStates,
|
||||
upStates,
|
||||
loading,
|
||||
data,
|
||||
hasActiveError,
|
||||
};
|
||||
}, [data, loading]);
|
||||
}
|
||||
|
||||
export const isActiveState = (item: PingState) => {
|
||||
const timestamp = item['@timestamp'];
|
||||
const interval = moment(item.monitor.timespan?.lt).diff(
|
||||
moment(item.monitor.timespan?.gte),
|
||||
'milliseconds'
|
||||
);
|
||||
return moment().diff(moment(timestamp), 'milliseconds') < interval;
|
||||
};
|
||||
|
|
|
@ -1,65 +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 * as useMonitorErrors from '../hooks/use_monitor_errors';
|
||||
import { isErrorActive } from './errors_list';
|
||||
|
||||
describe('isErrorActive', () => {
|
||||
let isActiveSpy: jest.SpyInstance;
|
||||
beforeEach(() => {
|
||||
isActiveSpy = jest.spyOn(useMonitorErrors, 'isActiveState').mockReturnValue(true);
|
||||
});
|
||||
const item = {
|
||||
'@timestamp': '2023-05-04T00:00:00.000Z',
|
||||
timestamp: '2023-05-04T00:00:00.000Z',
|
||||
docId: 'SGIQ6IcBTfgfaiALCdZ8',
|
||||
error: { message: 'Encountered an error and made this unhelpful message.' },
|
||||
state: {
|
||||
duration_ms: '415801',
|
||||
checks: 8,
|
||||
ends: null,
|
||||
started_at: '2023-05-04T18:32:41.111671462Z',
|
||||
id: 'foo',
|
||||
up: 8,
|
||||
down: 0,
|
||||
status: 'up',
|
||||
},
|
||||
monitor: {
|
||||
id: 'foo',
|
||||
status: 'up',
|
||||
type: 'browser',
|
||||
check_group: 'f01850cc-eaaa-11ed-887d-caddd792d648',
|
||||
timespan: { gte: '2023-05-04T00:00:00.000Z', lt: '2023-05-04T01:00:00.000Z' },
|
||||
},
|
||||
};
|
||||
const lastErrorId = 'foo';
|
||||
const latestPingStatus = 'down';
|
||||
|
||||
it('returns true if error is active', () => {
|
||||
const result = isErrorActive(item, lastErrorId, latestPingStatus);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if error is not active', () => {
|
||||
isActiveSpy.mockReturnValue(false);
|
||||
expect(
|
||||
isErrorActive(
|
||||
{ ...item, '@timestamp': '2023-05-04T02:00:00.000Z' },
|
||||
lastErrorId,
|
||||
latestPingStatus
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if `lastErrorId` does not match `item.state.id`', () => {
|
||||
expect(isErrorActive(item, 'bar', latestPingStatus)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if latestPingStatus is `up`', () => {
|
||||
expect(isErrorActive(item, lastErrorId, 'up')).toBe(false);
|
||||
});
|
||||
});
|
|
@ -24,21 +24,31 @@ import { Ping, PingState } from '../../../../../../common/runtime_types';
|
|||
import { useErrorFailedStep } from '../hooks/use_error_failed_step';
|
||||
import { formatTestDuration } from '../../../utils/monitor_test_result/test_time_formats';
|
||||
import { useDateFormat } from '../../../../../hooks/use_date_format';
|
||||
import { isActiveState } from '../hooks/use_monitor_errors';
|
||||
import { useMonitorLatestPing } from '../hooks/use_monitor_latest_ping';
|
||||
|
||||
export function isErrorActive(item: PingState, lastErrorId?: string, latestPingStatus?: string) {
|
||||
// if the error is the most recent, `isActiveState`, and the monitor
|
||||
// is not yet back up, label the error as active
|
||||
return isActiveState(item) && lastErrorId === item.state.id && latestPingStatus !== 'up';
|
||||
function isErrorActive(lastError: PingState, currentError: PingState, latestPing?: Ping) {
|
||||
return (
|
||||
latestPing?.monitor.status === 'down' &&
|
||||
lastError['@timestamp'] === currentError['@timestamp'] &&
|
||||
typeof currentError['@timestamp'] !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
function getNextUpStateForResolvedError(errorState: PingState, upStates: PingState[]) {
|
||||
for (const upState of upStates) {
|
||||
if (moment(upState.state.started_at).valueOf() > moment(errorState['@timestamp']).valueOf())
|
||||
return upState;
|
||||
}
|
||||
}
|
||||
|
||||
export const ErrorsList = ({
|
||||
errorStates,
|
||||
upStates,
|
||||
loading,
|
||||
location,
|
||||
}: {
|
||||
errorStates: PingState[];
|
||||
upStates: PingState[];
|
||||
loading: boolean;
|
||||
location: ReturnType<typeof useSelectedLocation>;
|
||||
}) => {
|
||||
|
@ -64,7 +74,6 @@ export const ErrorsList = ({
|
|||
const lastErrorTestRun = errorStates?.sort((a, b) => {
|
||||
return moment(b.state.started_at).valueOf() - moment(a.state.started_at).valueOf();
|
||||
})?.[0];
|
||||
|
||||
const isTabletOrGreater = useIsWithinMinBreakpoint('s');
|
||||
|
||||
const columns = [
|
||||
|
@ -83,7 +92,8 @@ export const ErrorsList = ({
|
|||
locationId={location?.id}
|
||||
/>
|
||||
);
|
||||
if (isErrorActive(item, lastErrorTestRun?.state.id, latestPing?.monitor.status)) {
|
||||
|
||||
if (isErrorActive(lastErrorTestRun, item, latestPing)) {
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="m" alignItems="center" wrap={true}>
|
||||
<EuiFlexItem grow={false} className="eui-textNoWrap">
|
||||
|
@ -118,7 +128,7 @@ export const ErrorsList = ({
|
|||
}
|
||||
return failedStep.synthetics?.step?.name;
|
||||
},
|
||||
render: (value: string, item: PingState) => {
|
||||
render: (value: string) => {
|
||||
const failedStep = failedSteps.find((step) => step.monitor.check_group === value);
|
||||
if (!failedStep) {
|
||||
return <>--</>;
|
||||
|
@ -142,19 +152,20 @@ export const ErrorsList = ({
|
|||
align: 'right' as const,
|
||||
sortable: true,
|
||||
render: (value: string, item: PingState) => {
|
||||
const isActive = isActiveState(item);
|
||||
let activeDuration = 0;
|
||||
if (item.monitor.timespan) {
|
||||
const diff = moment(item.monitor.timespan.lt).diff(
|
||||
moment(item.monitor.timespan.gte),
|
||||
'millisecond'
|
||||
);
|
||||
if (isActive) {
|
||||
if (isErrorActive(lastErrorTestRun, item, latestPing)) {
|
||||
const currentDiff = moment().diff(item['@timestamp']);
|
||||
|
||||
activeDuration = currentDiff < diff ? currentDiff : diff;
|
||||
} else {
|
||||
activeDuration = diff;
|
||||
const resolvedState = getNextUpStateForResolvedError(item, upStates);
|
||||
|
||||
activeDuration = moment(resolvedState?.state.started_at).diff(item['@timestamp']) ?? 0;
|
||||
}
|
||||
}
|
||||
return (
|
||||
|
|
|
@ -23,8 +23,10 @@ export const ErrorsTabContent = ({
|
|||
errorStates,
|
||||
loading,
|
||||
location,
|
||||
upStates,
|
||||
}: {
|
||||
errorStates: PingState[];
|
||||
upStates: PingState[];
|
||||
loading: boolean;
|
||||
location: ReturnType<typeof useSelectedLocation>;
|
||||
}) => {
|
||||
|
@ -69,7 +71,12 @@ export const ErrorsTabContent = ({
|
|||
<EuiFlexGroup gutterSize="m" wrap={true}>
|
||||
<EuiFlexItem grow={2} css={{ minWidth: 260 }}>
|
||||
<PanelWithTitle title={ERRORS_LABEL}>
|
||||
<ErrorsList location={location} errorStates={errorStates} loading={loading} />
|
||||
<ErrorsList
|
||||
location={location}
|
||||
errorStates={errorStates}
|
||||
upStates={upStates}
|
||||
loading={loading}
|
||||
/>
|
||||
</PanelWithTitle>
|
||||
</EuiFlexItem>
|
||||
<FailedTestsByStep time={time} />
|
||||
|
|
|
@ -23,7 +23,7 @@ import { MonitorPendingWrapper } from '../monitor_pending_wrapper';
|
|||
import { useSelectedLocation } from '../hooks/use_selected_location';
|
||||
|
||||
export const MonitorErrors = () => {
|
||||
const { errorStates, loading, data } = useMonitorErrors();
|
||||
const { errorStates, upStates, loading, data } = useMonitorErrors();
|
||||
const location = useSelectedLocation();
|
||||
|
||||
const initialLoading = !data;
|
||||
|
@ -42,7 +42,12 @@ export const MonitorErrors = () => {
|
|||
{initialLoading && <LoadingErrors />}
|
||||
{emptyState && <EmptyErrors />}
|
||||
<div style={{ visibility: initialLoading || emptyState ? 'collapse' : 'initial' }}>
|
||||
<ErrorsTabContent location={location} errorStates={errorStates ?? []} loading={loading} />
|
||||
<ErrorsTabContent
|
||||
location={location}
|
||||
errorStates={errorStates}
|
||||
upStates={upStates}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</MonitorPendingWrapper>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue