Skip to content

Commit 59db218

Browse files
committed
link PRs to issues in weekly report
1 parent 3332f68 commit 59db218

4 files changed

Lines changed: 135 additions & 25 deletions

File tree

src/sentry/tasks/summaries/weekly_reports.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
from sentry import analytics, features
2323
from sentry.analytics.events.weekly_report import WeeklyReportSent
2424
from sentry.models.group import Group, GroupStatus
25-
from sentry.models.grouphistory import GroupHistoryStatus
25+
from sentry.models.grouphistory import GroupHistory, GroupHistoryStatus
26+
from sentry.models.grouplink import GroupLink
2627
from sentry.models.organization import Organization, OrganizationStatus
2728
from sentry.models.organizationmember import OrganizationMember
29+
from sentry.models.pullrequest import PullRequest
2830
from sentry.notifications.services import notifications_service
2931
from sentry.silo.base import SiloMode
3032
from sentry.tasks.base import instrumented_task
@@ -528,6 +530,33 @@ def get_group_status_badge(group: Group) -> tuple[str, str, str]:
528530
return ("Ongoing", "#DAD9DE", "#DAD9DE")
529531

530532

533+
def get_group_pull_request_url(group: Group) -> str | None:
534+
group_link = (
535+
GroupLink.objects.filter(
536+
group_id=group.id,
537+
project_id=group.project_id,
538+
linked_type=GroupLink.LinkedType.pull_request,
539+
)
540+
.order_by("-datetime")
541+
.first()
542+
)
543+
if group_link is None:
544+
return None
545+
546+
pull_request = PullRequest.objects.filter(id=group_link.linked_id).first()
547+
if pull_request is None:
548+
return None
549+
550+
return pull_request.get_external_url()
551+
552+
553+
def get_group_history_status(group: Group, status: int) -> tuple[str, str | None]:
554+
if status == GroupHistoryStatus.SET_RESOLVED_IN_PULL_REQUEST:
555+
return "Resolved in PR", get_group_pull_request_url(group)
556+
557+
return str(dict(GroupHistory._meta.get_field("status").choices).get(status, status)), None
558+
559+
531560
def get_group_display(group: Group) -> dict[str, str]:
532561
metadata = group.get_event_metadata()
533562
event_type = group.get_event_type()
@@ -722,6 +751,7 @@ def all_key_errors():
722751
"title": display["title"],
723752
"message": display["message"],
724753
"status": "Unresolved",
754+
"status_url": None,
725755
"status_color": (group_status_to_color[GroupHistoryStatus.NEW]),
726756
"group_substatus": substatus,
727757
"group_substatus_color": substatus_color,
@@ -760,22 +790,28 @@ def all_key_performance_issues():
760790
substatus_color,
761791
substatus_border_color,
762792
) = get_group_status_badge(group)
793+
status, status_url = (
794+
get_group_history_status(group, group_history.status)
795+
if group_history
796+
else ("Unresolved", None)
797+
)
763798
yield {
764799
"count": count,
765800
"group": group,
766801
"title": display["title"],
767802
"message": display["message"],
768-
"status": (
769-
group_history.get_status_display() if group_history else "Unresolved"
770-
),
803+
"status": status,
804+
"status_url": status_url,
771805
"status_color": (
772806
group_status_to_color[group_history.status]
773807
if group_history
774808
else group_status_to_color[GroupHistoryStatus.NEW]
775809
),
776-
"group_substatus": substatus,
777-
"group_substatus_color": substatus_color,
778-
"group_substatus_border_color": substatus_border_color,
810+
"group_substatus": None if group_history else substatus,
811+
"group_substatus_color": None if group_history else substatus_color,
812+
"group_substatus_border_color": (
813+
None if group_history else substatus_border_color
814+
),
779815
}
780816

781817
return heapq.nlargest(3, all_key_performance_issues(), lambda d: d["count"])

