From f31975e0ef141d8dcaf15a3542cf7ffa009bc5b8 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Thu, 23 Apr 2026 16:09:28 +0200 Subject: [PATCH] feat(ui): checkpoints table collapsible, default collapsed --- .../AppDeploymentPage.module.css | 36 +++++ .../CheckpointsTable.test.tsx | 30 +++- .../AppDeploymentPage/CheckpointsTable.tsx | 152 ++++++++++-------- 3 files changed, 143 insertions(+), 75 deletions(-) diff --git a/ui/src/pages/AppsTab/AppDeploymentPage/AppDeploymentPage.module.css b/ui/src/pages/AppsTab/AppDeploymentPage/AppDeploymentPage.module.css index 0726b183..49184bcc 100644 --- a/ui/src/pages/AppsTab/AppDeploymentPage/AppDeploymentPage.module.css +++ b/ui/src/pages/AppsTab/AppDeploymentPage/AppDeploymentPage.module.css @@ -400,3 +400,39 @@ position: relative; z-index: 1; } + +/* Collapsible Checkpoints header */ +.checkpointsSection { + display: flex; + flex-direction: column; +} + +.checkpointsHeader { + display: inline-flex; + align-items: center; + gap: 6px; + background: none; + border: none; + padding: 8px 0; + font-size: 13px; + font-weight: 600; + color: var(--text-primary); + cursor: pointer; + text-align: left; +} + +.checkpointsHeader:hover { + color: var(--amber); +} + +.checkpointsChevron { + color: var(--text-muted); + font-size: 11px; + width: 12px; + text-align: center; +} + +.checkpointsCount { + color: var(--text-muted); + font-weight: 400; +} diff --git a/ui/src/pages/AppsTab/AppDeploymentPage/CheckpointsTable.test.tsx b/ui/src/pages/AppsTab/AppDeploymentPage/CheckpointsTable.test.tsx index 2b3e738a..b26ec90e 100644 --- a/ui/src/pages/AppsTab/AppDeploymentPage/CheckpointsTable.test.tsx +++ b/ui/src/pages/AppsTab/AppDeploymentPage/CheckpointsTable.test.tsx @@ -25,10 +25,23 @@ const stoppedDep: Deployment = { deployedConfigSnapshot: { jarVersionId: 'v6id', agentConfig: null, containerConfig: {}, sensitiveKeys: null }, }; +function expand() { + fireEvent.click(screen.getByRole('button', { name: /checkpoints/i })); +} + describe('CheckpointsTable', () => { - it('renders a row per checkpoint with version, jar, deployer', () => { + it('defaults to collapsed — header visible, rows hidden', () => { wrap( {}} />); + expect(screen.getByRole('button', { name: /checkpoints \(1\)/i })).toBeInTheDocument(); + expect(screen.queryByText('v6')).toBeNull(); + expect(screen.queryByText('my-app-1.2.3.jar')).toBeNull(); + }); + + it('clicking header expands to show rows', () => { + wrap( {}} />); + expand(); expect(screen.getByText('v6')).toBeInTheDocument(); expect(screen.getByText('my-app-1.2.3.jar')).toBeInTheDocument(); expect(screen.getByText('alice')).toBeInTheDocument(); @@ -38,7 +51,7 @@ describe('CheckpointsTable', () => { const onSelect = vi.fn(); wrap(); - // Use the version text as the row anchor (most stable selector) + expand(); fireEvent.click(screen.getByText('v6').closest('tr')!); expect(onSelect).toHaveBeenCalledWith('d1'); }); @@ -47,6 +60,7 @@ describe('CheckpointsTable', () => { const noActor = { ...stoppedDep, createdBy: null }; wrap( {}} />); + expand(); expect(screen.getByText('—')).toBeInTheDocument(); }); @@ -54,14 +68,14 @@ describe('CheckpointsTable', () => { const pruned = { ...stoppedDep, appVersionId: 'unknown' }; wrap( {}} />); + expand(); expect(screen.getByText(/archived/i)).toBeInTheDocument(); }); - it('excludes the currently-running deployment', () => { - wrap( { + const { container } = wrap( {}} />); - expect(screen.queryByText('v6')).toBeNull(); - expect(screen.getByText(/no past deployments/i)).toBeInTheDocument(); + expect(container).toBeEmptyDOMElement(); }); it('caps visible rows at jarRetentionCount and shows expander', () => { @@ -70,7 +84,7 @@ describe('CheckpointsTable', () => { })); wrap( {}} />); - // 3 visible rows + 1 header row = 4 rows max in the table + expand(); expect(screen.getAllByRole('row').length).toBeLessThanOrEqual(4); expect(screen.getByText(/show older \(7\)/i)).toBeInTheDocument(); }); @@ -78,6 +92,7 @@ describe('CheckpointsTable', () => { it('shows all rows when jarRetentionCount >= total', () => { wrap( {}} />); + expand(); expect(screen.queryByText(/show older/i)).toBeNull(); }); @@ -87,6 +102,7 @@ describe('CheckpointsTable', () => { })); wrap( {}} />); + expand(); expect(screen.getByText(/show older \(5\)/i)).toBeInTheDocument(); }); }); diff --git a/ui/src/pages/AppsTab/AppDeploymentPage/CheckpointsTable.tsx b/ui/src/pages/AppsTab/AppDeploymentPage/CheckpointsTable.tsx index db1e9140..27708cd8 100644 --- a/ui/src/pages/AppsTab/AppDeploymentPage/CheckpointsTable.tsx +++ b/ui/src/pages/AppsTab/AppDeploymentPage/CheckpointsTable.tsx @@ -22,6 +22,7 @@ export function CheckpointsTable({ onSelect, }: CheckpointsTableProps) { const [expanded, setExpanded] = useState(false); + const [open, setOpen] = useState(false); const versionMap = new Map(versions.map((v) => [v.id, v])); const checkpoints = deployments @@ -37,75 +38,90 @@ export function CheckpointsTable({ const hidden = checkpoints.length - visible.length; return ( -
- - - - - - - - - - - - - - {visible.map((d) => { - const v = versionMap.get(d.appVersionId); - const archived = !v; - const strategyLabel = - d.deploymentStrategy === 'BLUE_GREEN' ? 'blue/green' : 'rolling'; - return ( - onSelect(d.id)} - > - - - - - - - +
+ + {open && ( +
+
VersionJARDeployed byDeployedStrategyOutcome
- - - {v ? ( - {v.jarFilename} - ) : ( - <> - JAR pruned -
archived — JAR pruned
- - )} -
- {d.createdBy ?? } - - {d.deployedAt && timeAgo(d.deployedAt)} -
{d.deployedAt}
-
- {strategyLabel} - - - {d.status} - -
+ + + + + + + + + - ); - })} - -
VersionJARDeployed byDeployedStrategyOutcome
- {hidden > 0 && !expanded && ( - + + + {visible.map((d) => { + const v = versionMap.get(d.appVersionId); + const archived = !v; + const strategyLabel = + d.deploymentStrategy === 'BLUE_GREEN' ? 'blue/green' : 'rolling'; + return ( + onSelect(d.id)} + > + + + + + {v ? ( + {v.jarFilename} + ) : ( + <> + JAR pruned +
archived — JAR pruned
+ + )} + + + {d.createdBy ?? } + + + {d.deployedAt && timeAgo(d.deployedAt)} +
{d.deployedAt}
+ + + {strategyLabel} + + + + {d.status} + + + › + + ); + })} + + + {hidden > 0 && !expanded && ( + + )} +
)} );