refactor(recovery): align placement columns with canonical labels

This commit is contained in:
rcourtman 2026-03-26 09:13:20 +00:00
parent adc2d4267d
commit f4820811ad
4 changed files with 41 additions and 9 deletions

View file

@ -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

View file

@ -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 },

View file

@ -180,9 +180,15 @@ export const RecoveryHistoryTable: Component<RecoveryHistoryTableProps> = (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 (
<>

View file

@ -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(() => <Recovery />);
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(() => <Recovery />);