[8.18] [Synthetics] Fix overview error popover !! (#211431) (#212620)

# Backport

This will backport the following commits from `main` to `8.18`:
- [[Synthetics] Fix overview error popover !!
(#211431)](https://github.com/elastic/kibana/pull/211431)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT
[{"author":{"name":"Shahzad","email":"shahzad31comp@gmail.com"},"sourceCommit":{"committedDate":"2025-02-21T16:38:54Z","message":"[Synthetics]
Fix overview error popover !! (#211431)\n\n## Summary\n\nFix overview
error popover !!\n\nPings aren't being returned as part of overview data
anymore, so had to\nadd redux actions to fetch it separately via an
existing API\n\nFixes
https://github.com/elastic/kibana/issues/211745\n\n\n<img width=\"1728\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/2244948f-e42d-443d-b6e7-42e0a72b1bfa\"\n/>\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by:
Justin Kambic
<jk@elastic.co>","sha":"aaf73ff5f67a9163773ff43868f329e3b5693242","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:obs-ux-management","backport:version","v8.18.0","v9.1.0"],"title":"[Synthetics]
Fix overview error popover
!!","number":211431,"url":"https://github.com/elastic/kibana/pull/211431","mergeCommit":{"message":"[Synthetics]
Fix overview error popover !! (#211431)\n\n## Summary\n\nFix overview
error popover !!\n\nPings aren't being returned as part of overview data
anymore, so had to\nadd redux actions to fetch it separately via an
existing API\n\nFixes
https://github.com/elastic/kibana/issues/211745\n\n\n<img width=\"1728\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/2244948f-e42d-443d-b6e7-42e0a72b1bfa\"\n/>\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by:
Justin Kambic
<jk@elastic.co>","sha":"aaf73ff5f67a9163773ff43868f329e3b5693242"}},"sourceBranch":"main","suggestedTargetBranches":["8.18"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/212123","number":212123,"state":"MERGED","mergeCommit":{"sha":"83e37babbff8e702c51c2746c790930510f4ccb4","message":"[9.0]
[Synthetics] Fix overview error popover !! (#211431) (#212123)\n\n#
Backport\n\nThis will backport the following commits from `main` to
`9.0`:\n- [[Synthetics] Fix overview error popover
!!\n(#211431)](https://github.com/elastic/kibana/pull/211431)\n\n\n\n###
Questions ?\nPlease refer to the [Backport
tool\ndocumentation](https://github.com/sorenlouv/backport)\n\n\n\nCo-authored-by:
Shahzad
<shahzad31comp@gmail.com>"}},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/211431","number":211431,"mergeCommit":{"message":"[Synthetics]
Fix overview error popover !! (#211431)\n\n## Summary\n\nFix overview
error popover !!\n\nPings aren't being returned as part of overview data
anymore, so had to\nadd redux actions to fetch it separately via an
existing API\n\nFixes
https://github.com/elastic/kibana/issues/211745\n\n\n<img width=\"1728\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/2244948f-e42d-443d-b6e7-42e0a72b1bfa\"\n/>\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by:
Justin Kambic
<jk@elastic.co>","sha":"aaf73ff5f67a9163773ff43868f329e3b5693242"}}]}]
BACKPORT-->

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Shahzad 2025-02-27 14:51:52 +01:00 committed by GitHub
parent a00807b7a9
commit 8dd76bedd8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 234 additions and 99 deletions

View file

@ -152,7 +152,6 @@ module.exports = {
/x-pack[\/\\]solutions[\/\\]observability[\/\\]plugins[\/\\]synthetics[\/\\]public[\/\\]apps[\/\\]synthetics[\/\\]components[\/\\]monitor_add_edit[\/\\]fields[\/\\]key_value_field.tsx/,
/x-pack[\/\\]solutions[\/\\]observability[\/\\]plugins[\/\\]synthetics[\/\\]public[\/\\]apps[\/\\]synthetics[\/\\]components[\/\\]monitors_page[\/\\]overview[\/\\]overview[\/\\]actions_popover.tsx/,
/x-pack[\/\\]solutions[\/\\]observability[\/\\]plugins[\/\\]synthetics[\/\\]public[\/\\]apps[\/\\]synthetics[\/\\]components[\/\\]monitors_page[\/\\]overview[\/\\]overview[\/\\]grid_by_group[\/\\]grid_items_by_group.tsx/,
/x-pack[\/\\]solutions[\/\\]observability[\/\\]plugins[\/\\]synthetics[\/\\]public[\/\\]apps[\/\\]synthetics[\/\\]components[\/\\]monitors_page[\/\\]overview[\/\\]overview[\/\\]metric_item_icon.tsx/,
/x-pack[\/\\]solutions[\/\\]observability[\/\\]plugins[\/\\]synthetics[\/\\]public[\/\\]apps[\/\\]synthetics[\/\\]components[\/\\]settings[\/\\]alerting_defaults[\/\\]connector_field.tsx/,
/x-pack[\/\\]solutions[\/\\]observability[\/\\]plugins[\/\\]synthetics[\/\\]public[\/\\]apps[\/\\]synthetics[\/\\]components[\/\\]test_now_mode[\/\\]browser[\/\\]browser_test_results.tsx/,
/x-pack[\/\\]solutions[\/\\]observability[\/\\]plugins[\/\\]synthetics[\/\\]public[\/\\]apps[\/\\]synthetics[\/\\]components[\/\\]test_now_mode[\/\\]simple[\/\\]ping_list[\/\\]columns[\/\\]ping_error.tsx/,

View file

@ -51,7 +51,6 @@ export const OverviewStatusMetaDataCodec = t.intersection([
t.partial({
projectId: t.string,
updated_at: t.string,
ping: OverviewPingCodec,
timestamp: t.string,
spaceId: t.string,
}),

View file

@ -25,7 +25,7 @@ import { OverviewLoader } from '../overview_loader';
import { useFilteredGroupMonitors } from './use_filtered_group_monitors';
import { OverviewStatusMetaData } from '../../types';
import { selectOverviewStatus } from '../../../../../state/overview_status';
import { MetricItem } from '../metric_item';
import { MetricItem } from '../metric_item/metric_item';
const PER_ROW = 4;
const DEFAULT_ROW_SIZE = 2;

View file

@ -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 { EuiButtonIcon, useEuiShadow, useEuiTheme } from '@elastic/eui';
import * as React from 'react';
import { useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { i18n } from '@kbn/i18n';
import { css } from '@emotion/react';
import { useSyntheticsSettingsContext } from '../../../../../contexts';
import { selectErrorPopoverState, toggleErrorPopoverOpen } from '../../../../../state';
export const MetricErrorIcon = ({ configIdByLocation }: { configIdByLocation: string }) => {
const isPopoverOpen = useSelector(selectErrorPopoverState);
const dispatch = useDispatch();
const setIsPopoverOpen = () => {
dispatch(toggleErrorPopoverOpen(configIdByLocation));
};
const timer = useRef<NodeJS.Timeout | null>(null);
const euiShadow = useEuiShadow('s');
const theme = useEuiTheme().euiTheme;
const { darkMode } = useSyntheticsSettingsContext();
return (
<div
css={css`
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
width: 32px;
height: 32px;
background: ${darkMode ? theme.colors.backgroundBaseSubdued : theme.colors.lightestShade};
border: 1px solid ${darkMode ? theme.colors.darkShade : theme.colors.lightShade};
box-shadow: ${euiShadow};
border-radius: 16px;
flex: none;
order: 0;
flex-grow: 0;
`}
onMouseEnter={() => {
// show popover with delay
if (timer.current) {
clearTimeout(timer.current);
}
timer.current = setTimeout(() => {
setIsPopoverOpen();
}, 300);
}}
onMouseLeave={() => {
if (isPopoverOpen) {
return;
} else if (timer.current) {
clearTimeout(timer.current);
}
}}
onClick={() => {
if (configIdByLocation === isPopoverOpen) {
dispatch(toggleErrorPopoverOpen(null));
} else {
dispatch(toggleErrorPopoverOpen(configIdByLocation));
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
if (configIdByLocation === isPopoverOpen) {
dispatch(toggleErrorPopoverOpen(null));
} else {
dispatch(toggleErrorPopoverOpen(configIdByLocation));
}
}
}}
>
<EuiButtonIcon
data-test-subj="syntheticsMetricItemIconButton"
iconType="warning"
color="danger"
size="m"
aria-label={ERROR_DETAILS}
/>
</div>
);
};
const ERROR_DETAILS = i18n.translate('xpack.synthetics.errorDetails.label', {
defaultMessage: 'Error details',
});

View file

@ -15,24 +15,24 @@ import { useTheme } from '@kbn/observability-shared-plugin/public';
import moment from 'moment';
import { useSelector, useDispatch } from 'react-redux';
import { FlyoutParamProps } from './types';
import { MetricItemBody } from './metric_item/metric_item_body';
import { FlyoutParamProps } from '../types';
import { MetricItemBody } from './metric_item_body';
import {
selectErrorPopoverState,
selectOverviewTrends,
toggleErrorPopoverOpen,
} from '../../../../state';
import { useLocationName, useStatusByLocationOverview } from '../../../../hooks';
import { formatDuration } from '../../../../utils/formatting';
import { OverviewStatusMetaData } from '../../../../../../../common/runtime_types';
import { ActionsPopover } from './actions_popover';
} from '../../../../../state';
import { useLocationName, useStatusByLocationOverview } from '../../../../../hooks';
import { formatDuration } from '../../../../../utils/formatting';
import { OverviewStatusMetaData } from '../../../../../../../../common/runtime_types';
import { ActionsPopover } from '../actions_popover';
import {
hideTestNowFlyoutAction,
manualTestRunInProgressSelector,
toggleTestNowFlyoutAction,
} from '../../../../state/manual_test_runs';
} from '../../../../../state/manual_test_runs';
import { MetricItemIcon } from './metric_item_icon';
import { MetricItemExtra } from './metric_item/metric_item_extra';
import { MetricItemExtra } from './metric_item_extra';
const METRIC_ITEM_HEIGHT = 160;
@ -69,7 +69,7 @@ export const MetricItem = ({
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const isErrorPopoverOpen = useSelector(selectErrorPopoverState);
const locationName = useLocationName(monitor);
const { status, timestamp, ping, configIdByLocation } = useStatusByLocationOverview({
const { status, timestamp, configIdByLocation } = useStatusByLocationOverview({
configId: monitor.configId,
locationId: monitor.locationId,
});
@ -184,7 +184,6 @@ export const MetricItem = ({
<MetricItemIcon
monitor={monitor}
status={status}
ping={ping}
timestamp={timestamp}
configIdByLocation={configIdByLocation}
/>

View file

@ -14,23 +14,24 @@ import {
EuiPopoverTitle,
EuiPopoverFooter,
EuiButton,
useEuiShadow,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiLink,
EuiSpacer,
EuiSkeletonText,
} from '@elastic/eui';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import styled from '@emotion/styled';
import { i18n } from '@kbn/i18n';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { useRef } from 'react';
import { selectErrorPopoverState, toggleErrorPopoverOpen } from '../../../../state';
import { useErrorDetailsLink } from '../../../common/links/error_details_link';
import { OverviewPing, OverviewStatusMetaData } from '../../../../../../../common/runtime_types';
import { isTestRunning, manualTestRunSelector } from '../../../../state/manual_test_runs';
import { useDateFormat } from '../../../../../../hooks/use_date_format';
import { MetricErrorIcon } from './metric_error_icon';
import { OverviewStatusMetaData } from '../../../../../../../../common/runtime_types';
import { isTestRunning, manualTestRunSelector } from '../../../../../state/manual_test_runs';
import { selectErrorPopoverState, toggleErrorPopoverOpen } from '../../../../../state';
import { useErrorDetailsLink } from '../../../../common/links/error_details_link';
import { useDateFormat } from '../../../../../../../hooks/use_date_format';
import { useLatestError } from './use_latest_error';
const Container = styled.div`
display: inline-block;
@ -43,7 +44,6 @@ const Container = styled.div`
export const MetricItemIcon = ({
monitor,
status,
ping,
timestamp,
configIdByLocation,
}: {
@ -51,27 +51,24 @@ export const MetricItemIcon = ({
status: string;
configIdByLocation: string;
timestamp?: string;
ping?: OverviewPing;
}) => {
const testNowRun = useSelector(manualTestRunSelector(monitor.configId));
const isPopoverOpen = useSelector(selectErrorPopoverState);
const { latestPing } = useLatestError({
configIdByLocation,
monitorId: monitor.configId,
locationLabel: monitor.locationLabel,
});
const dispatch = useDispatch();
const timer = useRef<NodeJS.Timeout | null>(null);
const setIsPopoverOpen = () => {
dispatch(toggleErrorPopoverOpen(configIdByLocation));
};
const inProgress = isTestRunning(testNowRun);
const errorLink = useErrorDetailsLink({
configId: monitor.configId,
stateId: ping?.state?.id!,
stateId: latestPing?.state?.id!,
locationId: monitor.locationId,
});
const euiShadow = useEuiShadow('s');
const formatter = useDateFormat();
const testTime = formatter(timestamp);
@ -94,42 +91,7 @@ export const MetricItemIcon = ({
return (
<Container>
<EuiPopover
button={
<StyledIcon
onMouseEnter={() => {
// show popover with delay
if (timer.current) {
clearTimeout(timer.current);
}
timer.current = setTimeout(() => {
setIsPopoverOpen();
}, 300);
}}
onMouseLeave={() => {
if (isPopoverOpen) {
return;
} else if (timer.current) {
clearTimeout(timer.current);
}
}}
boxShadow={euiShadow}
onClick={() => {
if (configIdByLocation === isPopoverOpen) {
dispatch(toggleErrorPopoverOpen(null));
} else {
dispatch(toggleErrorPopoverOpen(configIdByLocation));
}
}}
>
<EuiButtonIcon
data-test-subj="syntheticsMetricItemIconButton"
iconType="warning"
color="danger"
size="m"
aria-label={ERROR_DETAILS}
/>
</StyledIcon>
}
button={<MetricErrorIcon configIdByLocation={configIdByLocation} />}
isOpen={configIdByLocation === isPopoverOpen}
closePopover={closePopover}
anchorPosition="upCenter"
@ -145,27 +107,43 @@ export const MetricItemIcon = ({
data-test-subj="syntheticsMetricItemIconButton"
iconType="cross"
onClick={closePopover}
aria-label={i18n.translate(
'xpack.synthetics.metricItemIcon.euiButtonIcon.closePopover',
{
defaultMessage: 'Close popover',
}
)}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPopoverTitle>
<div style={{ width: '300px' }}>
{ping?.url?.full && (
<div style={{ width: '300px', overflowWrap: 'break-word' }}>
{latestPing?.url?.full && (
<>
{i18n.translate('xpack.synthetics.metricItemIcon.div.urlLabel', {
defaultMessage: 'URL: ',
})}
<EuiLink
data-test-subj="syntheticsMetricItemIconLink"
href={ping.url.full}
href={latestPing.url.full}
target="_blank"
>
{ping.url.full}
{latestPing.url.full}
</EuiLink>
<EuiSpacer size="s" />
</>
)}
<EuiCallOut title={ping?.error?.message} color="danger" iconType="warning" />
<EuiCallOut
title={
latestPing?.error?.message ? (
latestPing?.error?.message
) : (
<EuiSkeletonText lines={2} />
)
}
color="danger"
iconType="warning"
/>
</div>
<EuiPopoverFooter>
<EuiButton
@ -181,14 +159,14 @@ export const MetricItemIcon = ({
</Container>
);
} else {
if (ping?.url) {
if (latestPing?.url) {
return (
<Container>
<EuiButtonIcon
title={ping.url.full}
title={latestPing.url.full}
color="text"
data-test-subj="syntheticsMetricItemIconButton"
href={ping.url.full}
href={latestPing.url.full}
iconType="link"
target="_blank"
aria-label={i18n.translate('xpack.synthetics.metricItemIcon.euiButtonIcon.monitorUrl', {
@ -209,22 +187,3 @@ const ERROR_DETAILS = i18n.translate('xpack.synthetics.errorDetails.label', {
const TEST_IN_PROGRESS = i18n.translate('xpack.synthetics.inProgress.label', {
defaultMessage: 'Manual test run is in progress.',
});
const StyledIcon = euiStyled.div<{ boxShadow: string }>`
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
width: 32px;
height: 32px;
background: ${({ theme }) =>
theme.darkMode ? theme.eui.euiColorDarkestShade : theme.eui.euiColorLightestShade};
border: 1px solid ${({ theme }) =>
theme.darkMode ? theme.eui.euiColorDarkShade : theme.eui.euiColorLightShade};
${({ boxShadow }) => boxShadow}
border-radius: 16px;
flex: none;
order: 0;
flex-grow: 0;
`;

View file

@ -0,0 +1,41 @@
/*
* 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 { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useSyntheticsRefreshContext } from '../../../../../contexts';
import {
getMonitorLastErrorRunAction,
selectErrorPopoverState,
selectLastErrorRunMetadata,
} from '../../../../../state';
interface UseMonitorLatestPingParams {
monitorId: string;
locationLabel: string;
configIdByLocation: string;
}
export const useLatestError = ({
monitorId,
locationLabel,
configIdByLocation,
}: UseMonitorLatestPingParams) => {
const dispatch = useDispatch();
const { lastRefresh } = useSyntheticsRefreshContext();
const isPopoverOpen = useSelector(selectErrorPopoverState);
const { data: latestPing, loading } = useSelector(selectLastErrorRunMetadata);
useEffect(() => {
if (monitorId && locationLabel && isPopoverOpen === configIdByLocation) {
dispatch(getMonitorLastErrorRunAction.get({ monitorId, locationLabel }));
}
}, [dispatch, monitorId, locationLabel, lastRefresh, isPopoverOpen, configIdByLocation]);
return { loading, latestPing };
};

View file

@ -18,6 +18,7 @@ import {
EuiAutoSizer,
EuiAutoSize,
} from '@elastic/eui';
import { MetricItem } from './metric_item/metric_item';
import { ShowAllSpaces } from '../../common/show_all_spaces';
import { OverviewStatusMetaData } from '../../../../../../../common/runtime_types';
import { quietFetchOverviewStatusAction } from '../../../../state/overview_status';
@ -40,7 +41,6 @@ import { SortFields } from './sort_fields';
import { NoMonitorsFound } from '../../common/no_monitors_found';
import { MonitorDetailFlyout } from './monitor_detail_flyout';
import { useSyntheticsRefreshContext } from '../../../../contexts';
import { MetricItem } from './metric_item';
import { FlyoutParamProps } from './types';
const ITEM_HEIGHT = 172;

View file

@ -56,6 +56,7 @@ export interface SyntheticsSettingsContextValues {
isDev?: boolean;
isServerless?: boolean;
setBreadcrumbs?: (crumbs: ChromeBreadcrumb[]) => void;
darkMode: boolean;
}
const { BASE_PATH } = CONTEXT_DEFAULTS;
@ -76,6 +77,7 @@ const defaultContext: SyntheticsSettingsContextValues = {
isDev: false,
canSave: false,
canManagePrivateLocations: false,
darkMode: false,
};
export const SyntheticsSettingsContext = createContext(defaultContext);
@ -91,6 +93,7 @@ export const SyntheticsSettingsContextProvider: React.FC<PropsWithChildren<Synth
commonlyUsedRanges,
isDev,
isServerless,
darkMode,
} = props;
const { dateRangeStart, dateRangeEnd } = useGetUrlParams();
@ -103,6 +106,7 @@ export const SyntheticsSettingsContextProvider: React.FC<PropsWithChildren<Synth
const value = useMemo(() => {
return {
darkMode,
canSave,
isDev,
basePath,
@ -116,6 +120,7 @@ export const SyntheticsSettingsContextProvider: React.FC<PropsWithChildren<Synth
canManagePrivateLocations,
};
}, [
darkMode,
canSave,
isDev,
basePath,

View file

@ -55,6 +55,7 @@ describe('useBreadcrumbs', () => {
<Route path={OVERVIEW_ROUTE}>
<SyntheticsSettingsContext.Provider
value={{
darkMode: false,
basePath: '/app/synthetics',
canSave: true,
dateRangeStart: '',

View file

@ -36,6 +36,5 @@ export const getConfigStatusByLocation = (
configIdByLocation,
status: config?.status || 'unknown',
timestamp: config?.timestamp,
ping: config?.ping,
};
};

View file

@ -24,6 +24,11 @@ export const getMonitorLastRunAction = createAsyncAction<
{ ping?: Ping }
>('[MONITOR DETAILS] GET LAST RUN');
export const getMonitorLastErrorRunAction = createAsyncAction<
{ monitorId: string; locationLabel: string },
{ ping?: Ping }
>('[MONITOR DETAILS] GET LAST ERROR RUN');
export const resetMonitorLastRunAction = createAction('[MONITOR DETAILS] LAST RUN RESET');
export const updateMonitorLastRunAction = createAction<{ data: Ping }>(

View file

@ -43,7 +43,7 @@ export const fetchMonitorRecentPings = async ({
{
monitorId,
from: from ?? moment().subtract(30, 'days').toISOString(),
to: to ?? new Date().toISOString(),
to: to ?? moment().toISOString(),
locations,
sort,
size,

View file

@ -15,6 +15,7 @@ import {
getMonitorRecentPingsAction,
getMonitorAction,
updateMonitorLastRunAction,
getMonitorLastErrorRunAction,
} from './actions';
import { fetchSyntheticsMonitor, fetchMonitorRecentPings, fetchLatestTestRun } from './api';
import { selectLastRunMetadata } from './selectors';
@ -38,6 +39,15 @@ export function* fetchSyntheticsMonitorEffect() {
)
);
yield takeLeading(
getMonitorLastErrorRunAction.get,
fetchEffectFactory(
fetchLatestTestRun,
getMonitorLastErrorRunAction.success,
getMonitorLastErrorRunAction.fail
)
);
// Additional listener on `getMonitorRecentPingsAction.success` to possibly update the `lastRun` as well
yield takeEvery(
getMonitorRecentPingsAction.success,

View file

@ -20,6 +20,7 @@ import {
setMonitorDetailsLocationAction,
getMonitorAction,
setStatusFilter,
getMonitorLastErrorRunAction,
} from './actions';
export interface MonitorDetailsState {
@ -33,6 +34,10 @@ export interface MonitorDetailsState {
loading: boolean;
loaded: boolean;
};
lastErrorRun: {
data?: Ping;
loading: boolean;
};
syntheticsMonitorLoading: boolean;
syntheticsMonitor: SyntheticsMonitorWithId | null;
syntheticsMonitorError?: IHttpSerializedFetchError | null;
@ -45,6 +50,7 @@ export interface MonitorDetailsState {
const initialState: MonitorDetailsState = {
pings: { total: 0, data: [], loading: false },
lastRun: { loading: false, loaded: false },
lastErrorRun: { loading: false },
syntheticsMonitor: null,
syntheticsMonitorLoading: false,
syntheticsMonitorDispatchedAt: 0,
@ -73,6 +79,21 @@ export const monitorDetailsReducer = createReducer(initialState, (builder) => {
state.lastRun.loading = false;
state.error = action.payload;
})
.addCase(getMonitorLastErrorRunAction.get, (state, action) => {
state.lastErrorRun.loading = true;
const configId = state.lastErrorRun?.data?.config_id;
if (action.payload.monitorId !== configId) {
state.lastErrorRun.data = undefined;
}
})
.addCase(getMonitorLastErrorRunAction.success, (state, action) => {
state.lastErrorRun.loading = false;
state.lastErrorRun.data = action.payload?.ping;
})
.addCase(getMonitorLastErrorRunAction.fail, (state, action) => {
state.lastErrorRun.loading = false;
state.error = action.payload;
})
.addCase(updateMonitorLastRunAction, (state, action) => {
state.lastRun.data = action.payload.data;
})

View file

@ -19,6 +19,7 @@ export const selectSelectedLocationId = createSelector(
);
export const selectLastRunMetadata = createSelector(getState, (state) => state.lastRun);
export const selectLastErrorRunMetadata = createSelector(getState, (state) => state.lastErrorRun);
export const selectPingsLoading = createSelector(getState, (state) => state.pings.loading);

View file

@ -197,6 +197,9 @@ function getBrowserJourneyMockSlice() {
function getMonitorDetailsMockSlice() {
return {
lastErrorRun: {
loading: false,
},
lastRun: {
loading: false,
loaded: true,