1515from sentry .models .groupopenperiod import GroupOpenPeriod
1616from sentry .models .organization import Organization
1717from sentry .snuba .dataset import Dataset
18- from sentry .snuba .models import QuerySubscription , SnubaQueryEventType
19- from sentry .workflow_engine .models import DataCondition , DataSourceDetector , DetectorGroup
18+ from sentry .snuba .models import QuerySubscription , SnubaQuery , SnubaQueryEventType
19+ from sentry .workflow_engine .models import (
20+ DataCondition ,
21+ DataConditionGroup ,
22+ DataSourceDetector ,
23+ Detector ,
24+ DetectorGroup ,
25+ )
2026from sentry .workflow_engine .models .data_condition import Condition
2127from sentry .workflow_engine .types import DetectorPriorityLevel
2228
@@ -97,6 +103,59 @@ def _condition_snapshot(
97103 return snapshot
98104
99105
106+ def breached_metric_detection_type (detector : Detector ) -> AlertRuleDetectionType | None :
107+ try :
108+ detection_type = AlertRuleDetectionType (detector .config .get ("detection_type" ))
109+ except (TypeError , ValueError ):
110+ return None
111+ if (
112+ detection_type == AlertRuleDetectionType .STATIC
113+ and detector .config .get ("comparison_delta" ) is not None
114+ ):
115+ return AlertRuleDetectionType .PERCENT
116+ return detection_type
117+
118+
119+ def breached_metric_monitor_snapshot (
120+ detector : Detector ,
121+ snuba_query : SnubaQuery ,
122+ condition_group : DataConditionGroup ,
123+ detection_type : AlertRuleDetectionType ,
124+ ) -> dict [str , Any ] | None :
125+ """The monitor settings we share with Seer, or None if Seer can't query the monitor."""
126+
127+ try :
128+ dataset = Dataset (snuba_query .dataset )
129+ except ValueError :
130+ return None
131+ code_mode_dataset = _code_mode_dataset (dataset , snuba_query .event_types )
132+ if code_mode_dataset is None :
133+ return None
134+ conditions = [
135+ _condition_snapshot (condition , detection_type )
136+ # Sort in Python, so a prefetch can still serve it.
137+ for condition in sorted (condition_group .conditions .all (), key = lambda c : c .id )
138+ ]
139+ if not conditions :
140+ return None
141+ return {
142+ "id" : str (detector .id ),
143+ "name" : detector .name ,
144+ "dataset" : code_mode_dataset ,
145+ "query" : snuba_query .query ,
146+ "aggregate" : snuba_query .aggregate ,
147+ "groupBy" : snuba_query .group_by or [],
148+ "timeWindowSeconds" : snuba_query .time_window ,
149+ "detectionType" : detection_type .value ,
150+ "comparisonDeltaSeconds" : detector .config .get ("comparison_delta" ),
151+ "direction" : _direction (conditions ),
152+ "conditions" : conditions ,
153+ "environment" : (
154+ snuba_query .environment .name if snuba_query .environment is not None else None
155+ ),
156+ }
157+
158+
100159def _analysis_window (open_period : GroupOpenPeriod , now : datetime ) -> dict [str , str ]:
101160 end = open_period .date_ended or now
102161 breach_start = max (open_period .date_started , end - MAX_BREACH_WINDOW )
@@ -189,15 +248,9 @@ def resolve_breached_metric_sources(
189248 detector = detector_group .detector
190249 if detector .type != "metric_issue" or detector .project_id != group .project_id :
191250 continue
192- try :
193- detection_type = AlertRuleDetectionType (detector .config .get ("detection_type" ))
194- except (TypeError , ValueError ):
251+ detection_type = breached_metric_detection_type (detector )
252+ if detection_type is None :
195253 continue
196- if (
197- detection_type == AlertRuleDetectionType .STATIC
198- and detector .config .get ("comparison_delta" ) is not None
199- ):
200- detection_type = AlertRuleDetectionType .PERCENT
201254 data_source_link = data_source_links .get (detector .id )
202255 if data_source_link is None :
203256 continue
@@ -208,43 +261,19 @@ def resolve_breached_metric_sources(
208261 subscription = subscriptions .get (subscription_id )
209262 if subscription is None or subscription .project_id != group .project_id :
210263 continue
211- snuba_query = subscription .snuba_query
212- try :
213- dataset = Dataset (snuba_query .dataset )
214- except ValueError :
215- continue
216- code_mode_dataset = _code_mode_dataset (dataset , snuba_query .event_types )
217- if code_mode_dataset is None :
218- continue
219264 condition_group = detector .workflow_condition_group
220265 if condition_group is None :
221266 continue
222- conditions = [
223- _condition_snapshot (condition , detection_type )
224- for condition in condition_group .conditions .all ()
225- ]
226- if not conditions :
267+ monitor = breached_metric_monitor_snapshot (
268+ detector , subscription .snuba_query , condition_group , detection_type
269+ )
270+ if monitor is None :
227271 continue
228272 snapshot = {
229273 "groupId" : str (group .id ),
230274 "groupTitle" : group .title ,
231275 "openPeriodId" : str (open_period .id ),
232- "monitor" : {
233- "id" : str (detector .id ),
234- "name" : detector .name ,
235- "dataset" : code_mode_dataset ,
236- "query" : snuba_query .query ,
237- "aggregate" : snuba_query .aggregate ,
238- "groupBy" : snuba_query .group_by or [],
239- "timeWindowSeconds" : snuba_query .time_window ,
240- "detectionType" : detection_type .value ,
241- "comparisonDeltaSeconds" : detector .config .get ("comparison_delta" ),
242- "direction" : _direction (conditions ),
243- "conditions" : conditions ,
244- "environment" : (
245- snuba_query .environment .name if snuba_query .environment is not None else None
246- ),
247- },
276+ "monitor" : monitor ,
248277 "project" : {"id" : str (group .project_id ), "slug" : group .project .slug },
249278 "analysisWindow" : _analysis_window (open_period , current_time ),
250279 }
@@ -258,7 +287,7 @@ def resolve_breached_metric_sources(
258287 }
259288 resolved [source_ref ] = BreachedMetricSource (
260289 project_id = group .project_id ,
261- dataset = code_mode_dataset ,
290+ dataset = monitor [ "dataset" ] ,
262291 source = source ,
263292 )
264293 return resolved
0 commit comments