Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 75 additions & 38 deletions src/sentry/preprod/vcs/pr_comments/snapshot_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
from sentry.preprod.models import PreprodArtifact, PreprodComparisonApproval
from sentry.preprod.snapshots.models import PreprodSnapshotComparison, PreprodSnapshotMetrics
from sentry.preprod.snapshots.utils import build_changes_map
from sentry.preprod.vcs.pr_comments.snapshot_templates import format_snapshot_pr_comment
from sentry.preprod.vcs.pr_comments.snapshot_templates import (
format_missing_base_snapshot_pr_comment,
format_snapshot_pr_comment,
format_solo_snapshot_pr_comment,
format_waiting_for_base_snapshot_pr_comment,
)
from sentry.preprod.vcs.pr_comments.tasks import find_existing_comment_id, save_pr_comment_result
from sentry.shared_integrations.exceptions import ApiError
from sentry.silo.base import SiloMode
Expand All @@ -38,7 +43,10 @@
retry=Retry(times=3, delay=60),
)
def create_preprod_snapshot_pr_comment_task(
preprod_artifact_id: int, caller: str | None = None, **kwargs: Any
preprod_artifact_id: int,
caller: str | None = None,
is_timeout_check: bool = False,
**kwargs: Any,
) -> None:
try:
artifact = PreprodArtifact.objects.select_related(
Expand Down Expand Up @@ -155,46 +163,75 @@ def create_preprod_snapshot_pr_comment_task(

base_artifact_map = PreprodArtifact.get_base_artifacts_for_commit(all_artifacts)

post_on_added = artifact.project.get_option(POST_ON_ADDED_OPTION_KEY, default=False)
post_on_removed = artifact.project.get_option(POST_ON_REMOVED_OPTION_KEY, default=True)
post_on_changed = artifact.project.get_option(POST_ON_CHANGED_OPTION_KEY, default=True)
post_on_renamed = artifact.project.get_option(POST_ON_RENAMED_OPTION_KEY, default=False)
changes_map = build_changes_map(
all_artifacts,
snapshot_metrics_map,
comparisons_map,
fail_on_added=post_on_added,
fail_on_removed=post_on_removed,
fail_on_changed=post_on_changed,
fail_on_renamed=post_on_renamed,
)
is_solo = not base_artifact_map

has_changes = any(changes_map.values())
# Failed comparisons are absent from changes_map (which only tracks
# SUCCESS state), so check comparisons_map directly to avoid
# suppressing failure reports.
has_failures = any(
c.state == PreprodSnapshotComparison.State.FAILED for c in comparisons_map.values()
)
if not has_changes and not has_failures:
logger.info(
"preprod.snapshot_pr_comments.create.skipped_no_diff",
extra={"artifact_id": artifact.id},
existing_comment_id = find_existing_comment_id(all_for_pr, "snapshots")
cc_id = cc.id

if is_solo:
app_ids = {a.app_id for a in all_artifacts if a.app_id}
has_previous_snapshots = (
PreprodSnapshotMetrics.objects.filter(
preprod_artifact__project_id=artifact.project_id,
preprod_artifact__app_id__in=app_ids,
)
.exclude(preprod_artifact__commit_comparison_id=commit_comparison.id)
.exists()
if app_ids
else False
)
return
is_first_upload = not has_previous_snapshots

comment_body = format_snapshot_pr_comment(
all_artifacts,
snapshot_metrics_map,
comparisons_map,
base_artifact_map,
changes_map,
approvals_map=approvals_map,
project=artifact.project,
)
if is_first_upload or not commit_comparison.base_sha:
comment_body = format_solo_snapshot_pr_comment(
all_artifacts, snapshot_metrics_map, project=artifact.project
)
elif not is_timeout_check:
comment_body = format_waiting_for_base_snapshot_pr_comment(
all_artifacts, snapshot_metrics_map, project=artifact.project
)
else:
comment_body = format_missing_base_snapshot_pr_comment(
all_artifacts, snapshot_metrics_map, project=artifact.project
)
Comment thread
cursor[bot] marked this conversation as resolved.
else:
post_on_added = artifact.project.get_option(POST_ON_ADDED_OPTION_KEY, default=False)
post_on_removed = artifact.project.get_option(POST_ON_REMOVED_OPTION_KEY, default=True)
post_on_changed = artifact.project.get_option(POST_ON_CHANGED_OPTION_KEY, default=True)
post_on_renamed = artifact.project.get_option(POST_ON_RENAMED_OPTION_KEY, default=False)
changes_map = build_changes_map(
all_artifacts,
snapshot_metrics_map,
comparisons_map,
fail_on_added=post_on_added,
fail_on_removed=post_on_removed,
fail_on_changed=post_on_changed,
fail_on_renamed=post_on_renamed,
)

existing_comment_id = find_existing_comment_id(all_for_pr, "snapshots")
cc_id = cc.id
has_changes = any(changes_map.values())
# Failed comparisons are absent from changes_map (which only tracks
# SUCCESS state), so check comparisons_map directly to avoid
# suppressing failure reports.
has_failures = any(
c.state == PreprodSnapshotComparison.State.FAILED for c in comparisons_map.values()
)
if not has_changes and not has_failures and not existing_comment_id:
logger.info(
"preprod.snapshot_pr_comments.create.skipped_no_diff",
extra={"artifact_id": artifact.id},
)
return
Comment thread
cursor[bot] marked this conversation as resolved.

comment_body = format_snapshot_pr_comment(
all_artifacts,
snapshot_metrics_map,
comparisons_map,
base_artifact_map,
changes_map,
approvals_map=approvals_map,
project=artifact.project,
)

post_snapshot_pr_comment_task.delay(
organization_id=organization.id,
Expand Down
80 changes: 74 additions & 6 deletions src/sentry/preprod/vcs/pr_comments/snapshot_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ def format_snapshot_pr_comment(
*,
project: Project,
) -> str:
"""Format a PR comment for snapshot comparisons."""
if not artifacts:
raise ValueError("Cannot format PR comment for empty artifact list")

Expand Down Expand Up @@ -90,12 +89,8 @@ def format_snapshot_pr_comment(
f" | {status} |"
)

settings_url = project.organization.absolute_url(
f"/settings/projects/{project.slug}/mobile-builds/", query="tab=snapshots"
)

table = f"{_HEADER}\n\n{COMPARISON_TABLE_HEADER}" + "\n".join(table_rows)
settings_link = f"[⚙️ {project.name} Snapshot Settings]({settings_url})"
settings_link = _format_settings_link(project)

return f"{table}\n\n{settings_link}"

Expand Down Expand Up @@ -137,3 +132,76 @@ def _section_cell(count: int, section: str, artifact_url: str) -> str:
if count > 0:
return f"[{count}]({artifact_url}?selectedTypes={section})"
return str(count)


def _format_settings_link(project: Project) -> str:
settings_url = project.organization.absolute_url(
f"/settings/projects/{project.slug}/mobile-builds/", query="tab=snapshots"
)
return f"[⚙️ {project.name} Snapshot Settings]({settings_url})"


def _format_solo_table(
artifacts: list[PreprodArtifact],
snapshot_metrics_map: dict[int, PreprodSnapshotMetrics],
) -> str:
table_rows = []
for artifact in artifacts:
app_display, app_id = _app_display_info(artifact)
artifact_url = get_preprod_artifact_url(artifact, view_type="snapshots")
name_cell = _format_name_cell(app_display, app_id, artifact_url)
metrics = snapshot_metrics_map.get(artifact.id)
if metrics:
table_rows.append(
f"| {name_cell} | - | - | - | - | - | - | ✅ {metrics.image_count} uploaded |"
)
else:
table_rows.append(f"| {name_cell} | - | - | - | - | - | - | {PROCESSING_STATUS} |")
return f"{_HEADER}\n\n{COMPARISON_TABLE_HEADER}" + "\n".join(table_rows)


_SOLO_MESSAGE = "Snapshot diffs will appear when we have a base upload to compare against. Make sure to upload snapshots from your main branch."
_WAITING_MESSAGE = "Waiting for base snapshots to finish uploading. This comment will update automatically within ~10 minutes or fail."
_MISSING_BASE_MESSAGE = "No base snapshots found to compare against. Make sure snapshots are uploaded from your main branch."


def _format_solo_comment(
artifacts: list[PreprodArtifact],
snapshot_metrics_map: dict[int, PreprodSnapshotMetrics],
message: str,
*,
project: Project,
) -> str:
if not artifacts:
raise ValueError("Cannot format PR comment for empty artifact list")
table = _format_solo_table(artifacts, snapshot_metrics_map)
return f"{table}\n\n{message}\n\n{_format_settings_link(project)}"


def format_solo_snapshot_pr_comment(
artifacts: list[PreprodArtifact],
snapshot_metrics_map: dict[int, PreprodSnapshotMetrics],
*,
project: Project,
) -> str:
return _format_solo_comment(artifacts, snapshot_metrics_map, _SOLO_MESSAGE, project=project)


def format_waiting_for_base_snapshot_pr_comment(
artifacts: list[PreprodArtifact],
snapshot_metrics_map: dict[int, PreprodSnapshotMetrics],
*,
project: Project,
) -> str:
return _format_solo_comment(artifacts, snapshot_metrics_map, _WAITING_MESSAGE, project=project)


def format_missing_base_snapshot_pr_comment(
artifacts: list[PreprodArtifact],
snapshot_metrics_map: dict[int, PreprodSnapshotMetrics],
*,
project: Project,
) -> str:
return _format_solo_comment(
artifacts, snapshot_metrics_map, _MISSING_BASE_MESSAGE, project=project
)
9 changes: 9 additions & 0 deletions src/sentry/preprod/vcs/status_checks/snapshots/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from sentry.preprod.snapshots.models import PreprodSnapshotComparison, PreprodSnapshotMetrics
from sentry.preprod.snapshots.utils import build_changes_map
from sentry.preprod.url_utils import get_preprod_artifact_url
from sentry.preprod.vcs.pr_comments.snapshot_tasks import create_preprod_snapshot_pr_comment_task
from sentry.preprod.vcs.status_checks.snapshots.templates import (
format_first_snapshot_status_check_messages,
format_generated_snapshot_status_check_messages,
Expand Down Expand Up @@ -319,6 +320,14 @@ def create_preprod_snapshot_status_check_task(
},
countdown=MISSING_BASE_GRACE_PERIOD_SECONDS,
)
create_preprod_snapshot_pr_comment_task.apply_async(
kwargs={
"preprod_artifact_id": preprod_artifact_id,
"caller": "missing_base_timeout",
"is_timeout_check": True,
},
countdown=MISSING_BASE_GRACE_PERIOD_SECONDS,
)


def _compute_snapshot_status(
Expand Down
Loading
Loading