refactor(recovery): simplify default events grid

This commit is contained in:
rcourtman
2026-03-27 23:30:54 +00:00
parent 3793cc161b
commit 78c025ff00
4 changed files with 34 additions and 26 deletions
@@ -678,6 +678,12 @@ pattern as the protected inventory surface, not a separate large titled header
bar plus another full toolbar slab. Event filter labels should also stay on the
canonical short Pulse vocabulary like `Platform` and `Status` instead of
recovery-only variants such as `History platform` or `History status`.
That same events-table contract should also keep the default column set on a
monitoring-style scan path rather than a report-export path. Recovery events
should default to the concise columns operators need to triage quickly, while
secondary fields such as verification, size, target, and details remain
available through the shared column picker instead of crowding the baseline
grid.
That shared unified-resource dependency now also includes policy-governed
resource metadata on the frontend decode path: storage and recovery surfaces
that route through `frontend-modern/src/hooks/useUnifiedResources.ts` must
@@ -305,7 +305,7 @@ const Recovery: Component = () => {
{ id: 'verified', label: 'Verified', toggleable: true },
{ id: 'size', label: 'Size', toggleable: true },
{ id: 'method', label: 'Method' },
{ id: 'repository', label: 'Target' },
{ id: 'repository', label: 'Target', toggleable: true },
{ id: 'details', label: 'Details', toggleable: true },
{ id: 'outcome', label: 'Outcome' },
];
@@ -333,7 +333,7 @@ const Recovery: Component = () => {
const artifactColumnVisibility = useColumnVisibility(
STORAGE_KEYS.RECOVERY_HIDDEN_COLUMNS,
artifactColumns,
['entityId', 'cluster', 'nodeAgent', 'namespace'],
['entityId', 'cluster', 'nodeAgent', 'namespace', 'verified', 'size', 'repository', 'details'],
relevantArtifactColumnIDs,
LEGACY_RECOVERY_COLUMN_ID_ALIASES,
);
@@ -117,7 +117,7 @@ export const RecoveryHistoryTable: Component<RecoveryHistoryTableProps> = (props
>
<div class="overflow-x-auto">
<Table
class="w-full border-collapse text-xs whitespace-nowrap"
class="w-full border-collapse whitespace-nowrap"
style={{ 'min-width': props.tableMinWidth(), 'table-layout': 'fixed' }}
>
<TableHeader>
@@ -125,7 +125,7 @@ export const RecoveryHistoryTable: Component<RecoveryHistoryTableProps> = (props
<For each={props.mobileVisibleArtifactColumns()}>
{(column) => (
<TableHead
class={`py-0.5 px-3 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap ${getRecoveryArtifactColumnHeaderClass(
class={`px-3 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap ${getRecoveryArtifactColumnHeaderClass(
column.id,
)}`}
>
@@ -203,7 +203,7 @@ export const RecoveryHistoryTable: Component<RecoveryHistoryTableProps> = (props
case 'time':
return (
<TableCell
class={`whitespace-nowrap px-3 py-0.5 text-right font-mono text-[11px] tabular-nums ${getRecoveryEventTimeTextClass(
class={`whitespace-nowrap px-3 py-1 text-right font-mono text-[11px] tabular-nums ${getRecoveryEventTimeTextClass(
tsMs,
)}`}
>
@@ -212,10 +212,10 @@ export const RecoveryHistoryTable: Component<RecoveryHistoryTableProps> = (props
);
case 'type':
return (
<TableCell class="whitespace-nowrap px-3 py-0.5 text-center">
<TableCell class="whitespace-nowrap px-3 py-1 text-center">
<Show when={itemType} fallback={<span class="text-muted">—</span>}>
<span
class={`inline-flex min-w-[2.75rem] justify-center rounded px-1.5 py-px text-[9px] font-medium leading-none ${getRecoveryPointItemTypeBadgeClass(
class={`inline-flex min-w-[2.9rem] justify-center rounded px-1.5 py-px text-[10px] font-medium leading-none ${getRecoveryPointItemTypeBadgeClass(
point,
)}`}
>
@@ -227,11 +227,11 @@ export const RecoveryHistoryTable: Component<RecoveryHistoryTableProps> = (props
case 'item':
return (
<TableCell
class="max-w-[420px] whitespace-nowrap px-3 py-0.5 text-base-content"
class="max-w-[420px] whitespace-nowrap px-3 py-1 text-base-content"
title={item}
>
<div class="flex min-w-0 max-w-full items-center gap-2">
<span class="min-w-0 flex-1 truncate font-medium">
<span class="min-w-0 flex-1 truncate text-[12px] font-medium">
{item}
</span>
<span class="inline-flex shrink-0 items-center gap-1">
@@ -271,34 +271,34 @@ export const RecoveryHistoryTable: Component<RecoveryHistoryTableProps> = (props
);
case 'entityId':
return (
<TableCell class="whitespace-nowrap px-3 py-0.5 text-[11px] text-muted font-mono tabular-nums">
<TableCell class="whitespace-nowrap px-3 py-1 text-[11px] text-muted font-mono tabular-nums">
{entityId || '—'}
</TableCell>
);
case 'cluster':
return (
<TableCell class="whitespace-nowrap px-3 py-0.5 text-[11px] text-muted font-mono">
<TableCell class="whitespace-nowrap px-3 py-1 text-[11px] text-muted font-mono">
{cluster || '—'}
</TableCell>
);
case 'nodeAgent':
return (
<TableCell class="whitespace-nowrap px-3 py-0.5 text-[11px] text-muted font-mono">
<TableCell class="whitespace-nowrap px-3 py-1 text-[11px] text-muted font-mono">
{nodeAgent || '—'}
</TableCell>
);
case 'namespace':
return (
<TableCell class="whitespace-nowrap px-3 py-0.5 text-[11px] text-muted font-mono">
<TableCell class="whitespace-nowrap px-3 py-1 text-[11px] text-muted font-mono">
{namespace || '—'}
</TableCell>
);
case 'platform': {
const badge = getSourcePlatformBadge(platform);
return (
<TableCell class="whitespace-nowrap px-3 py-0.5 text-center">
<TableCell class="whitespace-nowrap px-3 py-1 text-center">
<span
class={`${badge?.classes || ''} inline-flex min-w-[3.25rem] justify-center px-1.5 py-px text-[9px] font-medium`}
class={`${badge?.classes || ''} inline-flex min-w-[3.25rem] justify-center px-1.5 py-px text-[10px] font-medium`}
>
{badge?.label || getSourcePlatformLabel(platform)}
</span>
@@ -307,7 +307,7 @@ export const RecoveryHistoryTable: Component<RecoveryHistoryTableProps> = (props
}
case 'verified':
return (
<TableCell class="whitespace-nowrap px-3 py-0.5 text-center">
<TableCell class="whitespace-nowrap px-3 py-1 text-center">
{typeof point.verified === 'boolean' ? (
point.verified ? (
<span
@@ -355,7 +355,7 @@ export const RecoveryHistoryTable: Component<RecoveryHistoryTableProps> = (props
);
case 'size':
return (
<TableCell class="whitespace-nowrap px-3 py-0.5 text-right font-mono text-[11px] tabular-nums text-muted">
<TableCell class="whitespace-nowrap px-3 py-1 text-right font-mono text-[11px] tabular-nums text-muted">
{point.sizeBytes && point.sizeBytes > 0
? formatBytes(point.sizeBytes)
: '—'}
@@ -363,9 +363,9 @@ export const RecoveryHistoryTable: Component<RecoveryHistoryTableProps> = (props
);
case 'method':
return (
<TableCell class="whitespace-nowrap px-3 py-0.5 text-center">
<TableCell class="whitespace-nowrap px-3 py-1 text-center">
<span
class={`inline-flex min-w-[3.5rem] justify-center rounded px-1.5 py-px text-[9px] font-medium ${getRecoveryArtifactModePresentation(
class={`inline-flex min-w-[3.5rem] justify-center rounded px-1.5 py-px text-[10px] font-medium ${getRecoveryArtifactModePresentation(
mode as ArtifactMode,
).badgeClassName}`}
>
@@ -376,7 +376,7 @@ export const RecoveryHistoryTable: Component<RecoveryHistoryTableProps> = (props
case 'repository':
return (
<TableCell
class="max-w-[220px] truncate whitespace-nowrap px-3 py-0.5 text-[11px] leading-4 text-base-content"
class="max-w-[220px] truncate whitespace-nowrap px-3 py-1 text-[11px] leading-4 text-base-content"
title={repoLabel}
>
{repoLabel || '—'}
@@ -385,7 +385,7 @@ export const RecoveryHistoryTable: Component<RecoveryHistoryTableProps> = (props
case 'details':
return (
<TableCell
class="max-w-[280px] truncate whitespace-nowrap px-3 py-0.5 text-[10px] leading-4 text-muted"
class="max-w-[280px] truncate whitespace-nowrap px-3 py-1 text-[10px] leading-4 text-muted"
title={detailsSummary}
>
{detailsSummary || '—'}
@@ -393,9 +393,9 @@ export const RecoveryHistoryTable: Component<RecoveryHistoryTableProps> = (props
);
case 'outcome':
return (
<TableCell class="whitespace-nowrap px-3 py-0.5 text-center">
<TableCell class="whitespace-nowrap px-3 py-1 text-center">
<span
class={`inline-flex min-w-[4.5rem] justify-center rounded px-1.5 py-px text-[9px] font-medium ${getRecoveryOutcomeBadgeClass(
class={`inline-flex min-w-[4.75rem] justify-center rounded px-1.5 py-px text-[10px] font-medium ${getRecoveryOutcomeBadgeClass(
outcome,
)}`}
>
@@ -405,7 +405,7 @@ export const RecoveryHistoryTable: Component<RecoveryHistoryTableProps> = (props
);
default:
return (
<TableCell class="whitespace-nowrap px-3 py-0.5 text-muted">
<TableCell class="whitespace-nowrap px-3 py-1 text-muted">
-
</TableCell>
);
@@ -243,6 +243,8 @@ describe('Recovery', () => {
expect(within(historyTable).getByText('Item Type')).toBeInTheDocument();
expect(within(historyTable).getByText('Item')).toBeInTheDocument();
expect(within(historyTable).getByText('Platform')).toBeInTheDocument();
expect(within(historyTable).queryByText('Target')).not.toBeInTheDocument();
expect(within(historyTable).queryByText('Details')).not.toBeInTheDocument();
});
it('persists the selected recovery workspace view in the route when explicitly changed', async () => {
@@ -438,7 +440,7 @@ describe('Recovery', () => {
expect(within(historyTable).getByText('default/data')).toBeInTheDocument();
expect(within(historyTable).getByText('PVC')).toBeInTheDocument();
expect(within(historyTable).getByText('K8s')).toBeInTheDocument();
expect(within(historyTable).getByText('Immutable copy')).toBeInTheDocument();
expect(within(historyTable).queryByText('Immutable copy')).not.toBeInTheDocument();
fireEvent.click(within(historyTable).getByText('11:00'));
@@ -469,7 +471,7 @@ describe('Recovery', () => {
const tables = await screen.findAllByRole('table');
const historyTable = tables.find((table) => within(table).queryByText('Local Copy'));
expect(historyTable).toBeDefined();
expect(historyTable).toHaveStyle({ 'min-width': '1142px', 'table-layout': 'fixed' });
expect(historyTable).toHaveStyle({ 'min-width': '980px', 'table-layout': 'fixed' });
});
it('keeps canonical item and platform columns visible when legacy hidden-column ids exist', async () => {