Skip to content

Commit 4a943e6

Browse files
ceorourkesnigdhas
authored andcommitted
feat(aci): Write to IncidentGroupOpenPeriod (#97906)
#97697 but it's behind a feature flag this time so we can toggle it on and off, plus converts incident projects to a list rather than passing a queryset. See [this commit](cd0b7f9) for the only change in this from the original implementation. --------- Co-authored-by: Snigdha Sharma <[email protected]>
1 parent cbd4747 commit 4a943e6

File tree

6 files changed

+573
-2
lines changed

6 files changed

+573
-2
lines changed

src/sentry/features/temporary.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ def register_temporary_features(manager: FeatureManager):
167167
manager.add("organizations:issue-taxonomy", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
168168
manager.add("organizations:metric-issue-poc", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
169169
manager.add("projects:metric-issue-creation", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
170+
# Enable writing to the IncidentGroupOpenPeriod model
171+
manager.add("organizations:incident-group-open-period-write", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
170172
manager.add("organizations:issue-open-periods", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
171173
manager.add("organizations:mep-rollout-flag", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
172174
manager.add("organizations:mep-use-default-tags", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)

src/sentry/incidents/logic.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
from sentry.utils.not_set import NOT_SET, NotSet
102102
from sentry.utils.snuba import is_measurement
103103
from sentry.workflow_engine.endpoints.validators.utils import toggle_detector
104+
from sentry.workflow_engine.models import IncidentGroupOpenPeriod
104105
from sentry.workflow_engine.models.detector import Detector
105106

106107
# We can return an incident as "windowed" which returns a range of points around the start of the incident
@@ -181,6 +182,14 @@ def create_incident(
181182
incident_type=incident_type.value,
182183
)
183184

185+
# If this is a metric alert incident, check for pending group relationships
186+
if (
187+
alert_rule
188+
and incident_type == IncidentType.ALERT_TRIGGERED
189+
and features.has("organizations:incident-group-open-period-write", organization)
190+
):
191+
IncidentGroupOpenPeriod.create_pending_relationships_for_incident(incident, alert_rule)
192+
184193
return incident
185194

186195

src/sentry/issues/ingest.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from django.conf import settings
1111
from django.db import router, transaction
1212

13-
from sentry import eventstream
13+
from sentry import eventstream, features
1414
from sentry.constants import LOG_LEVELS_MAP, MAX_CULPRIT_LENGTH
1515
from sentry.event_manager import (
1616
GroupInfo,
@@ -21,6 +21,7 @@
2121
get_event_type,
2222
save_grouphash_and_group,
2323
)
24+
from sentry.incidents.grouptype import MetricIssue
2425
from sentry.issues.grouptype import FeedbackGroup, should_create_group
2526
from sentry.issues.issue_occurrence import IssueOccurrence, IssueOccurrenceData
2627
from sentry.issues.priority import PriorityChangeReason, update_priority
@@ -34,7 +35,7 @@
3435
from sentry.utils import json, metrics, redis
3536
from sentry.utils.strings import truncatechars
3637
from sentry.utils.tag_normalization import normalized_sdk_tag_from_event
37-
from sentry.workflow_engine.models.detector_group import DetectorGroup
38+
from sentry.workflow_engine.models import DetectorGroup, IncidentGroupOpenPeriod
3839

3940
issue_rate_limiter = RedisSlidingWindowRateLimiter(
4041
**settings.SENTRY_ISSUE_PLATFORM_RATE_LIMITER_OPTIONS
@@ -70,6 +71,25 @@ def save_issue_occurrence(
7071
group_info.group.project, environment, release, [group_info]
7172
)
7273
_get_or_create_group_release(environment, release, event, [group_info])
74+
75+
# Create IncidentGroupOpenPeriod relationship for metric issues
76+
if occurrence.type == MetricIssue and features.has(
77+
"organizations:incident-group-open-period-write", event.organization
78+
):
79+
open_period = get_latest_open_period(group_info.group)
80+
if open_period:
81+
IncidentGroupOpenPeriod.create_from_occurrence(
82+
occurrence, group_info.group, open_period
83+
)
84+
else:
85+
logger.error(
86+
"save_issue_occurrence.no_open_period",
87+
extra={
88+
"group_id": group_info.group.id,
89+
"occurrence_id": occurrence.id,
90+
},
91+
)
92+
7393
send_issue_occurrence_to_eventstream(event, occurrence, group_info)
7494
return occurrence, group_info
7595

src/sentry/workflow_engine/models/incident_groupopenperiod.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import logging
2+
13
from django.db import models
24
from django.db.models import Q
35

@@ -8,6 +10,12 @@
810
FlexibleForeignKey,
911
region_silo_model,
1012
)
13+
from sentry.incidents.models.alert_rule import AlertRule
14+
from sentry.incidents.models.incident import Incident
15+
from sentry.models.groupopenperiod import GroupOpenPeriod
16+
from sentry.workflow_engine.models.alertrule_detector import AlertRuleDetector
17+
18+
logger = logging.getLogger(__name__)
1119

1220

1321
@region_silo_model
@@ -32,3 +40,157 @@ class Meta:
3240
name="inc_id_inc_identifier_together",
3341
)
3442
]
43+
44+
@classmethod
45+
def create_from_occurrence(self, occurrence, group, open_period):
46+
"""
47+
Creates an IncidentGroupOpenPeriod relationship from an issue occurrence.
48+
This method handles the case where the incident might not exist yet.
49+
50+
Args:
51+
occurrence: The IssueOccurrence that triggered the group creation
52+
group: The Group that was created
53+
open_period: The GroupOpenPeriod for the group
54+
"""
55+
try:
56+
# Extract alert_id from evidence_data using the detector_id
57+
detector_id = occurrence.evidence_data.get("detector_id")
58+
if detector_id:
59+
alert_id = AlertRuleDetector.objects.get(detector_id=detector_id).alert_rule_id
60+
else:
61+
raise Exception("No detector_id found in evidence_data for metric issue")
62+
63+
# Try to find the active incident for this alert rule and project
64+
try:
65+
alert_rule = AlertRule.objects.get(id=alert_id)
66+
incident = Incident.objects.get_active_incident(
67+
alert_rule=alert_rule,
68+
project=group.project,
69+
)
70+
except AlertRule.DoesNotExist:
71+
logger.warning(
72+
"AlertRule not found for alert_id",
73+
extra={
74+
"alert_id": alert_id,
75+
"group_id": group.id,
76+
},
77+
)
78+
incident = None
79+
80+
if incident:
81+
# Incident exists, create the relationship immediately
82+
return self.create_relationship(incident, open_period)
83+
else:
84+
# Incident doesn't exist yet, create a placeholder relationship
85+
# that will be updated when the incident is created
86+
return self.create_placeholder_relationship(detector_id, open_period, group.project)
87+
88+
except Exception as e:
89+
logger.exception(
90+
"Failed to create IncidentGroupOpenPeriod relationship",
91+
extra={
92+
"group_id": group.id,
93+
"occurrence_id": occurrence.id,
94+
"error": str(e),
95+
},
96+
)
97+
return None
98+
99+
@classmethod
100+
def create_relationship(self, incident, open_period):
101+
"""
102+
Creates IncidentGroupOpenPeriod relationship.
103+
104+
Args:
105+
incident: The Incident to link
106+
open_period: The GroupOpenPeriod to link
107+
"""
108+
try:
109+
incident_group_open_period, _ = self.objects.get_or_create(
110+
group_open_period=open_period,
111+
defaults={
112+
"incident_id": incident.id,
113+
"incident_identifier": incident.identifier,
114+
},
115+
)
116+
117+
return incident_group_open_period
118+
119+
except Exception as e:
120+
logger.exception(
121+
"Failed to create/update IncidentGroupOpenPeriod relationship",
122+
extra={
123+
"incident_id": incident.id,
124+
"open_period_id": open_period.id,
125+
"error": str(e),
126+
},
127+
)
128+
return None
129+
130+
@classmethod
131+
def create_placeholder_relationship(self, detector_id, open_period, project):
132+
"""
133+
Creates a placeholder relationship when the incident doesn't exist yet.
134+
This will be updated when the incident is created.
135+
136+
Args:
137+
detector_id: The detector ID
138+
open_period: The GroupOpenPeriod to link
139+
project: The project for the group
140+
"""
141+
try:
142+
# Store the alert_id in the open_period data for later lookup
143+
data = open_period.data or {}
144+
data["pending_incident_detector_id"] = detector_id
145+
open_period.update(data=data)
146+
147+
return None
148+
149+
except Exception as e:
150+
logger.exception(
151+
"Failed to create placeholder IncidentGroupOpenPeriod relationship",
152+
extra={
153+
"detector_id": detector_id,
154+
"open_period_id": open_period.id,
155+
"error": str(e),
156+
},
157+
)
158+
return None
159+
160+
@classmethod
161+
def create_pending_relationships_for_incident(self, incident, alert_rule):
162+
"""
163+
Creates IncidentGroupOpenPeriod relationships for any groups that were created
164+
before the incident. This handles the timing issue where groups might be created
165+
before incidents.
166+
167+
Args:
168+
incident: The Incident that was just created
169+
alert_rule: The AlertRule that triggered the incident
170+
"""
171+
try:
172+
# Find all open periods that have a pending incident detector_id for this alert rule
173+
detector_id = AlertRuleDetector.objects.get(alert_rule_id=alert_rule.id).detector_id
174+
pending_open_periods = GroupOpenPeriod.objects.filter(
175+
data__pending_incident_detector_id=detector_id,
176+
group__project__in=list(incident.projects.all()),
177+
)
178+
179+
for open_period in pending_open_periods:
180+
# Create the relationship
181+
relationship = self.create_relationship(incident, open_period)
182+
if relationship:
183+
# Remove the pending flag from the open_period data
184+
data = open_period.data or {}
185+
data.pop("pending_incident_detector_id", None)
186+
open_period.update(data=data)
187+
188+
except Exception as e:
189+
logger.exception(
190+
"Failed to create pending IncidentGroupOpenPeriod relationships",
191+
extra={
192+
"incident_id": incident.id,
193+
"alert_rule_id": alert_rule.id,
194+
"error": str(e),
195+
},
196+
)

0 commit comments

Comments
 (0)