22from datetime import UTC , datetime
33
44from django .conf import settings
5- from django .db .models import DateTimeField , IntegerField , OuterRef , Q , Subquery , Value
5+ from django .db .models import Case , DateTimeField , IntegerField , OuterRef , Q , Subquery , Value , When
66from django .db .models .functions import Coalesce
77from drf_spectacular .utils import extend_schema , extend_schema_serializer
88from rest_framework import serializers , status
3535 AlertRuleSerializerResponse ,
3636 CombinedRuleSerializer ,
3737)
38+ from sentry .incidents .endpoints .utils import parse_team_params
3839from sentry .incidents .logic import get_slack_actions_with_async_lookups
3940from sentry .incidents .models .alert_rule import AlertRule
40- from sentry .incidents .models .incident import Incident
41+ from sentry .incidents .models .incident import Incident , IncidentStatus
4142from sentry .incidents .serializers import AlertRuleSerializer as DrfAlertRuleSerializer
4243from sentry .incidents .utils .sentry_apps import trigger_sentry_app_action_creators_for_incidents
4344from sentry .integrations .slack .utils import RedisRuleStatus
4950from sentry .signals import alert_rule_created
5051from sentry .snuba .dataset import Dataset
5152from sentry .tasks .integrations .slack import find_channel_id_for_alert_rule
53+ from sentry .uptime .models import ProjectUptimeSubscription , UptimeStatus
5254from sentry .utils .cursors import Cursor , StringCursor
5355
54- from .utils import parse_team_params
55-
5656
5757class AlertRuleIndexMixin (Endpoint ):
5858 def fetch_metric_alert (self , request , organization , project = None ):
@@ -152,7 +152,7 @@ class OrganizationCombinedRuleIndexEndpoint(OrganizationEndpoint):
152152
153153 def get (self , request : Request , organization ) -> Response :
154154 """
155- Fetches ( metric) alert rules and legacy (issue alert) rules for an organization
155+ Fetches metric, issue and uptime alert rules for an organization
156156 """
157157 project_ids = self .get_requested_project_ids_unchecked (request ) or None
158158 if project_ids == {- 1 }: # All projects for org:
@@ -196,6 +196,11 @@ def get(self, request: Request, organization) -> Response:
196196 project__in = projects ,
197197 )
198198
199+ uptime_rules = ProjectUptimeSubscription .objects .filter (project__in = projects )
200+
201+ if not features .has ("organizations:uptime-rule-api" , organization ):
202+ uptime_rules = ProjectUptimeSubscription .objects .none ()
203+
199204 if not features .has ("organizations:performance-view" , organization ):
200205 # Filter to only error alert rules
201206 alert_rules = alert_rules .filter (snuba_query__dataset = Dataset .Events .value )
@@ -208,8 +213,9 @@ def get(self, request: Request, organization) -> Response:
208213
209214 name = request .GET .get ("name" , None )
210215 if name :
211- alert_rules = alert_rules .filter (Q (name__icontains = name ))
212- issue_rules = issue_rules .filter (Q (label__icontains = name ))
216+ alert_rules = alert_rules .filter (name__icontains = name )
217+ issue_rules = issue_rules .filter (label__icontains = name )
218+ uptime_rules = uptime_rules .filter (name__icontains = name )
213219
214220 if teams_query is not None :
215221 team_ids = teams_query .values_list ("id" , flat = True )
@@ -220,6 +226,7 @@ def get(self, request: Request, organization) -> Response:
220226 team_rule_condition = team_rule_condition | Q (owner_team_id__isnull = True )
221227 alert_rules = alert_rules .filter (team_alert_condition )
222228 issue_rules = issue_rules .filter (team_rule_condition )
229+ uptime_rules = uptime_rules .filter (team_rule_condition )
223230
224231 expand = request .GET .getlist ("expand" , [])
225232 if "latestIncident" in expand :
@@ -255,6 +262,14 @@ def get(self, request: Request, organization) -> Response:
255262 issue_rules = issue_rules .annotate (
256263 incident_status = Value (- 2 , output_field = IntegerField ())
257264 )
265+ uptime_rules = uptime_rules .annotate (
266+ incident_status = Case (
267+ # If an uptime monitor is failing we want to treat it the same as if an alert is failing, so sort
268+ # by the critical status
269+ When (uptime_status = UptimeStatus .FAILED , then = IncidentStatus .CRITICAL .value ),
270+ default = - 2 ,
271+ )
272+ )
258273
259274 if "date_triggered" in sort_key :
260275 far_past_date = Value (datetime .min .replace (tzinfo = UTC ), output_field = DateTimeField ())
@@ -269,16 +284,18 @@ def get(self, request: Request, organization) -> Response:
269284 ),
270285 )
271286 issue_rules = issue_rules .annotate (date_triggered = far_past_date )
287+ uptime_rules = uptime_rules .annotate (date_triggered = far_past_date )
272288 alert_rules_count = alert_rules .count ()
273289 issue_rules_count = issue_rules .count ()
274290 alert_rule_intermediary = CombinedQuerysetIntermediary (alert_rules , sort_key )
275291 rule_intermediary = CombinedQuerysetIntermediary (issue_rules , rule_sort_key )
292+ uptime_intermediary = CombinedQuerysetIntermediary (uptime_rules , rule_sort_key )
276293 response = self .paginate (
277294 request ,
278295 paginator_cls = CombinedQuerysetPaginator ,
279296 on_results = lambda x : serialize (x , request .user , CombinedRuleSerializer (expand = expand )),
280297 default_per_page = 25 ,
281- intermediaries = [alert_rule_intermediary , rule_intermediary ],
298+ intermediaries = [alert_rule_intermediary , rule_intermediary , uptime_intermediary ],
282299 desc = not is_asc ,
283300 cursor_cls = StringCursor if case_insensitive else Cursor ,
284301 case_insensitive = case_insensitive ,
0 commit comments