Skip to content

Commit 554922f

Browse files
[9.4] [Synthetics] Stop per-card maintenance-window refetch storm on overview scroll (#289776) (#291103)
# Backport This will backport the following commits from `main` to `9.4`: - [[Synthetics] Stop per-card maintenance-window refetch storm on overview scroll (#289776)](#289776) <!--- 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":"2026-09-15T11:41:29Z","message":"[Synthetics] Stop per-card maintenance-window refetch storm on overview scroll (#289776)\n\n## Summary\n\n`useFetchMaintenanceWindows` had no `staleTime`, so react-query treated\nits cached result as stale immediately after every fetch. The hook is\ncalled per monitor card (`MetricItemIcon` -> `useMonitorMWs`) from the\nvirtualized Synthetics overview grid, so scrolling — which constantly\nmounts fresh card instances as new rows come into view — fired a brand\nnew `GET /internal/synthetics/monitors/maintenance_windows` request for\nevery card, instead of reusing the one already-cached result.\n\n## Fix\n\nSet `staleTime` to match the hook's existing `refetchInterval` (5\nminutes) — the same window during which the data is already known to be\nsafe to reuse (maintenance windows only take effect on a sync interval,\nand the existing `refetchInterval` comment explains why 5 minutes is the\nright cadence). This is a one-line change with no behavior change to the\nperiodic background polling.\n\n## Testing\n\n- Added a unit test (`use_fetch_maintenance_windows.test.ts`) asserting\n`staleTime` matches `refetchInterval`; confirmed red (fails without the\nfix) / green (passes with it).\n- Verified live: on the Synthetics overview page (card view), scrolling\nto load new pages of monitors previously fired one `maintenance_windows`\nrequest per newly-mounted card. After the fix, scrolling through\nnewly-loaded pages triggers zero additional requests.\n\n## Test plan\n\n- [x] Unit test added and passing\n- [x] Verified locally in a real browser (network trace before/after)\n- [x] `node scripts/eslint --fix` clean\n- [x] Scoped type-check clean (`synthetics` project full pass)","sha":"e443f816d1018d365c92bd7a6138be218bde19a9","branchLabelMapping":{"^v9.6.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:all-open","author:actionable-obs","v9.6.0"],"title":"[Synthetics] Stop per-card maintenance-window refetch storm on overview scroll","number":289776,"url":"https://github.com/elastic/kibana/pull/289776","mergeCommit":{"message":"[Synthetics] Stop per-card maintenance-window refetch storm on overview scroll (#289776)\n\n## Summary\n\n`useFetchMaintenanceWindows` had no `staleTime`, so react-query treated\nits cached result as stale immediately after every fetch. The hook is\ncalled per monitor card (`MetricItemIcon` -> `useMonitorMWs`) from the\nvirtualized Synthetics overview grid, so scrolling — which constantly\nmounts fresh card instances as new rows come into view — fired a brand\nnew `GET /internal/synthetics/monitors/maintenance_windows` request for\nevery card, instead of reusing the one already-cached result.\n\n## Fix\n\nSet `staleTime` to match the hook's existing `refetchInterval` (5\nminutes) — the same window during which the data is already known to be\nsafe to reuse (maintenance windows only take effect on a sync interval,\nand the existing `refetchInterval` comment explains why 5 minutes is the\nright cadence). This is a one-line change with no behavior change to the\nperiodic background polling.\n\n## Testing\n\n- Added a unit test (`use_fetch_maintenance_windows.test.ts`) asserting\n`staleTime` matches `refetchInterval`; confirmed red (fails without the\nfix) / green (passes with it).\n- Verified live: on the Synthetics overview page (card view), scrolling\nto load new pages of monitors previously fired one `maintenance_windows`\nrequest per newly-mounted card. After the fix, scrolling through\nnewly-loaded pages triggers zero additional requests.\n\n## Test plan\n\n- [x] Unit test added and passing\n- [x] Verified locally in a real browser (network trace before/after)\n- [x] `node scripts/eslint --fix` clean\n- [x] Scoped type-check clean (`synthetics` project full pass)","sha":"e443f816d1018d365c92bd7a6138be218bde19a9"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.6.0","branchLabelMappingKey":"^v9.6.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/289776","number":289776,"mergeCommit":{"message":"[Synthetics] Stop per-card maintenance-window refetch storm on overview scroll (#289776)\n\n## Summary\n\n`useFetchMaintenanceWindows` had no `staleTime`, so react-query treated\nits cached result as stale immediately after every fetch. The hook is\ncalled per monitor card (`MetricItemIcon` -> `useMonitorMWs`) from the\nvirtualized Synthetics overview grid, so scrolling — which constantly\nmounts fresh card instances as new rows come into view — fired a brand\nnew `GET /internal/synthetics/monitors/maintenance_windows` request for\nevery card, instead of reusing the one already-cached result.\n\n## Fix\n\nSet `staleTime` to match the hook's existing `refetchInterval` (5\nminutes) — the same window during which the data is already known to be\nsafe to reuse (maintenance windows only take effect on a sync interval,\nand the existing `refetchInterval` comment explains why 5 minutes is the\nright cadence). This is a one-line change with no behavior change to the\nperiodic background polling.\n\n## Testing\n\n- Added a unit test (`use_fetch_maintenance_windows.test.ts`) asserting\n`staleTime` matches `refetchInterval`; confirmed red (fails without the\nfix) / green (passes with it).\n- Verified live: on the Synthetics overview page (card view), scrolling\nto load new pages of monitors previously fired one `maintenance_windows`\nrequest per newly-mounted card. After the fix, scrolling through\nnewly-loaded pages triggers zero additional requests.\n\n## Test plan\n\n- [x] Unit test added and passing\n- [x] Verified locally in a real browser (network trace before/after)\n- [x] `node scripts/eslint --fix` clean\n- [x] Scoped type-check clean (`synthetics` project full pass)","sha":"e443f816d1018d365c92bd7a6138be218bde19a9"}}]}] BACKPORT--> Co-authored-by: Shahzad <shahzad31comp@gmail.com>
1 parent 34662a3 commit 554922f

2 files changed

Lines changed: 44 additions & 0 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { renderHook } from '@testing-library/react';
9+
import { useQuery } from '@kbn/react-query';
10+
import { useFetchMaintenanceWindows } from './use_fetch_maintenance_windows';
11+
12+
jest.mock('@kbn/react-query', () => ({
13+
useQuery: jest.fn().mockReturnValue({ data: undefined, isLoading: false }),
14+
}));
15+
16+
jest.mock('../../../utils/api_service', () => ({
17+
apiService: { get: jest.fn() },
18+
}));
19+
20+
const useQueryMock = useQuery as jest.Mock;
21+
22+
describe('useFetchMaintenanceWindows', () => {
23+
afterEach(() => jest.clearAllMocks());
24+
25+
it('sets staleTime equal to the refetch interval, so a component mount between polls does not trigger its own fetch', () => {
26+
// This hook is called per-card from the virtualized overview grid
27+
// (`MetricItemIcon` -> `useMonitorMWs`). Without a `staleTime` matching
28+
// `refetchInterval`, react-query's default `staleTime: 0` treats every
29+
// remount as needing a fresh fetch, so scrolling (which constantly
30+
// mounts new cards) fires a fetch per card instead of sharing the one
31+
// already-cached result.
32+
renderHook(() => useFetchMaintenanceWindows());
33+
34+
const options = useQueryMock.mock.calls[0][2];
35+
expect(options.staleTime).toBe(options.refetchInterval);
36+
expect(options.staleTime).toBeGreaterThan(0);
37+
});
38+
});

x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/hooks/use_fetch_maintenance_windows.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ export const useFetchMaintenanceWindows = () => {
3838
),
3939
{
4040
refetchInterval: REFRESH_INTERVAL_MS,
41+
// Without this, cached data is stale immediately (the default) and every
42+
// component mount re-fetches on top of the interval polling above. This
43+
// hook is called per-card from the virtualized overview grid
44+
// (`MetricItemIcon` -> `useMonitorMWs`), so scrolling constantly mounts
45+
// fresh subscribers — each one triggering its own request otherwise.
46+
staleTime: REFRESH_INTERVAL_MS,
4147
}
4248
);
4349
};

0 commit comments

Comments
 (0)