From af444ef9146374ff3e5cfa45a45cbed1a513124e Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 3 Jun 2024 11:45:31 -0400 Subject: [PATCH] Don't render time bins until there is a proper initial size. --- .../monitor_status/monitor_status_panel.tsx | 116 +++++++++--------- .../monitor_status/use_monitor_status_data.ts | 81 ++++++++---- .../apps/synthetics/state/root_effect.ts | 3 +- .../state/status_heatmap/actions.ts | 8 ++ .../state/status_heatmap/effects.ts | 13 +- .../synthetics/state/status_heatmap/index.ts | 16 ++- 6 files changed, 156 insertions(+), 81 deletions(-) diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/monitor_status_panel.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/monitor_status_panel.tsx index 0d0285650f95b..d19b1cb2d66dd 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/monitor_status_panel.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/monitor_status_panel.tsx @@ -5,9 +5,9 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React, { useMemo, useRef } from 'react'; -import { EuiPanel, useEuiTheme, EuiResizeObserver, EuiSpacer } from '@elastic/eui'; +import { EuiPanel, useEuiTheme, EuiResizeObserver, EuiSpacer, EuiProgress } from '@elastic/eui'; import { Chart, Settings, Heatmap, ScaleType, Tooltip, LEGACY_LIGHT_THEME } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; import { MonitorStatusHeader } from './monitor_status_header'; @@ -31,8 +31,9 @@ export const MonitorStatusPanel = ({ onBrushed, }: MonitorStatusPanelProps) => { const { euiTheme, colorMode } = useEuiTheme(); + const initialSizeRef = useRef(null); const { loading, timeBins, handleResize, getTimeBinByXValue, xDomain, minsPerBin } = - useMonitorStatusData({ from, to }); + useMonitorStatusData({ from, to, initSize: initialSizeRef.current?.clientWidth }); const heatmap = useMemo(() => { return getMonitorStatusChartTheme(euiTheme, brushable); @@ -51,61 +52,66 @@ export const MonitorStatusPanel = ({ - - {(resizeRef) => ( -
- - ( - + handleResize(e)}> + {(resizeRef) => ( +
+ {minsPerBin && ( + + ( + + )} /> - )} - /> - { - onBrushed?.(getBrushData(brushArea)); - }} - locale={i18n.getLocale()} - /> - end} - yAccessor={() => 'T'} - valueAccessor={(timeBin) => timeBin.value} - valueFormatter={(d) => d.toFixed(2)} - xAxisLabelFormatter={getXAxisLabelFormatter(minsPerBin)} - timeZone="UTC" - xScale={{ - type: ScaleType.Time, - interval: { - type: 'calendar', - unit: 'm', - value: minsPerBin, - }, - }} - /> - -
- )} -
+ { + onBrushed?.(getBrushData(brushArea)); + }} + locale={i18n.getLocale()} + /> + end} + yAccessor={() => 'T'} + valueAccessor={(timeBin) => timeBin.value} + valueFormatter={(d) => d.toFixed(2)} + xAxisLabelFormatter={getXAxisLabelFormatter(minsPerBin)} + timeZone="UTC" + xScale={{ + type: ScaleType.Time, + interval: { + type: 'calendar', + unit: 'm', + value: minsPerBin, + }, + }} + /> +
+ )} +
+ )} +
+ + {loading && } ); }; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts index 74758d58f2fd9..c27fb7d299061 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts @@ -6,10 +6,10 @@ */ import { useCallback, useEffect, useMemo, useState } from 'react'; -import { throttle } from 'lodash'; import { useSelector, useDispatch } from 'react-redux'; +import { useDebounce } from 'react-use'; +import { useLocation } from 'react-router-dom'; -import { scheduleToMinutes } from '../../../../../../common/lib/schedule_to_time'; import { useSyntheticsRefreshContext } from '../../../contexts/synthetics_refresh_context'; import { useSelectedMonitor } from '../hooks/use_selected_monitor'; @@ -23,36 +23,43 @@ import { MonitorStatusTimeBin, } from './monitor_status_data'; import { useSelectedLocation } from '../hooks/use_selected_location'; -import { getMonitorStatusHeatmapAction, selectHeatmap } from '../../../state/status_heatmap'; +import { + clearMonitorStatusHeatmapAction, + quietGetMonitorStatusHeatmapAction, + selectHeatmap, +} from '../../../state/status_heatmap'; + +type Props = Pick & { initSize?: number }; -export const useMonitorStatusData = ({ - from, - to, -}: Pick) => { +export const useMonitorStatusData = ({ from, to, initSize }: Props) => { const { lastRefresh } = useSyntheticsRefreshContext(); const { monitor } = useSelectedMonitor(); const location = useSelectedLocation(); - const monitorInterval = Math.max(3, monitor?.schedule ? scheduleToMinutes(monitor?.schedule) : 3); + const pageLocation = useLocation(); const fromMillis = dateToMilli(from); const toMillis = dateToMilli(to); const totalMinutes = Math.ceil(toMillis - fromMillis) / (1000 * 60); const [binsAvailableByWidth, setBinsAvailableByWidth] = useState(null); - const minsPerBin = Math.floor( - Math.max( - monitorInterval, - binsAvailableByWidth !== null ? totalMinutes / binsAvailableByWidth : 0 - ) - ); + const [debouncedBinsCount, setDebouncedCount] = useState(null); + + const minsPerBin = + debouncedBinsCount !== null ? Math.floor(totalMinutes / debouncedBinsCount) : null; const dispatch = useDispatch(); const { heatmap: dateHistogram, loading } = useSelector(selectHeatmap); useEffect(() => { - if (monitor?.id && location?.label) { + if (binsAvailableByWidth === null && initSize) { + setBinsAvailableByWidth(Math.floor(initSize / CHART_CELL_WIDTH)); + } + }, [binsAvailableByWidth, initSize]); + + useEffect(() => { + if (monitor?.id && location?.label && debouncedBinsCount !== null && minsPerBin !== null) { dispatch( - getMonitorStatusHeatmapAction.get({ + quietGetMonitorStatusHeatmapAction.get({ monitorId: monitor.id, location: location.label, from, @@ -61,23 +68,51 @@ export const useMonitorStatusData = ({ }) ); } - }, [dispatch, from, to, minsPerBin, location?.label, monitor?.id, lastRefresh]); + }, [ + dispatch, + from, + to, + minsPerBin, + location?.label, + monitor?.id, + lastRefresh, + debouncedBinsCount, + ]); + + useEffect(() => { + dispatch(clearMonitorStatusHeatmapAction()); + }, [dispatch, pageLocation.pathname]); - // Disabling deps warning as we wanna throttle the callback - // eslint-disable-next-line react-hooks/exhaustive-deps const handleResize = useCallback( - throttle((e: { width: number; height: number }) => { - setBinsAvailableByWidth(Math.floor(e.width / CHART_CELL_WIDTH)); - }, 500), + (e: { width: number; height: number }) => + setBinsAvailableByWidth(Math.floor(e.width / CHART_CELL_WIDTH)), [] ); + useDebounce( + async () => { + setDebouncedCount(binsAvailableByWidth); + }, + 500, + [binsAvailableByWidth] + ); + const { timeBins, timeBinMap, xDomain } = useMemo((): { timeBins: MonitorStatusTimeBin[]; timeBinMap: Map; xDomain: { min: number; max: number }; } => { - const timeBuckets = createTimeBuckets(minsPerBin, fromMillis, toMillis); + if (minsPerBin === null) { + return { + timeBins: [], + timeBinMap: new Map(), + xDomain: { + min: fromMillis, + max: toMillis, + }, + }; + } + const timeBuckets = createTimeBuckets(minsPerBin ?? 50, fromMillis, toMillis); const bins = createStatusTimeBins(timeBuckets, dateHistogram); return { timeBins: bins, diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/root_effect.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/root_effect.ts index 955371832ae4f..00d9beac14cf9 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/root_effect.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/root_effect.ts @@ -38,7 +38,7 @@ import { fetchServiceLocationsEffect } from './service_locations'; import { browserJourneyEffects, fetchJourneyStepsEffect } from './browser_journey'; import { fetchPingStatusesEffect } from './ping_status'; import { fetchOverviewStatusEffect } from './overview_status'; -import { fetchMonitorStatusHeatmap } from './status_heatmap'; +import { fetchMonitorStatusHeatmap, quietFetchMonitorStatusHeatmap } from './status_heatmap'; export const rootEffect = function* root(): Generator { yield all([ @@ -73,5 +73,6 @@ export const rootEffect = function* root(): Generator { fork(getDefaultAlertingEffect), fork(enableDefaultAlertingSilentlyEffect), fork(fetchMonitorStatusHeatmap), + fork(quietFetchMonitorStatusHeatmap), ]); }; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/status_heatmap/actions.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/status_heatmap/actions.ts index d3f1819caf66d..f56fe727e7cbb 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/status_heatmap/actions.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/status_heatmap/actions.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { createAction } from '@reduxjs/toolkit'; import { MonitorStatusHeatmapBucket } from '../../../../../common/runtime_types'; import { createAsyncAction } from '../utils/actions'; @@ -14,3 +15,10 @@ export const getMonitorStatusHeatmapAction = createAsyncAction< MonitorStatusHeatmapActionArgs, MonitorStatusHeatmapBucket[] >('MONITOR STATUS HEATMAP'); + +export const clearMonitorStatusHeatmapAction = createAction('CLEAR MONITOR STATUS HEATMAP'); + +export const quietGetMonitorStatusHeatmapAction = createAsyncAction< + MonitorStatusHeatmapActionArgs, + MonitorStatusHeatmapBucket[] +>('QUIET GET MONITOR STATUS HEATMAP'); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/status_heatmap/effects.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/status_heatmap/effects.ts index 08ad6c819c206..beffc42367e9a 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/status_heatmap/effects.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/status_heatmap/effects.ts @@ -9,7 +9,7 @@ import { takeLatest } from 'redux-saga/effects'; import { fetchEffectFactory } from '../utils/fetch_effect'; import { fetchMonitorStatusHeatmap as api } from './api'; -import { getMonitorStatusHeatmapAction } from './actions'; +import { getMonitorStatusHeatmapAction, quietGetMonitorStatusHeatmapAction } from './actions'; export function* fetchMonitorStatusHeatmap() { yield takeLatest( @@ -21,3 +21,14 @@ export function* fetchMonitorStatusHeatmap() { ) as ReturnType ); } + +export function* quietFetchMonitorStatusHeatmap() { + yield takeLatest( + quietGetMonitorStatusHeatmapAction.get, + fetchEffectFactory( + api, + getMonitorStatusHeatmapAction.success, + getMonitorStatusHeatmapAction.fail + ) as ReturnType + ); +} diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/status_heatmap/index.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/status_heatmap/index.ts index e1ccc158d5886..29f8a1ba87345 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/status_heatmap/index.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/status_heatmap/index.ts @@ -11,7 +11,11 @@ import { MonitorStatusHeatmapBucket } from '../../../../../common/runtime_types' import { IHttpSerializedFetchError } from '../utils/http_error'; -import { getMonitorStatusHeatmapAction } from './actions'; +import { + clearMonitorStatusHeatmapAction, + getMonitorStatusHeatmapAction, + quietGetMonitorStatusHeatmapAction, +} from './actions'; export interface MonitorStatusHeatmap { heatmap: MonitorStatusHeatmapBucket[]; @@ -27,6 +31,13 @@ const initialState: MonitorStatusHeatmap = { export const monitorStatusHeatmapReducer = createReducer(initialState, (builder) => { builder + .addCase(quietGetMonitorStatusHeatmapAction.success, (state, action) => { + state.heatmap = action.payload; + state.loading = false; + }) + .addCase(quietGetMonitorStatusHeatmapAction.get, (state) => { + state.loading = true; + }) .addCase(getMonitorStatusHeatmapAction.get, (state) => { state.loading = true; state.heatmap = []; @@ -38,6 +49,9 @@ export const monitorStatusHeatmapReducer = createReducer(initialState, (builder) .addCase(getMonitorStatusHeatmapAction.fail, (state, action) => { state.error = action.payload; state.loading = false; + }) + .addCase(clearMonitorStatusHeatmapAction, (state) => { + state.heatmap = []; }); });