src/sentry/templates/sentry/emails/reports/body.html

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -444,9 +444,13 @@ <h4>Issues with the most errors</h4>
444444
<div style="font-size: 12px; color: #80708F;">{{a.group.project.name}}</div>
445445
</div>
446446
{% if a.group_substatus and a.group_substatus_color %}
447-
<span style="background-color: {{a.group_substatus_color}}; border: 1px solid {{a.group_substatus_border_color}}; border-radius: 8px; font-size: 12px; align-self: center; padding: 2px 10px; margin-left: auto; height: 100%;">{{a.group_substatus}}</span>
447+
<span style="background-color: {{a.group_substatus_color}}; border: 1px solid {{a.group_substatus_border_color}}; border-radius: 8px; display: inline-block; font-size: 12px; align-self: center; padding: 2px 10px; margin-left: auto; height: 100%; text-align: center; width: 96px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">{{a.group_substatus}}</span>
448448
{% else %}
449-
<span style="background-color: {{a.status_color}}; border-radius: 8px; font-size: 12px; align-self: center; padding: 2px 10px; margin-left: auto; height: 100%;">{{a.status}}</span>
449+
{% if a.status_url %}
450+
<a href="{{a.status_url}}" style="background-color: {{a.status_color}}; border-radius: 8px; color: #2f2936; display: inline-block; font-size: 12px; align-self: center; padding: 2px 10px; margin-left: auto; height: 100%; line-height: 1.2; text-align: center; text-decoration: none; width: 96px; white-space: normal; overflow-wrap: break-word;">{{a.status}}</a>
451+
{% else %}
452+
<span style="background-color: {{a.status_color}}; border-radius: 8px; display: inline-block; font-size: 12px; align-self: center; padding: 2px 10px; margin-left: auto; height: 100%; line-height: 1.2; text-align: center; width: 96px; white-space: normal; overflow-wrap: break-word;">{{a.status}}</span>
453+
{% endif %}
450454
{% endif %}
451455
</div>
452456
{% endfor %}
@@ -467,9 +471,13 @@ <h4>Most frequent performance issues</h4>
467471
<div style="font-size: 12px; color: #80708F;">{{a.group.project.name}}</div>
468472
</div>
469473
{% if a.group_substatus and a.group_substatus_color %}
470-
<span style="background-color: {{a.group_substatus_color}}; border: 1px solid {{a.group_substatus_border_color}}; border-radius: 8px; font-size: 12px; align-self: center; padding: 2px 10px; margin-left: auto; height: 100%;">{{a.group_substatus}}</span>
474+
<span style="background-color: {{a.group_substatus_color}}; border: 1px solid {{a.group_substatus_border_color}}; border-radius: 8px; display: inline-block; font-size: 12px; align-self: center; padding: 2px 10px; margin-left: auto; height: 100%; text-align: center; width: 96px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">{{a.group_substatus}}</span>
471475
{% else %}
472-
<span style="background-color: {{a.status_color}}; border-radius: 8px; font-size: 12px; align-self: center; padding: 2px 10px; margin-left: auto; height: 100%;">{{a.status}}</span>
476+
{% if a.status_url %}
477+
<a href="{{a.status_url}}" style="background-color: {{a.status_color}}; border-radius: 8px; color: #2f2936; display: inline-block; font-size: 12px; align-self: center; padding: 2px 10px; margin-left: auto; height: 100%; line-height: 1.2; text-align: center; text-decoration: none; width: 96px; white-space: normal; overflow-wrap: break-word;">{{a.status}}</a>
478+
{% else %}
479+
<span style="background-color: {{a.status_color}}; border-radius: 8px; display: inline-block; font-size: 12px; align-self: center; padding: 2px 10px; margin-left: auto; height: 100%; line-height: 1.2; text-align: center; width: 96px; white-space: normal; overflow-wrap: break-word;">{{a.status}}</span>
480+
{% endif %}
473481
{% endif %}
474482
</div>
475483
{% endfor %}

