11import datetime
2- from typing import Literal
2+ from typing import Any , Generic , TypeVar
33
44from django .contrib .auth .models import AnonymousUser
5+ from django .core .exceptions import ObjectDoesNotExist
56from rest_framework import serializers , status
67from rest_framework .exceptions import NotFound , PermissionDenied
78from rest_framework .request import Request
1516from sentry .api .exceptions import BadRequest
1617from sentry .api .serializers import Serializer , register , serialize
1718from sentry .api .serializers .rest_framework .base import CamelSnakeSerializer
19+ from sentry .db .models .base import Model
20+ from sentry .db .models .manager .base_query_set import BaseQuerySet
1821from sentry .incidents .models .alert_rule import AlertRule
1922from sentry .models .organization import Organization
2023from sentry .models .organizationmember import OrganizationMember
@@ -73,69 +76,61 @@ def serialize(self, obj, attrs, user, **kwargs):
7376 return result
7477
7578
76- @region_silo_endpoint
77- class BaseRuleSnoozeEndpoint (ProjectEndpoint ):
79+ T = TypeVar ("T" , bound = Model )
80+
81+
82+ class BaseRuleSnoozeEndpoint (ProjectEndpoint , Generic [T ]):
7883 permission_classes = (ProjectAlertRulePermission ,)
79- rule_model = type [Rule ] | type [AlertRule ]
80- rule_field = Literal ["rule" , "alert_rule" ]
84+ rule_field : str # abstract, value comes from child class
8185
8286 def convert_args (self , request : Request , rule_id : int , * args , ** kwargs ):
8387 (args , kwargs ) = super ().convert_args (request , * args , ** kwargs )
8488 project = kwargs ["project" ]
8589 try :
86- if self .rule_model is AlertRule :
87- queryset = self .rule_model .objects .fetch_for_project (project )
88- else :
89- queryset = self .rule_model .objects .filter (project = project )
90+ queryset = self .fetch_rule_list (project = project )
9091 rule = queryset .get (id = rule_id )
91- except self . rule_model . DoesNotExist :
92+ except ObjectDoesNotExist :
9293 raise NotFound (detail = "Rule does not exist" )
9394
9495 kwargs ["rule" ] = rule
9596
9697 return (args , kwargs )
9798
98- def post (self , request : Request , project : Project , rule : Rule | AlertRule ) -> Response :
99+ def post (self , request : Request , project : Project , rule : T ) -> Response :
99100 serializer = RuleSnoozeValidator (data = request .data )
100101 if not serializer .is_valid ():
101102 return Response (serializer .errors , status = status .HTTP_400_BAD_REQUEST )
102103
103104 data = serializer .validated_data
104105
105106 if not can_edit_alert_rule (project .organization , request ):
106- raise PermissionDenied (
107- detail = "Requesting user cannot mute this rule." , code = status .HTTP_403_FORBIDDEN
108- )
109-
110- kwargs = {self .rule_field : rule }
107+ raise PermissionDenied (detail = "Requesting user cannot mute this rule." )
111108
112109 user_id = request .user .id if data .get ("target" ) == "me" else None
113- rule_snooze , created = RuleSnooze .objects .get_or_create (
114- user_id = user_id ,
115- defaults = {
116- "owner_id" : request .user .id ,
117- "until" : data .get ("until" ),
118- "date_added" : datetime .datetime .now (datetime .UTC ),
119- },
120- ** kwargs ,
121- )
122- # don't allow editing of a rulesnooze object for a given rule and user (or no user)
123- if not created :
110+
111+ try :
112+ rule_snooze = self .fetch_instance (
113+ user_id = user_id ,
114+ rule = rule ,
115+ )
116+ # don't allow editing of a rulesnooze object for a given rule and user (or no user)
124117 return Response (
125118 {"detail" : "RuleSnooze already exists for this rule and scope." },
126119 status = status .HTTP_410_GONE ,
127120 )
121+ except RuleSnooze .DoesNotExist :
122+ rule_snooze = self .create_instance (
123+ rule = rule ,
124+ user_id = user_id ,
125+ owner_id = request .user .id ,
126+ until = data .get ("until" ),
127+ date_added = datetime .datetime .now (datetime .UTC ),
128+ )
128129
129130 if not user_id :
130131 # create an audit log entry if the rule is snoozed for everyone
131- audit_log_event = "RULE_SNOOZE" if self .rule_model == Rule else "ALERT_RULE_SNOOZE"
132-
133- self .create_audit_entry (
134- request = request ,
135- organization = project .organization ,
136- target_object = rule .id ,
137- event = audit_log .get_event_id (audit_log_event ),
138- data = rule .get_audit_log_data (),
132+ self .record_audit_log_entry (
133+ request = request , organization = project .organization , rule = rule
139134 )
140135
141136 analytics .record (
@@ -154,13 +149,12 @@ def post(self, request: Request, project: Project, rule: Rule | AlertRule) -> Re
154149 status = status .HTTP_201_CREATED ,
155150 )
156151
157- def delete (self , request : Request , project : Project , rule : Rule | AlertRule ) -> Response :
152+ def delete (self , request : Request , project : Project , rule : T ) -> Response :
158153 # find if there is a mute for all that I can remove
159154 shared_snooze = None
160155 deletion_type = None
161- kwargs = {self .rule_field : rule , "user_id" : None }
162156 try :
163- shared_snooze = RuleSnooze . objects . get ( ** kwargs )
157+ shared_snooze = self . fetch_instance ( user_id = None , rule = rule )
164158 except RuleSnooze .DoesNotExist :
165159 pass
166160
@@ -170,10 +164,9 @@ def delete(self, request: Request, project: Project, rule: Rule | AlertRule) ->
170164 deletion_type = "everyone"
171165
172166 # next check if there is a mute for me that I can remove
173- kwargs = {self .rule_field : rule , "user_id" : request .user .id }
174167 my_snooze = None
175168 try :
176- my_snooze = RuleSnooze . objects . get ( ** kwargs )
169+ my_snooze = self . fetch_instance ( user_id = request . user . id , rule = rule )
177170 except RuleSnooze .DoesNotExist :
178171 pass
179172 else :
@@ -197,32 +190,98 @@ def delete(self, request: Request, project: Project, rule: Rule | AlertRule) ->
197190 # didn't find a match but there is a shared snooze
198191 if shared_snooze :
199192 raise PermissionDenied (
200- detail = "Requesting user cannot unmute this rule." , code = status . HTTP_403_FORBIDDEN
193+ detail = "Requesting user cannot unmute this rule." ,
201194 )
202195 # no snooze at all found
203196 return Response (
204197 {"detail" : "This rulesnooze object doesn't exist." },
205198 status = status .HTTP_404_NOT_FOUND ,
206199 )
207200
201+ def record_audit_log_entry (
202+ self , request : Request , organization : Organization , rule : T , ** kwargs : Any
203+ ) -> None :
204+ raise NotImplementedError ()
205+
206+ def fetch_instance (self , rule : T , user_id : int | None , ** kwargs : Any ) -> RuleSnooze :
207+ raise NotImplementedError ()
208+
209+ def create_instance (self , rule : T , user_id : int | None , ** kwargs : Any ) -> RuleSnooze :
210+ raise NotImplementedError ()
211+
212+ def fetch_rule_list (self , project : Project ) -> BaseQuerySet [T ]:
213+ raise NotImplementedError ()
214+
208215
209216@region_silo_endpoint
210- class RuleSnoozeEndpoint (BaseRuleSnoozeEndpoint ):
217+ class RuleSnoozeEndpoint (BaseRuleSnoozeEndpoint [ Rule ] ):
211218 owner = ApiOwner .ISSUES
212219 publish_status = {
213220 "DELETE" : ApiPublishStatus .UNKNOWN ,
214221 "POST" : ApiPublishStatus .UNKNOWN ,
215222 }
216- rule_model = Rule
217223 rule_field = "rule"
218224
225+ def fetch_rule_list (self , project : Project ) -> BaseQuerySet [Rule ]:
226+ queryset = Rule .objects .filter (project = project )
227+
228+ return queryset
229+
230+ def fetch_instance (self , rule : Rule , user_id : int | None , ** kwargs : Any ) -> RuleSnooze :
231+ rule_snooze = RuleSnooze .objects .get (user_id = user_id , rule = rule , ** kwargs )
232+
233+ return rule_snooze
234+
235+ def create_instance (self , rule : Rule , user_id : int | None , ** kwargs : Any ) -> RuleSnooze :
236+ rule_snooze = RuleSnooze .objects .create (user_id = user_id , rule = rule , ** kwargs )
237+
238+ return rule_snooze
239+
240+ def record_audit_log_entry (
241+ self , request : Request , organization : Organization , rule : Rule , ** kwargs : Any
242+ ) -> None :
243+ self .create_audit_entry (
244+ request = request ,
245+ organization = organization ,
246+ target_object = rule .id ,
247+ event = audit_log .get_event_id ("RULE_SNOOZE" ),
248+ data = rule .get_audit_log_data (),
249+ ** kwargs ,
250+ )
251+
219252
220253@region_silo_endpoint
221- class MetricRuleSnoozeEndpoint (BaseRuleSnoozeEndpoint ):
254+ class MetricRuleSnoozeEndpoint (BaseRuleSnoozeEndpoint [ AlertRule ] ):
222255 owner = ApiOwner .ISSUES
223256 publish_status = {
224257 "DELETE" : ApiPublishStatus .UNKNOWN ,
225258 "POST" : ApiPublishStatus .UNKNOWN ,
226259 }
227- rule_model = AlertRule
228260 rule_field = "alert_rule"
261+
262+ def fetch_rule_list (self , project : Project ) -> BaseQuerySet [AlertRule ]:
263+ queryset = AlertRule .objects .fetch_for_project (project = project )
264+
265+ return queryset
266+
267+ def fetch_instance (self , rule : AlertRule , user_id : int | None , ** kwargs : Any ) -> RuleSnooze :
268+ rule_snooze = RuleSnooze .objects .get (user_id = user_id , alert_rule = rule , ** kwargs )
269+
270+ return rule_snooze
271+
272+ def create_instance (self , rule : AlertRule , user_id : int | None , ** kwargs : Any ) -> RuleSnooze :
273+ rule_snooze = RuleSnooze .objects .create (user_id = user_id , alert_rule = rule , ** kwargs )
274+
275+ return rule_snooze
276+
277+ def record_audit_log_entry (
278+ self , request : Request , organization : Organization , rule : AlertRule , ** kwargs : Any
279+ ) -> None :
280+ self .create_audit_entry (
281+ request = request ,
282+ organization = organization ,
283+ target_object = rule .id ,
284+ event = audit_log .get_event_id ("ALERT_RULE_SNOOZE" ),
285+ data = rule .get_audit_log_data (),
286+ ** kwargs ,
287+ )
0 commit comments