From 9b4cd6471edd68cd02029d12eb98e71f987686cf Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 27 Mar 2026 18:41:30 +0000 Subject: [PATCH] refactor(recovery): strengthen inventory workspace treatment --- .../internal/subsystems/storage-recovery.md | 5 + .../RecoveryProtectedInventorySection.tsx | 92 +++++++++++++++---- .../Recovery/__tests__/Recovery.test.tsx | 10 +- 3 files changed, 87 insertions(+), 20 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index 3100fa3e6d..de486ebde8 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -473,6 +473,11 @@ protected estate into one endless slab; it should page or otherwise bound the primary inventory table so the workspace, filters, and adjacent activity panel remain readable as one monitoring surface instead of dissolving into a raw list dump. +That same protected-inventory surface should carry compact operator orientation +inside the table shell itself. `RecoveryProtectedInventorySection.tsx` should +expose the current bounded range, page, and sort state near the primary grid, +and the first column should carry enough secondary item metadata to read as a +monitored inventory row rather than a bare export line. That same hierarchy rule also applies to the activity panel below the primary workspace. `RecoveryActivitySection.tsx` should read as timeline analysis for the selected history window, not as a second copy of the page-level posture diff --git a/frontend-modern/src/components/Recovery/RecoveryProtectedInventorySection.tsx b/frontend-modern/src/components/Recovery/RecoveryProtectedInventorySection.tsx index b8b52b7096..9a72f1166d 100644 --- a/frontend-modern/src/components/Recovery/RecoveryProtectedInventorySection.tsx +++ b/frontend-modern/src/components/Recovery/RecoveryProtectedInventorySection.tsx @@ -169,6 +169,31 @@ export const RecoveryProtectedInventorySection: Component< return sortedRollups().slice(start, start + PROTECTED_ITEMS_PAGE_SIZE); }); + const pageStart = createMemo(() => + sortedRollups().length === 0 ? 0 : (protectedPage() - 1) * PROTECTED_ITEMS_PAGE_SIZE + 1, + ); + + const pageEnd = createMemo(() => + Math.min(protectedPage() * PROTECTED_ITEMS_PAGE_SIZE, sortedRollups().length), + ); + + const protectedSortLabel = createMemo(() => { + switch (protectedSortCol()) { + case 'item': + return 'Item'; + case 'type': + return 'Item Type'; + case 'platform': + return 'Platform'; + case 'lastBackup': + return 'Latest Point'; + case 'outcome': + return 'Outcome'; + default: + return 'Latest Point'; + } + }); + createEffect(() => { props.queryFilter(); props.platformFilter(); @@ -325,6 +350,27 @@ export const RecoveryProtectedInventorySection: Component< + 0}> +
+
+
+ + Showing {pageStart()} - {pageEnd()} + + + Page {protectedPage()} / {protectedTotalPages()} + + + Sorted by {protectedSortLabel()} + +
+
+ Open any item to drill directly into its recovery events. +
+
+
+
+
{getRecoveryProtectedItemsLoadingState().text} @@ -427,7 +473,7 @@ export const RecoveryProtectedInventorySection: Component< onClick={() => props.onSelectRollup(rollup.rollupId)} > -
- {label} - - - {getRecoveryRollupStatusPillLabel('never-succeeded')} - - - - - {getRecoveryRollupStatusPillLabel('stale')} - - +
+
+ {label} + + + {getRecoveryRollupStatusPillLabel('never-succeeded')} + + + + + {getRecoveryRollupStatusPillLabel('stale')} + + +
+
+ + {itemTypePresentation?.label} + + 0}> + + {platforms + .map((platform) => getSourcePlatformLabel(platform)) + .join(' • ')} + + +
@@ -456,7 +516,7 @@ export const RecoveryProtectedInventorySection: Component< fallback={—} > @@ -525,9 +585,7 @@ export const RecoveryProtectedInventorySection: Component< fallback={Showing 0 of 0 protected items} > - Showing {(protectedPage() - 1) * PROTECTED_ITEMS_PAGE_SIZE + 1} -{' '} - {Math.min(protectedPage() * PROTECTED_ITEMS_PAGE_SIZE, sortedRollups().length)} of{' '} - {sortedRollups().length} protected items + Showing {pageStart()} - {pageEnd()} of {sortedRollups().length} protected items
diff --git a/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx b/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx index c7599a71c2..9298adbbe3 100644 --- a/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx +++ b/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx @@ -213,7 +213,7 @@ describe('Recovery', () => { expect(within(inventoryTable).getByText('Item')).toBeInTheDocument(); expect(within(inventoryTable).getByText('Item Type')).toBeInTheDocument(); expect(within(inventoryTable).getByText('Platform')).toBeInTheDocument(); - expect(within(inventoryTable).getByText('VM')).toBeInTheDocument(); + expect(within(inventoryTable).getAllByText('VM').length).toBeGreaterThan(0); expect(screen.queryByText('Backups By Date')).not.toBeInTheDocument(); fireEvent.click(await screen.findByText('VM 123')); @@ -742,13 +742,17 @@ describe('Recovery', () => { render(() => ); expect(await screen.findByText('Showing 1 - 24 of 26 protected items')).toBeInTheDocument(); - expect(screen.getByText('Page 1 / 2')).toBeInTheDocument(); + expect(screen.getAllByText('Page 1 / 2').length).toBeGreaterThan(0); + expect(screen.getByText('Sorted by Latest Point')).toBeInTheDocument(); + expect( + screen.getByText('Open any item to drill directly into its recovery events.'), + ).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Next' })); await waitFor(() => { expect(screen.getByText('Showing 25 - 26 of 26 protected items')).toBeInTheDocument(); - expect(screen.getByText('Page 2 / 2')).toBeInTheDocument(); + expect(screen.getAllByText('Page 2 / 2').length).toBeGreaterThan(0); }); });