Skip to content
Merged
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
44 changes: 41 additions & 3 deletions src/sentry/preprod/vcs/status_checks/size/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@
from sentry.models.commitcomparison import CommitComparison
from sentry.models.project import Project
from sentry.models.repository import Repository
from sentry.preprod.models import PreprodArtifact, PreprodArtifactSizeMetrics
from sentry.preprod.models import (
PreprodArtifact,
PreprodArtifactSizeMetrics,
PreprodComparisonApproval,
)
from sentry.preprod.url_utils import get_preprod_artifact_url
from sentry.preprod.vcs.status_checks.size.templates import format_status_check_messages
from sentry.preprod.vcs.status_checks.size.types import StatusCheckRule, TriggeredRule
Expand Down Expand Up @@ -163,6 +167,7 @@ def create_preprod_status_check_task(
return

size_metrics_map: dict[int, list[PreprodArtifactSizeMetrics]] = {}
approvals_map: dict[int, PreprodComparisonApproval] = {}
if all_artifacts:
artifact_ids = [artifact.id for artifact in all_artifacts]
size_metrics_qs = PreprodArtifactSizeMetrics.objects.filter(
Expand All @@ -174,15 +179,32 @@ def create_preprod_status_check_task(
size_metrics_map[metrics.preprod_artifact_id] = []
size_metrics_map[metrics.preprod_artifact_id].append(metrics)

approval_qs = PreprodComparisonApproval.objects.filter(
preprod_artifact_id__in=artifact_ids,
preprod_feature_type=PreprodComparisonApproval.FeatureType.SIZE,
approval_status=PreprodComparisonApproval.ApprovalStatus.APPROVED,
)
for approval in approval_qs:
approvals_map[approval.preprod_artifact_id] = approval

rules = _get_status_check_rules(preprod_artifact.project)
base_size_metrics_map = _fetch_base_size_metrics(all_artifacts, preprod_artifact.project)

status, triggered_rules = _compute_overall_status(
all_artifacts, size_metrics_map, rules=rules, base_size_metrics_map=base_size_metrics_map
all_artifacts,
size_metrics_map,
rules=rules,
base_size_metrics_map=base_size_metrics_map,
approvals_map=approvals_map,
)

title, subtitle, summary = format_status_check_messages(
all_artifacts, size_metrics_map, status, preprod_artifact.project, triggered_rules
all_artifacts,
size_metrics_map,
status,
preprod_artifact.project,
triggered_rules,
approvals_map,
)

target_url = get_preprod_artifact_url(preprod_artifact)
Expand Down Expand Up @@ -211,6 +233,7 @@ def create_preprod_status_check_task(
target_url=target_url,
started_at=preprod_artifact.date_added,
completed_at=completed_at,
include_approve_action=bool(triggered_rules),
)
except Exception as e:
extra: dict[str, Any] = {
Expand Down Expand Up @@ -580,6 +603,7 @@ def _compute_overall_status(
size_metrics_map: dict[int, list[PreprodArtifactSizeMetrics]],
rules: list[StatusCheckRule] | None = None,
base_size_metrics_map: dict[int, PreprodArtifactSizeMetrics] | None = None,
approvals_map: dict[int, PreprodComparisonApproval] | None = None,
) -> tuple[StatusCheckStatus, list[TriggeredRule]]:
triggered_rules: list[TriggeredRule] = []

Expand Down Expand Up @@ -609,6 +633,9 @@ def _compute_overall_status(

if rules:
for artifact in artifacts:
if approvals_map and artifact.id in approvals_map:
continue

context = _get_artifact_filter_context(artifact)
size_metrics_list = size_metrics_map.get(artifact.id, [])

Expand Down Expand Up @@ -777,6 +804,7 @@ def create_status_check(
started_at: datetime,
completed_at: datetime | None = None,
target_url: str | None = None,
include_approve_action: bool = False,
) -> str | None:
"""Create a status check using provider-specific format."""
raise NotImplementedError
Expand All @@ -796,6 +824,7 @@ def create_status_check(
started_at: datetime,
completed_at: datetime | None = None,
target_url: str | None = None,
include_approve_action: bool = False,
) -> str | None:
with self._create_scm_interaction_event().capture() as lifecycle:
mapped_status = GITHUB_STATUS_CHECK_STATUS_MAPPING.get(status)
Expand Down Expand Up @@ -872,6 +901,15 @@ def create_status_check(
"Omit completed_at entirely instead of setting it to None."
)

if include_approve_action:
check_data["actions"] = [
{
"label": "Approve",
"description": "Approve size changes for this PR",
"identifier": APPROVE_SIZE_ACTION_IDENTIFIER,
}
]

try:
response = self.client.create_check_run(repo=repo, data=check_data)
check_id = response.get("id")
Expand Down
71 changes: 35 additions & 36 deletions src/sentry/preprod/vcs/status_checks/size/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@

from sentry.integrations.source_code_management.status_check import StatusCheckStatus
from sentry.models.project import Project
from sentry.preprod.models import PreprodArtifact, PreprodArtifactSizeMetrics
from sentry.preprod.models import (
PreprodArtifact,
PreprodArtifactSizeMetrics,
PreprodComparisonApproval,
)
from sentry.preprod.url_utils import get_preprod_artifact_comparison_url, get_preprod_artifact_url
from sentry.preprod.vcs.status_checks.size.types import TriggeredRule

Expand All @@ -18,6 +22,7 @@ def format_status_check_messages(
overall_status: StatusCheckStatus,
project: Project,
triggered_rules: list[TriggeredRule] | None = None,
approvals_map: dict[int, PreprodComparisonApproval] | None = None,
) -> tuple[str, str, str]:
"""
Args:
Expand All @@ -26,6 +31,7 @@ def format_status_check_messages(
overall_status: The overall status of the check
project: The project associated with the artifacts
triggered_rules: List of triggered rules with artifact associations
approvals_map: Dict mapping artifact_id to PreprodComparisonApproval

Returns:
tuple: (title, subtitle, summary)
Expand Down Expand Up @@ -94,30 +100,38 @@ def format_status_check_messages(
failed_artifacts = [a for a in artifacts if a.id in failed_artifact_ids]
passed_artifacts = [a for a in artifacts if a.id not in failed_artifact_ids]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI @mtopo27 is planning to do some more UI cleanup to our status checks but I was doing some testing and mostly made changes to make sure the current UI at least makes sense (no redundant titles, the # above the table matches the number of rows in the table, etc)


failed_count = len(failed_artifact_ids)
failed_metrics_count = sum(
len(size_metrics_map.get(a.id, [])) or 1 for a in failed_artifacts
)
failed_header = ngettext(
"## ❌ %(count)d App Failed Size Checks",
"## ❌ %(count)d Apps Failed Size Checks",
failed_count,
) % {"count": failed_count}
"## ❌ %(count)d Failed Size Check",
"## ❌ %(count)d Failed Size Checks",
failed_metrics_count,
) % {"count": failed_metrics_count}
Comment thread
cursor[bot] marked this conversation as resolved.

summary_parts = []

# Failed apps section
summary_parts.append(failed_header)
summary_parts.append(_format_artifact_summary(failed_artifacts, size_metrics_map))
summary_parts.append(
_format_artifact_summary(failed_artifacts, size_metrics_map, approvals_map)
)
summary_parts.append(_format_failed_checks_details(triggered_rules, settings_url))

# Passed apps section (if any)
if passed_artifacts:
passed_count = len(passed_artifacts)
passed_metrics_count = sum(
len(size_metrics_map.get(a.id, [])) or 1 for a in passed_artifacts
)
passed_header = ngettext(
"## %(count)d App Analyzed",
"## %(count)d Apps Analyzed",
passed_count,
) % {"count": passed_count}
"## %(count)d Analyzed",
"## %(count)d Analyzed",
passed_metrics_count,
) % {"count": passed_metrics_count}
summary_parts.append(passed_header)
summary_parts.append(_format_artifact_summary(passed_artifacts, size_metrics_map))
summary_parts.append(
_format_artifact_summary(passed_artifacts, size_metrics_map, approvals_map)
)

# Footer link
summary_parts.append(_format_configure_link(project, settings_url))
Expand All @@ -132,24 +146,7 @@ def format_status_check_messages(
else:
# Success or in-progress
summary_parts = []

if analyzed_count > 0:
analyzed_header = ngettext(
"## %(count)d App Analyzed",
"## %(count)d Apps Analyzed",
analyzed_count,
) % {"count": analyzed_count}
summary_parts.append(analyzed_header)
else:
# All apps still processing
processing_header = ngettext(
"## %(count)d App Processing",
"## %(count)d Apps Processing",
processing_count,
) % {"count": processing_count}
summary_parts.append(processing_header)

summary_parts.append(_format_artifact_summary(artifacts, size_metrics_map))
summary_parts.append(_format_artifact_summary(artifacts, size_metrics_map, approvals_map))
summary_parts.append(_format_configure_link(project, settings_url))

summary = "\n\n".join(summary_parts)
Expand All @@ -160,6 +157,7 @@ def format_status_check_messages(
def _format_artifact_summary(
artifacts: list[PreprodArtifact],
size_metrics_map: dict[int, list[PreprodArtifactSizeMetrics]],
approvals_map: dict[int, PreprodComparisonApproval] | None = None,
) -> str:
"""Format summary for artifacts with size data."""
artifact_metric_rows = _create_sorted_artifact_metric_rows(artifacts, size_metrics_map)
Expand Down Expand Up @@ -229,10 +227,13 @@ def _format_artifact_summary(
f"{artifact.build_configuration.name or '--'}" if artifact.build_configuration else "--"
)

# TODO(preprod): Add approval text once we have it
na_text = str(_("N/A"))
approval = approvals_map.get(artifact.id) if approvals_map else None
if approval:
approval_text = "✅ Approved"
else:
approval_text = str(_("N/A"))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we include ❌ Rejected at all?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but I think @mtopo27 actually wants to axe the approvals table so I'll just leave this alone for now since he'll do that later today


row = f"| {name_text} | {configuration_text} | {version_string} | {download_text} | {install_text} | {na_text} |"
row = f"| {name_text} | {configuration_text} | {version_string} | {download_text} | {install_text} | {approval_text} |"

group_key = "android" if artifact.is_android() else "ios"
grouped_rows[group_key].append(row)
Expand Down Expand Up @@ -336,8 +337,6 @@ def _format_failed_checks_details(
f"- **{metric_display} - {measurement_display}** ≥ **{threshold_display}**"
)

details_content.append(f"\n⚙️ [Configure status check rules]({settings_url})")

return (
f"<details>\n"
f"<summary>{summary_text}</summary>\n\n"
Expand Down
20 changes: 5 additions & 15 deletions tests/sentry/preprod/vcs/status_checks/size/test_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -802,8 +802,6 @@ def test_mixed_platforms_render_separate_tables(self):
settings_url = f"http://testserver/settings/projects/{self.project.slug}/builds/"

expected = f"""\
## 2 Apps Analyzed

### Android Builds

| Name | Configuration | Version | Download Size | Uncompressed Size | Approval |
Expand Down Expand Up @@ -1132,7 +1130,7 @@ def test_single_triggered_rule_shows_details_section(self):
)

expected = f"""\
## ❌ 1 App Failed Size Checks
## ❌ 1 Failed Size Check

### Android Builds

Expand All @@ -1145,8 +1143,6 @@ def test_single_triggered_rule_shows_details_section(self):

`com.example.app` (Android)
- **Download Size - Total Size** ≥ **52.4 MB**

⚙️ [Configure status check rules]({settings_url})
</details>

[Configure test_project status check rules]({settings_url})\
Expand Down Expand Up @@ -1227,7 +1223,7 @@ def test_multiple_triggered_rules_url_formatting(self):
settings_url = f"http://testserver/settings/projects/{self.project.slug}/builds/?expanded=rule-download-absolute&expanded=rule-install-diff&expanded=rule-download-percent"

expected = f"""\
## ❌ 1 App Failed Size Checks
## ❌ 1 Failed Size Check

### Android Builds

Expand All @@ -1242,8 +1238,6 @@ def test_multiple_triggered_rules_url_formatting(self):
- **Download Size - Total Size** ≥ **52.4 MB**
- **Install Size - Absolute Diff** ≥ **10.5 MB**
- **Download Size - Relative Diff** ≥ **5.0%**

⚙️ [Configure status check rules]({settings_url})
</details>

[Configure test_project status check rules]({settings_url})\
Expand Down Expand Up @@ -1334,7 +1328,7 @@ def test_multiple_apps_with_triggered_rules(self):
settings_url = f"http://testserver/settings/projects/{self.project.slug}/builds/?expanded=rule-1&expanded=rule-2"

expected = f"""\
## ❌ 2 Apps Failed Size Checks
## ❌ 2 Failed Size Checks

### iOS Builds

Expand All @@ -1355,8 +1349,6 @@ def test_multiple_apps_with_triggered_rules(self):
- **Download Size - Total Size** ≥ **52.4 MB**
`com.example.app2` (Android)
- **Install Size - Total Size** ≥ **104.9 MB**

⚙️ [Configure status check rules]({settings_url})
</details>

[Configure test_project status check rules]({settings_url})\
Expand Down Expand Up @@ -1439,7 +1431,7 @@ def test_mixed_pass_fail_with_triggered_rules(self):
)

expected = f"""\
## ❌ 1 App Failed Size Checks
## ❌ 1 Failed Size Check

### Android Builds

Expand All @@ -1452,11 +1444,9 @@ def test_mixed_pass_fail_with_triggered_rules(self):

`com.example.failed` (Android)
- **Download Size - Total Size** ≥ **52.4 MB**

⚙️ [Configure status check rules]({settings_url})
</details>

## 1 App Analyzed
## 1 Analyzed

### Android Builds

Expand Down
Loading