src/sentry/web/frontend/debug/debug_weekly_report.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
PerformanceSlowDBQueryGroupType,
1414
)
1515
from sentry.models.group import Group, GroupStatus
16+
from sentry.models.grouphistory import GroupHistory, GroupHistoryStatus
1617
from sentry.models.organization import Organization
1718
from sentry.models.project import Project
1819
from sentry.tasks.summaries.utils import ONE_DAY, OrganizationReportContext, ProjectContext
@@ -171,22 +172,36 @@ def get_context(self, request):
171172
PerformanceNPlusOneGroupType,
172173
PerformanceP95EndpointRegressionGroupType,
173174
]
174-
project_context.key_performance_issues = [
175-
(
176-
make_debug_group(
177-
group_id=20000 + (project.id * 100) + group_index,
175+
performance_issue_statuses = [
176+
None,
177+
GroupHistoryStatus.SET_RESOLVED_IN_PULL_REQUEST,
178+
GroupHistoryStatus.SET_RESOLVED_IN_COMMIT,
179+
]
180+
project_context.key_performance_issues = []
181+
for group_index, performance_issue_type in enumerate(performance_issue_types):
182+
group = make_debug_group(
183+
group_id=20000 + (project.id * 100) + group_index,
184+
project=project,
185+
title=make_debug_issue_title(random, performance_issue_type.description),
186+
message=make_debug_issue_message(random),
187+
group_type=performance_issue_type,
188+
event_type="transaction",
189+
substatus=substatuses[group_index],
190+
)
191+
status = performance_issue_statuses[group_index]
192+
group_history = (
193+
GroupHistory(
194+
group=group,
178195
project=project,
179-
title=make_debug_issue_title(random, performance_issue_type.description),
180-
message=make_debug_issue_message(random),
181-
group_type=performance_issue_type,
182-
event_type="transaction",
183-
substatus=substatuses[group_index],
184-
),
185-
None,
186-
random.randint(100, 1000),
196+
organization=organization,
197+
status=status,
198+
)
199+
if status is not None
200+
else None
201+
)
202+
project_context.key_performance_issues.append(
203+
(group, group_history, random.randint(100, 1000))
187204
)
188-
for group_index, performance_issue_type in enumerate(performance_issue_types)
189-
]
190205

191206
ctx.projects_context_map[project.id] = project_context
192207

tests/sentry/tasks/test_weekly_reports.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from sentry.issues.grouptype import PerformanceNPlusOneGroupType
1616
from sentry.models.group import GroupStatus
1717
from sentry.models.grouphistory import GroupHistoryStatus
18+
from sentry.models.grouplink import GroupLink
1819
from sentry.models.organization import Organization
1920
from sentry.models.organizationmember import OrganizationMember
2021
from sentry.models.project import Project
@@ -1173,6 +1174,56 @@ def test_key_errors_and_performance_issues_share_substatus_badges(self) -> None:
11731174
field: performance_issue[field] for field in substatus_fields
11741175
}
11751176

1177+
@mock.patch("sentry.models.pullrequest.PullRequest.get_external_url")
1178+
def test_key_performance_issue_resolved_in_pr_status_links_pull_request(
1179+
self, get_external_url: mock.MagicMock
1180+
) -> None:
1181+
get_external_url.return_value = "https://github.com/example/example/pull/1"
1182+
user = self.create_user()
1183+
self.create_member(teams=[self.team], user=user, organization=self.organization)
1184+
group = self.create_group(
1185+
project=self.project,
1186+
message="performance message",
1187+
status=GroupStatus.UNRESOLVED,
1188+
substatus=GroupSubStatus.ONGOING,
1189+
type=PerformanceNPlusOneGroupType.type_id,
1190+
data={
1191+
"type": "transaction",
1192+
"metadata": {"title": "N+1 Query", "value": "performance message"},
1193+
},
1194+
)
1195+
repo = self.create_repo(project=self.project)
1196+
pull_request = self.create_pull_request(
1197+
repository_id=repo.id,
1198+
organization_id=self.organization.id,
1199+
key="1",
1200+
title="Fix N+1 query",
1201+
)
1202+
GroupLink.objects.create(
1203+
group_id=group.id,
1204+
project_id=group.project_id,
1205+
linked_type=GroupLink.LinkedType.pull_request,
1206+
linked_id=pull_request.id,
1207+
relationship=GroupLink.Relationship.resolves,
1208+
)
1209+
group_history = self.create_group_history(
1210+
group=group,
1211+
status=GroupHistoryStatus.SET_RESOLVED_IN_PULL_REQUEST,
1212+
)
1213+
ctx = OrganizationReportContext(self.now.timestamp(), ONE_DAY * 7, self.organization)
1214+
project_context = ProjectContext(self.project)
1215+
project_context.key_performance_issues = [(group, group_history, 10)]
1216+
ctx.projects_context_map = {self.project.id: project_context}
1217+
ctx.project_ownership[user.id] = {self.project.id}
1218+
1219+
rendered_context = render_template_context(ctx, user.id)
1220+
1221+
assert rendered_context is not None
1222+
performance_issue = rendered_context["key_performance_issues"][0]
1223+
assert performance_issue["status"] == "Resolved in PR"
1224+
assert performance_issue["status_url"] == "https://github.com/example/example/pull/1"
1225+
assert performance_issue["group_substatus"] is None
1226+
11761227
@mock.patch("sentry.analytics.record")
11771228
@mock.patch("sentry.tasks.summaries.weekly_reports.MessageBuilder")
11781229
def test_email_override_simple(

0 commit comments

Comments
 (0)