Skip to content

Commit e34e412

Browse files
authored
feat(related_issues): Support passing an event_id to the endpoint (#70878)
This enables showing trace connected issues from the issue details page for the event we're currently viewing.
1 parent 2ac4ef7 commit e34e412

File tree

5 files changed

+92
-15
lines changed

5 files changed

+92
-15
lines changed

Diff for: src/sentry/api/endpoints/issues/related_issues.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,12 @@ def get(self, request: Request, group: Group) -> Response:
3535
"""
3636
# The type of related issues to retrieve. Can be either `same_root_cause` or `trace_connected`.
3737
related_type = request.query_params["type"]
38-
data, meta = RELATED_ISSUES_ALGORITHMS[related_type](group)
39-
return Response({"type": related_type, "data": data, "meta": meta})
38+
extra_args = {
39+
"event_id": request.query_params.get("event_id"),
40+
"project_id": request.query_params.get("project_id"),
41+
}
42+
try:
43+
data, meta = RELATED_ISSUES_ALGORITHMS[related_type](group, extra_args)
44+
return Response({"type": related_type, "data": data, "meta": meta})
45+
except AssertionError:
46+
return Response({}, status=400)

Diff for: src/sentry/issues/related/same_root_cause.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
from sentry.utils.query import RangeQuerySetWrapper
88

99

10-
def same_root_cause_analysis(group: Group) -> tuple[list[int], dict[str, str]]:
10+
def same_root_cause_analysis(
11+
group: Group, _extra_args: dict[str, str | None] | None = None
12+
) -> tuple[list[int], dict[str, str]]:
1113
"""Analyze and create a group set if the group was caused by the same root cause."""
1214
# Querying the data field (which is a GzippedDictField) cannot be done via
1315
# Django's ORM, thus, we do so via compare_groups

Diff for: src/sentry/issues/related/trace_connected.py

+42-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# Module to evaluate if other errors happened in the same trace.
22
#
33
# Refer to README in module for more details.
4+
from sentry import eventstore
45
from sentry.api.utils import default_start_end_dates
6+
from sentry.eventstore.models import GroupEvent
57
from sentry.models.group import Group
68
from sentry.models.project import Project
79
from sentry.search.events.builder import QueryBuilder
@@ -11,12 +13,47 @@
1113
from sentry.utils.snuba import bulk_snuba_queries
1214

1315

14-
def trace_connected_analysis(group: Group) -> tuple[list[int], dict[str, str]]:
16+
# If we drop trace connected issues from similar issues we can stop using the group
17+
def trace_connected_analysis(
18+
group: Group, extra_args: dict[str, str | None] | None = None
19+
) -> tuple[list[int], dict[str, str]]:
1520
"""Determine if the group has a trace connected to it and return other issues that were part of it."""
16-
event = group.get_recommended_event_for_environments()
17-
if not event or event.trace_id is None:
18-
return [], {}
21+
if extra_args is None:
22+
extra_args = {}
1923

24+
issues: list[int] = []
25+
meta: dict[str, str] = {}
26+
event_id = extra_args.get("event_id")
27+
project_id = extra_args.get("project_id")
28+
if event_id:
29+
# If we are passing an specific event_id, we need to get the project_id
30+
assert project_id is not None
31+
event = eventstore.backend.get_event_by_id(project_id, event_id, group_id=group.id)
32+
# If we are requesting an specific event, we want to be notified with an error
33+
assert event is not None
34+
# This ensures that the event is actually part of the group and we are notified
35+
assert event.group_id == group.id
36+
else:
37+
# If we drop trace connected issues from similar issues we can remove this
38+
event = group.get_recommended_event_for_environments()
39+
40+
if event:
41+
issues, meta = trace_connected_issues(event)
42+
else:
43+
meta["error"] = "No event found for group."
44+
45+
return issues, meta
46+
47+
48+
def trace_connected_issues(event: GroupEvent) -> tuple[list[int], dict[str, str]]:
49+
meta = {"event_id": event.event_id}
50+
if event.trace_id:
51+
meta["trace_id"] = event.trace_id
52+
else:
53+
meta["error"] = "No trace_id found in event."
54+
return [], meta
55+
56+
group = event.group
2057
org_id = group.project.organization_id
2158
# XXX: Test without a list and validate the data type
2259
project_ids = list(Project.objects.filter(organization_id=org_id).values_list("id", flat=True))
@@ -41,4 +78,4 @@ def trace_connected_analysis(group: Group) -> tuple[list[int], dict[str, str]]:
4178
if datum["issue.id"] != group.id # Exclude itself
4279
}
4380
)
44-
return transformed_results, {"event_id": event.event_id, "trace_id": event.trace_id}
81+
return transformed_results, meta

Diff for: src/sentry/testutils/cases.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -3406,7 +3406,11 @@ def convert_event_data_to_span(self, event: Event) -> dict[str, Any]:
34063406

34073407
return span_data
34083408

3409-
def load_errors(self, project: Project, span_id: str) -> list[Event]:
3409+
def load_errors(
3410+
self,
3411+
project: Project,
3412+
span_id: str | None = None,
3413+
) -> list[Event]:
34103414
"""Generates trace with errors across two projects."""
34113415
start, _ = self.get_start_end_from_day_ago(1000)
34123416
error_data = load_data(
@@ -3416,7 +3420,7 @@ def load_errors(self, project: Project, span_id: str) -> list[Event]:
34163420
error_data["contexts"]["trace"] = {
34173421
"type": "trace",
34183422
"trace_id": self.trace_id,
3419-
"span_id": span_id,
3423+
"span_id": span_id or uuid4().hex[:16],
34203424
}
34213425
error_data["level"] = "fatal"
34223426
error = self.store_event(error_data, project_id=project.id)

Diff for: tests/sentry/api/endpoints/issues/test_related_issues.py

+32-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from uuid import uuid4
2-
31
from django.urls import reverse
42

53
from sentry.testutils.cases import APITestCase, SnubaTestCase, TraceTestCase
@@ -47,20 +45,49 @@ def test_same_root_related_issues(self) -> None:
4745
assert response.json() == {"type": "same_root_cause", "data": [5], "meta": {}}
4846

4947
def test_trace_connected_errors(self) -> None:
50-
error_event, _, another_proj_event = self.load_errors(self.project, uuid4().hex[:16])
48+
error_event, _, another_proj_event = self.load_errors(self.project)
5149
group = error_event.group
52-
self.group_id = error_event.group_id # type: ignore[assignment]
5350
recommended_event = group.get_recommended_event_for_environments() # type: ignore[union-attr]
5451
assert recommended_event is not None # It helps with typing
52+
# This assertion ensures that the behaviour is different from the next test
53+
assert recommended_event.event_id != another_proj_event.event_id
5554

55+
# This asserts that there are two issues which belong to the same trace
5656
assert error_event.group_id != another_proj_event.group_id
5757
assert error_event.project.id != another_proj_event.project.id
5858
assert error_event.trace_id == another_proj_event.trace_id
5959

60+
# This sets the group_id to the one we want to query about
61+
self.group_id = error_event.group_id # type: ignore[assignment]
6062
response = self.get_success_response(qs_params={"type": "trace_connected"})
6163
assert response.json() == {
6264
"type": "trace_connected",
6365
# This is the other issue in the trace that it is not itself
6466
"data": [another_proj_event.group_id],
65-
"meta": {"event_id": recommended_event.event_id, "trace_id": error_event.trace_id},
67+
"meta": {
68+
"event_id": recommended_event.event_id,
69+
"trace_id": error_event.trace_id,
70+
},
71+
}
72+
73+
def test_trace_connected_errors_specific_event(self) -> None:
74+
error_event, _, another_proj_event = self.load_errors(self.project)
75+
76+
# This sets the group_id to the one we want to query about
77+
self.group_id = another_proj_event.group_id # type: ignore[assignment]
78+
response = self.get_success_response(
79+
qs_params={
80+
"type": "trace_connected",
81+
"event_id": another_proj_event.event_id,
82+
"project_id": another_proj_event.project_id,
83+
}
84+
)
85+
assert response.json() == {
86+
"type": "trace_connected",
87+
# This is the other issue in the trace that it is not itself
88+
"data": [error_event.group_id],
89+
"meta": {
90+
"event_id": another_proj_event.event_id,
91+
"trace_id": another_proj_event.trace_id,
92+
},
6693
}

0 commit comments

Comments
 (0)