From 76c06578e02d86a71c56a9a538cbc21e3f33f975 Mon Sep 17 00:00:00 2001 From: Nico Hinderling Date: Tue, 12 May 2026 15:19:21 -0700 Subject: [PATCH 1/3] fix(snapshots): Show "Waiting for base" instead of "Base" when base_sha exists but base artifact not found --- .../api/endpoints/preprod_artifact_snapshot.py | 7 ++++++- .../models/project_preprod_build_details_models.py | 6 +++++- .../preprod/preprodBuildsSnapshotTable.tsx | 11 +++++++++++ .../preprod/snapshots/main/snapshotMainContent.tsx | 12 +++++++++++- static/app/views/preprod/snapshots/snapshots.tsx | 2 +- static/app/views/preprod/types/buildDetailsTypes.ts | 7 ++++++- static/app/views/preprod/types/snapshotTypes.ts | 2 +- 7 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/sentry/preprod/api/endpoints/preprod_artifact_snapshot.py b/src/sentry/preprod/api/endpoints/preprod_artifact_snapshot.py index 1dbc03c8ddf82f..1898577648b39a 100644 --- a/src/sentry/preprod/api/endpoints/preprod_artifact_snapshot.py +++ b/src/sentry/preprod/api/endpoints/preprod_artifact_snapshot.py @@ -344,7 +344,12 @@ def get(self, request: Request, organization: Organization, snapshot_id: str) -> if pending_or_failed_state is not None: comparison_state = PreprodSnapshotComparison.State(pending_or_failed_state).name - comparison_type = "diff" if comparison_manifest is not None else "solo" + if comparison_manifest is not None: + comparison_type = "diff" + elif commit_comparison and commit_comparison.base_sha: + comparison_type = "waiting_for_base" + else: + comparison_type = "solo" run_info: SnapshotComparisonRunInfo | None = None if comparison_state is not None: diff --git a/src/sentry/preprod/api/models/project_preprod_build_details_models.py b/src/sentry/preprod/api/models/project_preprod_build_details_models.py index ee936c16ca36ea..72225f31bd2842 100644 --- a/src/sentry/preprod/api/models/project_preprod_build_details_models.py +++ b/src/sentry/preprod/api/models/project_preprod_build_details_models.py @@ -299,7 +299,11 @@ def to_snapshot_comparison_info(head_artifact: PreprodArtifact) -> SnapshotCompa reverse=True, ) comparison = comparisons[0] if comparisons else None - if comparison: + if not comparison: + cc = head_artifact.commit_comparison + if cc and cc.base_sha: + comparison_state = "waiting_for_base" + elif comparison: comparison_state = PreprodSnapshotComparison.State(comparison.state).name.lower() if comparison.state == PreprodSnapshotComparison.State.SUCCESS: images_added = comparison.images_added diff --git a/static/app/components/preprod/preprodBuildsSnapshotTable.tsx b/static/app/components/preprod/preprodBuildsSnapshotTable.tsx index 2f82ccb2b9ff12..6977703f52eacf 100644 --- a/static/app/components/preprod/preprodBuildsSnapshotTable.tsx +++ b/static/app/components/preprod/preprodBuildsSnapshotTable.tsx @@ -65,6 +65,17 @@ function ChangeCounts({ if (!comparisonState) { return {t('Base')}; } + if (comparisonState === 'waiting_for_base') { + return ( + + {t('Waiting for base')} + + ); + } if (comparisonState === 'pending') { return ( diff --git a/static/app/views/preprod/snapshots/main/snapshotMainContent.tsx b/static/app/views/preprod/snapshots/main/snapshotMainContent.tsx index 52190ddc6742e2..848320cf331f39 100644 --- a/static/app/views/preprod/snapshots/main/snapshotMainContent.tsx +++ b/static/app/views/preprod/snapshots/main/snapshotMainContent.tsx @@ -54,7 +54,7 @@ export interface NavButtonRefs { interface SnapshotMainContentProps { canNavigateNext: boolean; canNavigatePrev: boolean; - comparisonType: 'diff' | 'solo' | undefined; + comparisonType: 'diff' | 'solo' | 'waiting_for_base' | undefined; diffImageBaseUrl: string; diffMode: DiffMode; hasDiffComparison: boolean; @@ -192,6 +192,16 @@ export function SnapshotMainContent({ ); } else if (comparisonType === 'solo') { soloDiffToggle = {t('Base')}; + } else if (comparisonType === 'waiting_for_base') { + soloDiffToggle = ( + + {t('Waiting for base')} + + ); } if (viewMode === 'list') { diff --git a/static/app/views/preprod/snapshots/snapshots.tsx b/static/app/views/preprod/snapshots/snapshots.tsx index 7aac75fd30b41e..1e773a8d9c9007 100644 --- a/static/app/views/preprod/snapshots/snapshots.tsx +++ b/static/app/views/preprod/snapshots/snapshots.tsx @@ -266,7 +266,7 @@ export default function SnapshotsPage() { viewOverride === 'solo' ? 'solo' : (data?.comparison_type ?? 'solo'); const comparisonRunInfo = data?.comparison_run_info; - const isSoloView = comparisonType === 'solo'; + const isSoloView = comparisonType === 'solo' || comparisonType === 'waiting_for_base'; const handleToggleView = useCallback(() => { const {view: _view, ...restQuery} = location.query; if (isSoloView) { diff --git a/static/app/views/preprod/types/buildDetailsTypes.ts b/static/app/views/preprod/types/buildDetailsTypes.ts index 2b538cdcacf63c..92557d103ab9aa 100644 --- a/static/app/views/preprod/types/buildDetailsTypes.ts +++ b/static/app/views/preprod/types/buildDetailsTypes.ts @@ -194,7 +194,12 @@ export function isStatusCheckFailure( return result?.success === false; } -export type SnapshotComparisonState = 'pending' | 'processing' | 'success' | 'failed'; +export type SnapshotComparisonState = + | 'pending' + | 'processing' + | 'success' + | 'failed' + | 'waiting_for_base'; export type SnapshotApprovalStatus = 'approved' | 'requires_approval'; interface SnapshotComparisonInfo { diff --git a/static/app/views/preprod/types/snapshotTypes.ts b/static/app/views/preprod/types/snapshotTypes.ts index 32b84062e3a3b3..60e6ebe42aa79f 100644 --- a/static/app/views/preprod/types/snapshotTypes.ts +++ b/static/app/views/preprod/types/snapshotTypes.ts @@ -40,7 +40,7 @@ interface SnapshotApprovalInfo { } export interface SnapshotDetailsApiResponse { - comparison_type: 'solo' | 'diff'; + comparison_type: 'solo' | 'diff' | 'waiting_for_base'; head_artifact_id: string; image_count: number; images: SnapshotImage[]; From 57342fac5bf6bf25d2f4af4f928a4b69797ea799 Mon Sep 17 00:00:00 2001 From: Nico Hinderling Date: Tue, 12 May 2026 15:24:42 -0700 Subject: [PATCH 2/3] fix(snapshots): Add waiting_for_base to SnapshotComparisonInfo Literal type The comparison_state field was being set to "waiting_for_base" but the Pydantic Literal type only allowed "pending", "processing", "success", and "failed", which would cause a ValidationError at runtime. Co-Authored-By: Claude Opus 4.6 --- .../api/models/project_preprod_build_details_models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sentry/preprod/api/models/project_preprod_build_details_models.py b/src/sentry/preprod/api/models/project_preprod_build_details_models.py index 72225f31bd2842..e56bccf27a3770 100644 --- a/src/sentry/preprod/api/models/project_preprod_build_details_models.py +++ b/src/sentry/preprod/api/models/project_preprod_build_details_models.py @@ -91,7 +91,9 @@ class PostedStatusChecks(BaseModel): class SnapshotComparisonInfo(BaseModel): image_count: int - comparison_state: Literal["pending", "processing", "success", "failed"] | None = None + comparison_state: ( + Literal["pending", "processing", "success", "failed", "waiting_for_base"] | None + ) = None comparison_error_message: str | None = None images_added: int = 0 images_removed: int = 0 From 2843d85add27feae9466d085e3efc2361adc44d4 Mon Sep 17 00:00:00 2001 From: Nico Hinderling Date: Tue, 12 May 2026 15:36:37 -0700 Subject: [PATCH 3/3] fix(snapshots): Only show waiting_for_base when no comparison record exists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a comparison record exists in PENDING/PROCESSING/FAILED state, the base artifact was already found. Showing "waiting_for_base" in that case is misleading — fall through to "solo" and let the run_info carry the actual comparison state. Co-Authored-By: Claude Opus 4.6 --- src/sentry/preprod/api/endpoints/preprod_artifact_snapshot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry/preprod/api/endpoints/preprod_artifact_snapshot.py b/src/sentry/preprod/api/endpoints/preprod_artifact_snapshot.py index 1898577648b39a..898134a5c091ec 100644 --- a/src/sentry/preprod/api/endpoints/preprod_artifact_snapshot.py +++ b/src/sentry/preprod/api/endpoints/preprod_artifact_snapshot.py @@ -346,7 +346,7 @@ def get(self, request: Request, organization: Organization, snapshot_id: str) -> if comparison_manifest is not None: comparison_type = "diff" - elif commit_comparison and commit_comparison.base_sha: + elif commit_comparison and commit_comparison.base_sha and pending_or_failed_state is None: comparison_type = "waiting_for_base" else: comparison_type = "solo"