Skip to content

Commit 6075243

Browse files
getsentry-botkcons
andcommitted
Revert "ref(aci): Don't rely on an AlertRule existing in single processing contexts (#101116)"
This reverts commit dce91fa. Co-authored-by: kcons <[email protected]>
1 parent 0a2e0e8 commit 6075243

File tree

2 files changed

+24
-47
lines changed

2 files changed

+24
-47
lines changed

src/sentry/incidents/subscription_processor.py

Lines changed: 23 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from collections.abc import Sequence
66
from copy import deepcopy
77
from datetime import datetime, timedelta
8-
from typing import Literal, TypedDict, TypeVar, cast
8+
from typing import TypeVar, cast
99

1010
from django.conf import settings
1111
from django.db import router, transaction
@@ -87,15 +87,6 @@
8787
T = TypeVar("T")
8888

8989

90-
class MetricIssueDetectorConfig(TypedDict):
91-
"""
92-
Schema for Metric Issue Detector.config.
93-
"""
94-
95-
comparison_delta: int | None
96-
detection_type: Literal["static", "percent", "dynamic"]
97-
98-
9990
class SubscriptionProcessor:
10091
"""
10192
Class for processing subscription updates for an alert rule. Accepts a subscription
@@ -116,20 +107,19 @@ class SubscriptionProcessor:
116107

117108
def __init__(self, subscription: QuerySubscription) -> None:
118109
self.subscription = subscription
119-
self._alert_rule: AlertRule | None = None
120110
try:
121-
self._alert_rule = AlertRule.objects.get_for_subscription(subscription)
111+
self.alert_rule = AlertRule.objects.get_for_subscription(subscription)
122112
except AlertRule.DoesNotExist:
123113
return
124114

125-
self.triggers = AlertRuleTrigger.objects.get_for_alert_rule(self._alert_rule)
115+
self.triggers = AlertRuleTrigger.objects.get_for_alert_rule(self.alert_rule)
126116
self.triggers.sort(key=lambda trigger: trigger.alert_threshold)
127117

128118
(
129119
self.last_update,
130120
self.trigger_alert_counts,
131121
self.trigger_resolve_counts,
132-
) = get_alert_rule_stats(self._alert_rule, self.subscription, self.triggers)
122+
) = get_alert_rule_stats(self.alert_rule, self.subscription, self.triggers)
133123
self.orig_trigger_alert_counts = deepcopy(self.trigger_alert_counts)
134124
self.orig_trigger_resolve_counts = deepcopy(self.trigger_resolve_counts)
135125

@@ -145,14 +135,6 @@ def __init__(self, subscription: QuerySubscription) -> None:
145135
or self._has_workflow_engine_processing_only
146136
)
147137

148-
@property
149-
def alert_rule(self) -> AlertRule:
150-
"""
151-
Only use this in non-single processing contexts.
152-
"""
153-
assert self._alert_rule is not None
154-
return self._alert_rule
155-
156138
@property
157139
def active_incident(self) -> Incident | None:
158140
"""
@@ -206,15 +188,15 @@ def check_trigger_matches_status(
206188
incident_trigger = self.incident_trigger_map.get(trigger.id)
207189
return incident_trigger is not None and incident_trigger.status == status.value
208190

209-
def reset_trigger_counts(self, alert_rule: AlertRule) -> None:
191+
def reset_trigger_counts(self) -> None:
210192
"""
211193
Helper method that clears both the trigger alert and the trigger resolve counts
212194
"""
213195
for trigger_id in self.trigger_alert_counts:
214196
self.trigger_alert_counts[trigger_id] = 0
215197
for trigger_id in self.trigger_resolve_counts:
216198
self.trigger_resolve_counts[trigger_id] = 0
217-
self.update_alert_rule_stats(alert_rule)
199+
self.update_alert_rule_stats()
218200

219201
def calculate_resolve_threshold(self, trigger: AlertRuleTrigger) -> float:
220202
"""
@@ -271,8 +253,8 @@ def get_crash_rate_alert_metrics_aggregation_value(
271253
aggregation_value = get_crash_rate_alert_metrics_aggregation_value_helper(
272254
subscription_update
273255
)
274-
if aggregation_value is None and self._alert_rule is not None:
275-
self.reset_trigger_counts(self._alert_rule)
256+
if aggregation_value is None:
257+
self.reset_trigger_counts()
276258
return aggregation_value
277259

278260
def get_aggregation_value(
@@ -289,7 +271,7 @@ def get_aggregation_value(
289271
organization_id=self.subscription.project.organization.id,
290272
project_ids=[self.subscription.project_id],
291273
comparison_delta=comparison_delta,
292-
alert_rule_id=self._alert_rule.id if self._alert_rule else None,
274+
alert_rule_id=self.alert_rule.id,
293275
)
294276

295277
return aggregation_value
@@ -318,7 +300,7 @@ def handle_trigger_anomalies(
318300
is_resolved=False,
319301
)
320302
incremented = metrics_incremented or incremented
321-
incident_trigger = self.trigger_alert_threshold(trigger)
303+
incident_trigger = self.trigger_alert_threshold(trigger, aggregation_value)
322304
if incident_trigger is not None:
323305
fired_incident_triggers.append(incident_trigger)
324306
else:
@@ -350,12 +332,9 @@ def get_comparison_delta(self, detector: Detector | None) -> int | None:
350332
comparison_delta = None
351333

352334
if detector:
353-
detector_cfg: MetricIssueDetectorConfig = detector.config
354-
comparison_delta = detector_cfg.get("comparison_delta")
335+
comparison_delta = detector.config.get("comparison_delta")
355336
else:
356-
# If we don't have a Detector, we must have an AlertRule.
357-
assert self._alert_rule is not None
358-
comparison_delta = self._alert_rule.comparison_delta
337+
comparison_delta = self.alert_rule.comparison_delta
359338

360339
return comparison_delta
361340

@@ -442,7 +421,7 @@ def handle_trigger_alerts(
442421
)
443422
incremented = metrics_incremented or incremented
444423
# triggering a threshold will create an incident and set the status to active
445-
incident_trigger = self.trigger_alert_threshold(trigger)
424+
incident_trigger = self.trigger_alert_threshold(trigger, aggregation_value)
446425
if incident_trigger is not None:
447426
fired_incident_triggers.append(incident_trigger)
448427
else:
@@ -476,13 +455,11 @@ def handle_trigger_alerts(
476455

477456
def process_results_workflow_engine(
478457
self,
479-
detector: Detector,
480458
subscription_update: QuerySubscriptionUpdate,
481459
aggregation_value: float,
482460
organization: Organization,
483461
) -> list[tuple[Detector, dict[DetectorGroupKey, DetectorEvaluationResult]]]:
484-
detector_cfg: MetricIssueDetectorConfig = detector.config
485-
if detector_cfg["detection_type"] == AlertRuleDetectionType.DYNAMIC.value:
462+
if self.alert_rule.detection_type == AlertRuleDetectionType.DYNAMIC:
486463
anomaly_detection_packet = AnomalyDetectionUpdate(
487464
entity=subscription_update.get("entity", ""),
488465
subscription_id=subscription_update["subscription_id"],
@@ -522,13 +499,14 @@ def process_results_workflow_engine(
522499
"results": results,
523500
"num_results": len(results),
524501
"value": aggregation_value,
525-
"rule_id": self._alert_rule.id if self._alert_rule else None,
502+
"rule_id": self.alert_rule.id,
526503
},
527504
)
528505
return results
529506

530507
def process_legacy_metric_alerts(
531508
self,
509+
subscription_update: QuerySubscriptionUpdate,
532510
aggregation_value: float,
533511
detector: Detector | None,
534512
results: list[tuple[Detector, dict[DetectorGroupKey, DetectorEvaluationResult]]] | None,
@@ -654,7 +632,7 @@ def process_legacy_metric_alerts(
654632
# is killed here. The trade-off is that we might process an update twice. Mostly
655633
# this will have no effect, but if someone manages to close a triggered incident
656634
# before the next one then we might alert twice.
657-
self.update_alert_rule_stats(self.alert_rule)
635+
self.update_alert_rule_stats()
658636
return fired_incident_triggers
659637

660638
def has_downgraded(self, dataset: str, organization: Organization) -> bool:
@@ -699,7 +677,7 @@ def process_update(self, subscription_update: QuerySubscriptionUpdate) -> None:
699677
if self.has_downgraded(dataset, organization):
700678
return
701679

702-
if self._alert_rule is None:
680+
if not hasattr(self, "alert_rule"):
703681
# QuerySubscriptions must _always_ have an associated AlertRule
704682
# If the alert rule has been removed then clean up associated tables and return
705683
metrics.incr("incidents.alert_rules.no_alert_rule_for_subscription", sample_rate=1.0)
@@ -758,9 +736,8 @@ def process_update(self, subscription_update: QuerySubscriptionUpdate) -> None:
758736
legacy_results = None
759737

760738
if self._has_workflow_engine_processing:
761-
assert detector is not None
762739
workflow_engine_results = self.process_results_workflow_engine(
763-
detector, subscription_update, aggregation_value, organization
740+
subscription_update, aggregation_value, organization
764741
)
765742

766743
if self._has_workflow_engine_processing_only:
@@ -779,6 +756,7 @@ def process_update(self, subscription_update: QuerySubscriptionUpdate) -> None:
779756
workflow engine "and" metric alerts.
780757
"""
781758
legacy_results = self.process_legacy_metric_alerts(
759+
subscription_update,
782760
aggregation_value,
783761
detector,
784762
workflow_engine_results,
@@ -797,8 +775,7 @@ def process_update(self, subscription_update: QuerySubscriptionUpdate) -> None:
797775
)
798776

799777
def trigger_alert_threshold(
800-
self,
801-
trigger: AlertRuleTrigger,
778+
self, trigger: AlertRuleTrigger, metric_value: float
802779
) -> IncidentTrigger | None:
803780
"""
804781
Called when a subscription update exceeds the value defined in the
@@ -1042,7 +1019,7 @@ def handle_incident_severity_update(self) -> None:
10421019
status_method=IncidentStatusMethod.RULE_TRIGGERED,
10431020
)
10441021

1045-
def update_alert_rule_stats(self, alert_rule: AlertRule) -> None:
1022+
def update_alert_rule_stats(self) -> None:
10461023
"""
10471024
Updates stats about the alert rule, if they're changed.
10481025
:return:
@@ -1059,7 +1036,7 @@ def update_alert_rule_stats(self, alert_rule: AlertRule) -> None:
10591036
}
10601037

10611038
update_alert_rule_stats(
1062-
alert_rule,
1039+
self.alert_rule,
10631040
self.subscription,
10641041
self.last_update,
10651042
updated_trigger_alert_counts,

tests/sentry/incidents/subscription_processor/test_subscription_processor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3481,7 +3481,7 @@ def test_seer_call_null_aggregation_value(
34813481

34823482
mock_seer_request.return_value = HTTPResponse(orjson.dumps(seer_return_value), status=200)
34833483
processor = SubscriptionProcessor(self.sub)
3484-
processor._alert_rule = self.dynamic_rule
3484+
processor.alert_rule = self.dynamic_rule
34853485
result = get_anomaly_data_from_seer_legacy(
34863486
alert_rule=processor.alert_rule,
34873487
subscription=processor.subscription,

0 commit comments

Comments
 (0)