diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/capacity/Capacity.test.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/capacity/Capacity.test.tsx index 037fcd093cd..89612d76784 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/capacity/Capacity.test.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/capacity/Capacity.test.tsx @@ -307,4 +307,57 @@ describe('Capacity Page', () => { ); expect(screen.queryByRole('option', { name: 'dn-17' })).not.toBeInTheDocument(); }); + + test('keeps polling the DN scan to completion even when Auto Refresh is off', async () => { + // Auto Refresh disabled before mount, so useAutoReload never starts its timer. + sessionStorage.setItem('autoReloadEnabled', 'false'); + + let dnCallCount = 0; + // Report an in-progress scan on the first two reads, then FINISHED. + const dnStatuses = ['IN_PROGRESS', 'IN_PROGRESS', 'FINISHED']; + capacityServer.use( + rest.get('api/v1/pendingDeletion', (req, res, ctx) => { + const component = req.url.searchParams.get('component'); + if (component === 'dn') { + const status = dnStatuses[Math.min(dnCallCount, dnStatuses.length - 1)]; + dnCallCount++; + return res( + ctx.status(200), + ctx.json({ ...mockResponses.DnPendingDeletion, status }) + ); + } + const map: Record = { + scm: mockResponses.ScmPendingDeletion, + om: mockResponses.OmPendingDeletion + }; + const body = component ? map[component] : undefined; + return body + ? res(ctx.status(200), ctx.json(body)) + : res(ctx.status(400), ctx.json({ message: 'Unsupported pending deletion component.' })); + }) + ); + + vi.useFakeTimers(); + try { + render(); + + // Flush the initial mount fetch (dn read #1 -> IN_PROGRESS). + await vi.advanceTimersByTimeAsync(50); + expect(dnCallCount).toBe(1); + + // With the toggle off, an in-progress scan still polls every 5s. Advancing + // the timer drives further reads (#2 IN_PROGRESS, #3 FINISHED). + await vi.advanceTimersByTimeAsync(5000); + expect(dnCallCount).toBe(2); + await vi.advanceTimersByTimeAsync(5000); + expect(dnCallCount).toBe(3); + + // Once FINISHED, polling stops even though Auto Refresh remains off. + await vi.advanceTimersByTimeAsync(15000); + expect(dnCallCount).toBe(3); + } finally { + vi.useRealTimers(); + sessionStorage.removeItem('autoReloadEnabled'); + } + }); }); diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/capacity/capacity.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/capacity/capacity.tsx index d6695af6be3..ba52984e55e 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/capacity/capacity.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/capacity/capacity.tsx @@ -202,18 +202,27 @@ const Capacity: React.FC = () => { } }; - // Adjust the polling interval based on DN scan status: + // Keep the latest refresh callback in a ref so the interval below always calls + // the current closure instead of a stale one captured when the effect last ran. + const loadDataIfIdleRef = React.useRef(loadDataIfIdle); + loadDataIfIdleRef.current = loadDataIfIdle; + + // Drive the DN pending-deletion scan through to completion: // fast (5s) while a scan is running, normal (60s) once finished. - // Honors the auto-reload toggle: if polling is OFF, do nothing. + // A running scan must be polled to completion and refresh the whole page even + // when Auto Refresh is off, otherwise the datanode sections stay stuck loading + // after the toggle is disabled. The toggle only governs the steady-state + // periodic reload once the scan has FINISHED. React.useEffect(() => { - if (!autoReload.isPolling) { + const scanInProgress = dnPendingDeletes.data.status !== "FINISHED"; + if (autoReload.isPolling) { + autoReload.startPolling(scanInProgress ? PENDING_POLL_INTERVAL : AUTO_RELOAD_INTERVAL_DEFAULT); return; } - autoReload.startPolling( - dnPendingDeletes.data.status === "FINISHED" - ? AUTO_RELOAD_INTERVAL_DEFAULT - : PENDING_POLL_INTERVAL - ); + if (scanInProgress) { + const timer = window.setInterval(() => loadDataIfIdleRef.current(), PENDING_POLL_INTERVAL); + return () => clearInterval(timer); + } }, [dnPendingDeletes.data.status, autoReload.isPolling]); // eslint-disable-line react-hooks/exhaustive-deps const dnReportStatus = (