Skip to content

Commit 2fe740f

Browse files
authored
fix(aci): handle fake incident ids in IGOP lookup (#103937)
When looking up a GroupOpenPeriod via incident id, it's possible that there is no corresponding row in the IncidentGroupOpenPeriod table. We then assume that the incident id that was passed is a fake id, so we can use `get_object_id_from_fake_id()` to get the open period id and group.
1 parent 8b1cd88 commit 2fe740f

File tree

3 files changed

+74
-5
lines changed

3 files changed

+74
-5
lines changed

src/sentry/workflow_engine/endpoints/organization_incident_groupopenperiod_index.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
RESPONSE_UNAUTHORIZED,
1616
)
1717
from sentry.apidocs.parameters import GlobalParams
18+
from sentry.incidents.endpoints.serializers.utils import get_object_id_from_fake_id
19+
from sentry.models.groupopenperiod import GroupOpenPeriod
1820
from sentry.workflow_engine.endpoints.serializers.incident_groupopenperiod_serializer import (
1921
IncidentGroupOpenPeriodSerializer,
2022
)
@@ -49,6 +51,9 @@ def get(self, request, organization):
4951
"""
5052
Returns an incident and group open period relationship.
5153
Can optionally filter by incident_id, incident_identifier, group_id, or open_period_id.
54+
If incident_identifier is provided but no match is found, falls back to calculating
55+
open_period_id by subtracting 10^9 from the incident_identifier and looking up the
56+
GroupOpenPeriod directly.
5257
"""
5358
validator = IncidentGroupOpenPeriodValidator(data=request.query_params)
5459
validator.is_valid(raise_exception=True)
@@ -74,7 +79,37 @@ def get(self, request, organization):
7479
queryset = queryset.filter(group_open_period_id=open_period_id)
7580

7681
incident_groupopenperiod = queryset.first()
77-
if not incident_groupopenperiod:
78-
raise ResourceDoesNotExist
7982

80-
return Response(serialize(incident_groupopenperiod, request.user))
83+
if incident_groupopenperiod:
84+
return Response(serialize(incident_groupopenperiod, request.user))
85+
86+
# Fallback: if incident_identifier or incident_id was provided but no IGOP found,
87+
# try looking up GroupOpenPeriod directly using calculated open_period_id
88+
fake_id = incident_identifier or incident_id
89+
if fake_id:
90+
calculated_open_period_id = get_object_id_from_fake_id(int(fake_id))
91+
gop_queryset = GroupOpenPeriod.objects.filter(
92+
id=calculated_open_period_id,
93+
project__organization=organization,
94+
)
95+
96+
if group_id:
97+
gop_queryset = gop_queryset.filter(group_id=group_id)
98+
99+
if open_period_id:
100+
gop_queryset = gop_queryset.filter(id=open_period_id)
101+
102+
group_open_period = gop_queryset.first()
103+
104+
if group_open_period:
105+
# Serialize the GroupOpenPeriod as if it were an IncidentGroupOpenPeriod
106+
return Response(
107+
{
108+
"incidentId": str(fake_id),
109+
"incidentIdentifier": str(fake_id),
110+
"groupId": str(group_open_period.group_id),
111+
"openPeriodId": str(group_open_period.id),
112+
}
113+
)
114+
115+
raise ResourceDoesNotExist

src/sentry/workflow_engine/endpoints/serializers/incident_groupopenperiod_serializer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class IncidentGroupOpenPeriodSerializerResponse(TypedDict):
99
incidentId: str | None
10-
incidentIdentifier: int | None
10+
incidentIdentifier: str | None
1111
groupId: str
1212
openPeriodId: str
1313

@@ -19,7 +19,7 @@ def serialize(
1919
) -> IncidentGroupOpenPeriodSerializerResponse:
2020
return {
2121
"incidentId": str(obj.incident_id) if obj.incident_id else None,
22-
"incidentIdentifier": obj.incident_identifier,
22+
"incidentIdentifier": str(obj.incident_identifier) if obj.incident_identifier else None,
2323
"groupId": str(obj.group_open_period.group_id),
2424
"openPeriodId": str(obj.group_open_period.id),
2525
}

tests/sentry/workflow_engine/endpoints/test_organization_incident_groupopenperiod.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from sentry.api.serializers import serialize
2+
from sentry.incidents.endpoints.serializers.utils import get_fake_id_from_object_id
23
from sentry.incidents.grouptype import MetricIssue
34
from sentry.models.groupopenperiod import GroupOpenPeriod
45
from sentry.testutils.cases import APITestCase
@@ -103,3 +104,36 @@ def test_get_with_nonexistent_open_period_id(self) -> None:
103104

104105
def test_no_filter_provided(self) -> None:
105106
self.get_error_response(self.organization.slug, status_code=400)
107+
108+
def test_fallback_with_fake_incident_identifier(self) -> None:
109+
"""
110+
Test that when an IGOP doesn't exist, the endpoint falls back to looking up
111+
the GroupOpenPeriod by subtracting 10^9 from the incident_identifier.
112+
This is the reverse of the GOP -> Incident serialization logic.
113+
"""
114+
# Create a group with open period but NO IGOP
115+
group_no_igop = self.create_group(type=MetricIssue.type_id)
116+
open_period_no_igop = GroupOpenPeriod.objects.get(group=group_no_igop)
117+
118+
# Calculate the fake incident_identifier (same as serializer does)
119+
fake_incident_identifier = get_fake_id_from_object_id(open_period_no_igop.id)
120+
121+
# Query using the fake incident_identifier
122+
response = self.get_success_response(
123+
self.organization.slug, incident_identifier=str(fake_incident_identifier)
124+
)
125+
126+
# Should return a fake IGOP response
127+
assert response.data == {
128+
"incidentId": str(fake_incident_identifier),
129+
"incidentIdentifier": str(fake_incident_identifier),
130+
"groupId": str(group_no_igop.id),
131+
"openPeriodId": str(open_period_no_igop.id),
132+
}
133+
134+
def test_fallback_with_nonexistent_open_period(self) -> None:
135+
# Use a fake incident_identifier that won't map to any real open period
136+
nonexistent_fake_id = get_fake_id_from_object_id(999999)
137+
self.get_error_response(
138+
self.organization.slug, incident_identifier=str(nonexistent_fake_id), status_code=404
139+
)

0 commit comments

Comments
 (0)