Skip to content

Commit 7d304d1

Browse files
authored
feat(uptime): Include uptime monitors in the combined rule endpoint. (#74611)
This updates the combined rule endpoint to include uptime monitors if the flag is enabled. These monitors will be sorted to the top of the list if failed, and then lower down with issue alerts if they're in an ok state. <!-- Describe your PR here. -->
1 parent 9142820 commit 7d304d1

File tree

3 files changed

+242
-38
lines changed

3 files changed

+242
-38
lines changed

src/sentry/api/paginator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,8 @@ def _sort_combined_querysets(item):
659659
sort_keys = []
660660
sort_keys.append(self.get_item_key(item))
661661
if len(self.model_key_map.get(type(item))) > 1:
662+
# XXX: This doesn't do anything - it just uses a column name as the sort key. It should be pulling the
663+
# value of the other keys out instead.
662664
sort_keys.extend(iter(self.model_key_map.get(type(item))[1:]))
663665
sort_keys.append(type(item).__name__)
664666
return tuple(sort_keys)

src/sentry/incidents/endpoints/organization_alert_rule_index.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from datetime import UTC, datetime
33

44
from 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
66
from django.db.models.functions import Coalesce
77
from drf_spectacular.utils import extend_schema, extend_schema_serializer
88
from rest_framework import serializers, status
@@ -35,9 +35,10 @@
3535
AlertRuleSerializerResponse,
3636
CombinedRuleSerializer,
3737
)
38+
from sentry.incidents.endpoints.utils import parse_team_params
3839
from sentry.incidents.logic import get_slack_actions_with_async_lookups
3940
from sentry.incidents.models.alert_rule import AlertRule
40-
from sentry.incidents.models.incident import Incident
41+
from sentry.incidents.models.incident import Incident, IncidentStatus
4142
from sentry.incidents.serializers import AlertRuleSerializer as DrfAlertRuleSerializer
4243
from sentry.incidents.utils.sentry_apps import trigger_sentry_app_action_creators_for_incidents
4344
from sentry.integrations.slack.utils import RedisRuleStatus
@@ -49,10 +50,9 @@
4950
from sentry.signals import alert_rule_created
5051
from sentry.snuba.dataset import Dataset
5152
from sentry.tasks.integrations.slack import find_channel_id_for_alert_rule
53+
from sentry.uptime.models import ProjectUptimeSubscription, UptimeStatus
5254
from sentry.utils.cursors import Cursor, StringCursor
5355

54-
from .utils import parse_team_params
55-
5656

5757
class 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

Comments
 (0)