From f4820811add46b81cecce8c98ea95db882002f74 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 26 Mar 2026 09:13:20 +0000 Subject: [PATCH] refactor(recovery): align placement columns with canonical labels --- .../internal/subsystems/storage-recovery.md | 8 ++++--- .../src/components/Recovery/Recovery.tsx | 6 ++--- .../Recovery/RecoveryHistoryTable.tsx | 12 +++++++--- .../Recovery/__tests__/Recovery.test.tsx | 24 +++++++++++++++++++ 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index 2432c8386..b7865692f 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -365,9 +365,11 @@ Cluster, node, and namespace facets remain valid supporting filters for Proxmox-heavy and Kubernetes-heavy operators, but the governed recovery surface must present them through platform-neutral labels such as `Cluster / Site`, `Host / Agent`, and `Namespace / Group` across advanced -filters, active chips, table headers, and point details so the page treats -placement as optional context inside a multi-platform recovery model rather -than a Proxmox-native spine. +filters, active chips, table headers, column-picker entries, and point +details so the page treats placement as optional context inside a +multi-platform recovery model rather than a Proxmox-native spine. When +normalized display labels are present, the visible history rows must prefer +those labels over raw transport values for the same placement dimensions. The recovery table presentation helper now owns the canonical subject-type label fallback for recovery rows and delegates its title-casing to the shared `frontend-modern/src/utils/textPresentation.ts` helper rather than keeping a diff --git a/frontend-modern/src/components/Recovery/Recovery.tsx b/frontend-modern/src/components/Recovery/Recovery.tsx index 389794c5d..a96be5d42 100644 --- a/frontend-modern/src/components/Recovery/Recovery.tsx +++ b/frontend-modern/src/components/Recovery/Recovery.tsx @@ -294,9 +294,9 @@ const Recovery: Component = () => { { ...createVisibleCanonicalTypeColumn(), label: getRecoveryArtifactColumnLabel('type', 'Type') }, { id: 'subject', label: getRecoveryArtifactColumnLabel('subject', 'Subject') }, { id: 'entityId', label: 'ID', toggleable: true }, - { id: 'cluster', label: 'Cluster', toggleable: true }, - { id: 'nodeAgent', label: 'Node/Agent', toggleable: true }, - { id: 'namespace', label: 'Namespace', toggleable: true }, + { id: 'cluster', label: getRecoveryArtifactColumnLabel('cluster', 'Cluster'), toggleable: true }, + { id: 'nodeAgent', label: getRecoveryArtifactColumnLabel('nodeAgent', 'Node/Agent'), toggleable: true }, + { id: 'namespace', label: getRecoveryArtifactColumnLabel('namespace', 'Namespace'), toggleable: true }, { id: 'source', label: getRecoveryArtifactColumnLabel('source', 'Source') }, { id: 'verified', label: 'Verified', toggleable: true }, { id: 'size', label: 'Size', toggleable: true }, diff --git a/frontend-modern/src/components/Recovery/RecoveryHistoryTable.tsx b/frontend-modern/src/components/Recovery/RecoveryHistoryTable.tsx index 36ed23abb..46e62b93b 100644 --- a/frontend-modern/src/components/Recovery/RecoveryHistoryTable.tsx +++ b/frontend-modern/src/components/Recovery/RecoveryHistoryTable.tsx @@ -180,9 +180,15 @@ export const RecoveryHistoryTable: Component = (props const repoLabel = getRecoveryPointRepositoryLabel(point); const detailsSummary = getRecoveryPointDetailsSummary(point); const entityId = String(point.entityId || '').trim(); - const cluster = String(point.cluster || '').trim(); - const nodeAgent = String(point.node || '').trim(); - const namespace = String(point.namespace || '').trim(); + const cluster = String( + point.display?.clusterLabel || point.cluster || '', + ).trim(); + const nodeAgent = String( + point.display?.nodeHostLabel || point.display?.nodeAgentLabel || point.node || '', + ).trim(); + const namespace = String( + point.display?.namespaceLabel || point.namespace || '', + ).trim(); return ( <> diff --git a/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx b/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx index 15c1a85a9..8355aaae4 100644 --- a/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx +++ b/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx @@ -360,6 +360,30 @@ describe('Recovery', () => { expect(within(detailsPanel as HTMLTableCellElement).getByText('Finance')).toBeInTheDocument(); }); + it('keeps optional history placement columns on the neutral recovery vocabulary', async () => { + facetsPayload.clusters = ['lab-cluster']; + facetsPayload.nodesAgents = ['pve-01']; + facetsPayload.namespaces = ['finance']; + + render(() => ); + + fireEvent.click(await screen.findByText('VM 123')); + await screen.findByText(/Showing 1 - 1 of 1 recovery points/i); + + fireEvent.click(screen.getByRole('button', { name: /columns/i })); + + expect(await screen.findByText('Cluster / Site')).toBeInTheDocument(); + expect(screen.getByText('Host / Agent')).toBeInTheDocument(); + expect(screen.getByText('Namespace / Group')).toBeInTheDocument(); + + fireEvent.click(screen.getByLabelText('Cluster / Site')); + + const tables = await screen.findAllByRole('table'); + const table = tables[tables.length - 1]; + expect(within(table).getByText('Cluster / Site')).toBeInTheDocument(); + expect(within(table).getByText('Lab Cluster')).toBeInTheDocument(); + }); + it('filters protected rollups by provider', async () => { render(() => );