Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, object> = {
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(<Capacity />);

// 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');
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -202,18 +202,27 @@ const Capacity: React.FC<object> = () => {
}
};

// 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 = (
Expand Down