Skip to content

Commit

Permalink
[Synthetics] ensure metric color and current status are synced (#145031)
Browse files Browse the repository at this point in the history
## Summary

Relates to #146001

This PR ensures that the status of monitors is in sync with the current
status metric by deriving the status of each individual monitor card
based on the current status api.

Updates the current status api to [return objects representing all the
down monitors and all the up
monitors](https://github.com/elastic/kibana/pull/145031/files#diff-db953498cb683d4f81a105a8c53f80ef08aea4bb687e6a29f651dd4e148bcc55R122),
with the keys being the combination of the monitor config id and
location name.

These two objects are [combined into one object in
redux](https://github.com/elastic/kibana/pull/145031/files#diff-2fb9b43772c61e2706a828651abd651a2a03b33b9f7b59c91d91c3a245edf655R77).

Creates a new `useStatusByLocationOverview`
[hook](https://github.com/elastic/kibana/pull/145031/files#diff-b80b952c8c3e15be0472d8b63a23f6ca0798dfbfdbce6a1ed056d7f954ab200eR11)
that consumes this object.

[Uses that
hook](https://github.com/elastic/kibana/pull/145031/files#diff-d65b7f3fa061b4f65d63f72296d29f603e10757e595dd688a34fa99deeac3250R56)
in Overview Metric item to derive the status.

### Testing

1. Create a handful of monitors, ensuring you have at least 1 ui monitor
and 1 project monitor
2. Ensure your cluster is connected to the synthetics service, either by
running the service locally or connecting to dev environment
3. Navigate to overview. Ensure the number of down monitor cards and
number of up monitor cards matches the current status metric
4. Use the sort by status feature to test for regression. That logic was
lightly touched in this PR
  • Loading branch information
dominiqueclarke authored Dec 15, 2022
1 parent d0039ea commit 578d643
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,28 @@
import * as t from 'io-ts';

export const OverviewStatusMetaDataCodec = t.interface({
heartbeatId: t.string,
monitorQueryId: t.string,
configId: t.string,
location: t.string,
status: t.string,
});

export const OverviewStatusType = t.type({
export const OverviewStatusCodec = t.interface({
up: t.number,
down: t.number,
disabledCount: t.number,
upConfigs: t.array(OverviewStatusMetaDataCodec),
downConfigs: t.array(OverviewStatusMetaDataCodec),
upConfigs: t.record(t.string, OverviewStatusMetaDataCodec),
downConfigs: t.record(t.string, OverviewStatusMetaDataCodec),
enabledIds: t.array(t.string),
});

export type OverviewStatus = t.TypeOf<typeof OverviewStatusType>;
export const OverviewStatusStateCodec = t.intersection([
OverviewStatusCodec,
t.interface({
allConfigs: t.record(t.string, OverviewStatusMetaDataCodec),
}),
]);

export type OverviewStatus = t.TypeOf<typeof OverviewStatusCodec>;
export type OverviewStatusState = t.TypeOf<typeof OverviewStatusStateCodec>;
export type OverviewStatusMetaData = t.TypeOf<typeof OverviewStatusMetaDataCodec>;
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,30 @@ import { Chart, Settings, Metric, MetricTrendShape } from '@elastic/charts';
import { EuiPanel } from '@elastic/eui';
import { DARK_THEME } from '@elastic/charts';
import { useTheme } from '@kbn/observability-plugin/public';
import { useLocationName, useStatusByLocation } from '../../../../hooks';
import { useLocationName, useStatusByLocationOverview } from '../../../../hooks';
import { formatDuration } from '../../../../utils/formatting';
import { MonitorOverviewItem, Ping } from '../../../../../../../common/runtime_types';
import { MonitorOverviewItem } from '../../../../../../../common/runtime_types';
import { ActionsPopover } from './actions_popover';
import { OverviewGridItemLoader } from './overview_grid_item_loader';

export const getColor = (theme: ReturnType<typeof useTheme>, isEnabled: boolean, ping?: Ping) => {
export const getColor = (
theme: ReturnType<typeof useTheme>,
isEnabled: boolean,
status?: string
) => {
if (!isEnabled) {
return theme.eui.euiColorLightestShade;
}
return (ping?.summary?.down || 0) > 0
? theme.eui.euiColorVis9_behindText
: theme.eui.euiColorVis0_behindText;
switch (status) {
case 'down':
return theme.eui.euiColorVis9_behindText;
case 'up':
return theme.eui.euiColorVis0_behindText;
case 'unknown':
return theme.eui.euiColorGhost;
default:
return theme.eui.euiColorVis0_behindText;
}
};

export const MetricItem = ({
Expand All @@ -41,8 +52,7 @@ export const MetricItem = ({
const [isMouseOver, setIsMouseOver] = useState(false);
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const locationName = useLocationName({ locationId: monitor.location?.id });
const { locations } = useStatusByLocation(monitor.configId);
const ping = locations.find((loc) => loc.observer?.geo?.name === locationName);
const status = useStatusByLocationOverview(monitor.configId, locationName);
const theme = useTheme();

return (
Expand Down Expand Up @@ -92,7 +102,7 @@ export const MetricItem = ({
</span>
),
valueFormatter: (d: number) => formatDuration(d),
color: getColor(theme, monitor.isEnabled, ping),
color: getColor(theme, monitor.isEnabled, status),
},
],
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import { OverviewGrid } from './overview_grid';
import * as hooks from '../../../../hooks/use_last_50_duration_chart';

describe('Overview Grid', () => {
const locationIdToName: Record<string, string> = {
us_central: 'Us Central',
us_east: 'US East',
};
const getMockData = (): MonitorOverviewItem[] => {
const data: MonitorOverviewItem[] = [];
for (let i = 0; i < 20; i++) {
Expand Down Expand Up @@ -72,8 +76,17 @@ describe('Overview Grid', () => {
loaded: true,
loading: false,
status: {
downConfigs: [],
upConfigs: [],
downConfigs: {},
upConfigs: {},
allConfigs: getMockData().reduce((acc, cur) => {
acc[`${cur.id}-${locationIdToName[cur.location.id]}`] = {
configId: cur.configId,
monitorQueryId: cur.id,
location: locationIdToName[cur.location.id],
status: 'down',
};
return acc;
}, {} as Record<string, any>),
},
},
serviceLocations: {
Expand Down Expand Up @@ -121,8 +134,17 @@ describe('Overview Grid', () => {
loaded: true,
loading: false,
status: {
downConfigs: [],
upConfigs: [],
downConfigs: {},
upConfigs: {},
allConfigs: getMockData().reduce((acc, cur) => {
acc[`${cur.id}-${locationIdToName[cur.location.id]}`] = {
configId: cur.configId,
monitorQueryId: cur.id,
location: locationIdToName[cur.location.id],
status: 'down',
};
return acc;
}, {} as Record<string, any>),
},
},
serviceLocations: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ export * from './use_last_x_checks';
export * from './use_last_50_duration_chart';
export * from './use_location_name';
export * from './use_status_by_location';
export * from './use_status_by_location_overview';
export * from './use_composite_image';
export * from './use_dimensions';
Original file line number Diff line number Diff line change
Expand Up @@ -56,40 +56,40 @@ describe('useMonitorsSortedByStatus', () => {
sortField: 'name.keyword',
},
status: {
upConfigs: [
{
upConfigs: {
[`test-monitor-1-${location2.label}`]: {
configId: 'test-monitor-1',
heartbeatId: 'test-monitor-1',
monitorQueryId: 'test-monitor-1',
location: location2.label,
},
{
[`test-monitor-2-${location2.label}`]: {
configId: 'test-monitor-2',
heartbeatId: 'test-monitor-2',
monitorQueryId: 'test-monitor-2',
location: location2.label,
},
{
[`test-monitor-3-${location2.label}`]: {
configId: 'test-monitor-3',
heartbeatId: 'test-monitor-3',
monitorQueryId: 'test-monitor-3',
location: location2.label,
},
],
downConfigs: [
{
},
downConfigs: {
[`test-monitor-1-${location1.label}`]: {
configId: 'test-monitor-1',
heartbeatId: 'test-monitor-1',
monitorQueryId: 'test-monitor-1',
location: location1.label,
},
{
[`test-monitor-2-${location1.label}`]: {
configId: 'test-monitor-2',
heartbeatId: 'test-monitor-2',
monitorQueryId: 'test-monitor-2',
location: location1.label,
},
{
[`test-monitor-3${location1.label}`]: {
configId: 'test-monitor-3',
heartbeatId: 'test-monitor-3',
monitorQueryId: 'test-monitor-3',
location: location1.label,
},
],
},
},
data: {
total: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function useMonitorsSortedByStatus() {

const { downConfigs } = status;
const downMonitorMap: Record<string, string[]> = {};
downConfigs.forEach(({ location, configId }) => {
Object.values(downConfigs).forEach(({ location, configId }) => {
if (downMonitorMap[configId]) {
downMonitorMap[configId].push(location);
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 { useSelector } from 'react-redux';
import { selectOverviewStatus } from '../state/overview';

export function useStatusByLocationOverview(configId: string, locationName?: string) {
const { status } = useSelector(selectOverviewStatus);
if (!locationName || !status) {
return 'unknown';
}
const allConfigs = status.allConfigs;

return allConfigs[`${configId}-${locationName}`]?.status || 'unknown';
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
MonitorOverviewResultCodec,
FetchMonitorOverviewQueryArgs,
OverviewStatus,
OverviewStatusType,
OverviewStatusCodec,
} from '../../../../../common/runtime_types';
import { apiService } from '../../../../utils/api_service';
import { MonitorOverviewPageState } from './models';
Expand Down Expand Up @@ -54,5 +54,5 @@ export const fetchOverviewStatus = async (
pageState: MonitorOverviewPageState
): Promise<OverviewStatus> => {
const params = toMonitorOverviewQueryArgs(pageState);
return await apiService.get(SYNTHETICS_API_URLS.OVERVIEW_STATUS, params, OverviewStatusType);
return await apiService.get(SYNTHETICS_API_URLS.OVERVIEW_STATUS, params, OverviewStatusCodec);
};
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ export const monitorOverviewReducer = createReducer(initialState, (builder) => {
state.flyoutConfig = action.payload;
})
.addCase(fetchOverviewStatusAction.success, (state, action) => {
state.status = action.payload;
state.status = {
...action.payload,
allConfigs: { ...action.payload.upConfigs, ...action.payload.downConfigs },
};
})
.addCase(fetchOverviewStatusAction.fail, (state, action) => {
state.statusError = action.payload;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { MonitorOverviewResult, OverviewStatus } from '../../../../../common/runtime_types';
import { MonitorOverviewResult, OverviewStatusState } from '../../../../../common/runtime_types';

import { IHttpSerializedFetchError } from '../utils/http_error';

Expand All @@ -31,6 +31,6 @@ export interface MonitorOverviewState {
loading: boolean;
loaded: boolean;
error: IHttpSerializedFetchError | null;
status: OverviewStatus | null;
status: OverviewStatusState | null;
statusError: IHttpSerializedFetchError | null;
}
Loading

0 comments on commit 578d643

Please sign in to comment.