From ee8fe09f8707925c57831a03da8b92245e8b0275 Mon Sep 17 00:00:00 2001 From: Priyesh Karatha Date: Tue, 23 Jun 2026 10:06:49 +0530 Subject: [PATCH] HDDS-15587. Show 0 for offline DN pending deletion in Recon UI --- .../src/__tests__/capacity/Capacity.test.tsx | 44 +++++++++++++++++++ .../src/v2/pages/capacity/capacity.tsx | 10 ++--- 2 files changed, 49 insertions(+), 5 deletions(-) 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 d6f878aaad02..6951a25e16b2 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 @@ -95,6 +95,50 @@ describe('Capacity Page', () => { expect(datanodeCard).toHaveTextContent(/FREE SPACE\s*3\s*KB/i); }); + test('clamps pendingBlockSize to 0 when selected datanode reports -1 (offline/unreachable)', async () => { + capacityServer.use( + rest.get('api/v1/pendingDeletion', (req, res, ctx) => { + const component = req.url.searchParams.get('component'); + if (component === 'dn') { + return res( + ctx.status(200), + ctx.json({ + ...mockResponses.DnPendingDeletion, + pendingDeletionPerDataNode: [ + { hostName: 'dn-1', datanodeUuid: 'uuid-1', pendingBlockSize: -1 }, + { hostName: 'dn-2', datanodeUuid: 'uuid-2', pendingBlockSize: 2048 } + ] + }) + ); + } + 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.' })); + }) + ); + + render(); + + const downloadLink = await screen.findByText('Download Insights'); + const datanodeCard = downloadLink.closest('.ant-card'); + expect(datanodeCard).not.toBeNull(); + if (!datanodeCard) { + return; + } + // dn-1 is selected by default; its pendingBlockSize is -1 (offline sentinel). + // PENDING DELETION should show 0 B, not a negative value. + await waitFor(() => + expect(datanodeCard).toHaveTextContent(/PENDING DELETION\s*0\s*B/i) + ); + // USED SPACE = used (4096) + clamped pendingBlockSize (0) = 4 KB + expect(datanodeCard).toHaveTextContent(/USED SPACE\s*4\s*KB/i); + }); + test('shows scm-only error state when SCM pending deletion returns sentinel failure values', async () => { capacityServer.use( rest.get('api/v1/pendingDeletion', (req, res, ctx) => { 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 68b614c1b1d1..883123d8df7d 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 @@ -127,6 +127,9 @@ const Capacity: React.FC = () => { const selectedDNDetails: DataNodeUsage & { pendingBlockSize: number } = React.useMemo(() => { const selected = storageDistribution.data.dataNodeUsage.find(datanode => datanode.hostName === selectedDatanode) ?? storageDistribution.data.dataNodeUsage[0]; + const dnPendingEntry = dnPendingDeletes.data.pendingDeletionPerDataNode?.find( + dn => dn.hostName === (selected?.hostName ?? selectedDatanode) + ) ?? { hostName: "unknown-host", datanodeUuid: "unknown-uuid", pendingBlockSize: 0 }; return { ...(selected ?? { datanodeUuid: "unknown-uuid", @@ -138,11 +141,8 @@ const Capacity: React.FC = () => { minimumFreeSpace: 0, reserved: 0 }), - ...dnPendingDeletes.data.pendingDeletionPerDataNode?.find(dn => dn.hostName === (selected?.hostName ?? selectedDatanode)) ?? { - hostName: "unknown-host", - datanodeUuid: "unknown-uuid", - pendingBlockSize: 0 - } + ...dnPendingEntry, + pendingBlockSize: Math.max(0, dnPendingEntry.pendingBlockSize) } }, [selectedDatanode, storageDistribution.data.dataNodeUsage, dnPendingDeletes.data.pendingDeletionPerDataNode]);