fix(deploy): Checkpoints — preserve STOPPED history, fix filter + placement
- Backend: rename deleteTerminalByAppAndEnvironment → deleteFailedByAppAndEnvironment. STOPPED rows were being wiped on every redeploy, so Checkpoints was always empty. Now only FAILED rows are pruned; STOPPED deployments are retained as restorable checkpoints (they still carry deployed_config_snapshot from their RUNNING window). - UI filter: any deployment with a snapshot is a checkpoint (was RUNNING|DEGRADED only, which excluded the main case — the previous blue/green deployment now in STOPPED). - UI placement: Checkpoints disclosure now renders inside IdentitySection, matching the design spec. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,15 +15,11 @@ export function Checkpoints({ deployments, versions, currentDeploymentId, onRest
|
||||
const [open, setOpen] = useState(false);
|
||||
const versionMap = new Map(versions.map((v) => [v.id, v]));
|
||||
|
||||
// Deployments that reached COMPLETE stage and captured a snapshot (RUNNING or DEGRADED).
|
||||
// Exclude the currently-running one.
|
||||
// Any deployment that captured a snapshot is restorable — that covers RUNNING,
|
||||
// DEGRADED, and STOPPED (blue/green swap previous, user-stopped). Exclude the
|
||||
// currently-running one and anything without a snapshot (FAILED, STARTING).
|
||||
const checkpoints = deployments
|
||||
.filter(
|
||||
(d) =>
|
||||
d.deployedAt &&
|
||||
(d.status === 'RUNNING' || d.status === 'DEGRADED') &&
|
||||
d.id !== currentDeploymentId,
|
||||
)
|
||||
.filter((d) => d.deployedConfigSnapshot && d.id !== currentDeploymentId)
|
||||
.sort((a, b) => (b.deployedAt ?? '').localeCompare(a.deployedAt ?? ''));
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useRef } from 'react';
|
||||
import { useRef, type ReactNode } from 'react';
|
||||
import { SectionHeader, Input, MonoText, Button } from '@cameleer/design-system';
|
||||
import type { App, AppVersion } from '../../../api/queries/admin/apps';
|
||||
import type { Environment } from '../../../api/queries/admin/environments';
|
||||
@@ -25,11 +25,12 @@ interface IdentitySectionProps {
|
||||
stagedJar: File | null;
|
||||
onStagedJarChange: (file: File | null) => void;
|
||||
deploying: boolean;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export function IdentitySection({
|
||||
mode, environment, app, currentVersion,
|
||||
name, onNameChange, stagedJar, onStagedJarChange, deploying,
|
||||
name, onNameChange, stagedJar, onStagedJarChange, deploying, children,
|
||||
}: IdentitySectionProps) {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const slug = app?.slug ?? slugify(name);
|
||||
@@ -109,6 +110,7 @@ export function IdentitySection({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -467,7 +467,7 @@ export default function AppDeploymentPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Identity & Artifact ── */}
|
||||
{/* ── Identity & Artifact (with Checkpoints for deployed apps) ── */}
|
||||
<IdentitySection
|
||||
mode={mode}
|
||||
environment={env}
|
||||
@@ -478,17 +478,16 @@ export default function AppDeploymentPage() {
|
||||
stagedJar={stagedJar}
|
||||
onStagedJarChange={setStagedJar}
|
||||
deploying={deploymentInProgress}
|
||||
/>
|
||||
|
||||
{/* ── Checkpoints (deployed apps only) ── */}
|
||||
{app && (
|
||||
<Checkpoints
|
||||
deployments={deployments}
|
||||
versions={versions}
|
||||
currentDeploymentId={currentDeployment?.id ?? null}
|
||||
onRestore={handleRestore}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{app && (
|
||||
<Checkpoints
|
||||
deployments={deployments}
|
||||
versions={versions}
|
||||
currentDeploymentId={currentDeployment?.id ?? null}
|
||||
onRestore={handleRestore}
|
||||
/>
|
||||
)}
|
||||
</IdentitySection>
|
||||
|
||||
{/* ── Config tabs ── */}
|
||||
<div className={styles.tabGroup}>
|
||||
|
||||
Reference in New Issue
Block a user