Skip to content

Commit 0ecd18a

Browse files
committed
ref(uptime): Remove usage of ProjectUptimeSubscription from endpoints
This systemically removes ProjectUptimeSubscription usages from uptime endpoints. - Remove legacy dual ID handling logic and query parameter dependencies. The useDetectorId query parameter no longer has any affect, all IDs are treated as detector Ids. - Update all endpoints to use Detector model instead of ProjectUptimeSubscription. - Refactor serializers to work with Detector objects and associated UptimeSubscription data. - Update validators to create and update Detector instances directly - Update audit logging to use detector IDs and consolidated audit log data functions. Previously the get_audit_log_data method was part of the ProjectUptimeSubscription, which is no longer used. - Remove unused ProjectUptimeSubscription references throughout codebase IMPORTANTLY the tests have very little changes to them, this is because this change does NOT change how data is returned. Requires #98527
1 parent 68f18b8 commit 0ecd18a

21 files changed

+494
-356
lines changed

src/sentry/apidocs/parameters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ class UptimeParams:
654654
location="path",
655655
required=True,
656656
type=int,
657-
description="The ID of the uptime alert rule you'd like to query. Can be either a project uptime subscription ID (default) or a detector ID (when useDetectorId=1 query parameter is provided).",
657+
description="The ID of the uptime alert rule you'd like to query.",
658658
)
659659
OWNER = OpenApiParameter(
660660
name="owner",

src/sentry/incidents/endpoints/organization_alert_rule_index.py

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,14 @@
7272
from sentry.sentry_apps.services.app import app_service
7373
from sentry.sentry_apps.utils.errors import SentryAppBaseError
7474
from sentry.snuba.dataset import Dataset
75-
from sentry.uptime.models import ProjectUptimeSubscription, UptimeStatus
76-
from sentry.uptime.types import UptimeMonitorMode
75+
from sentry.uptime.types import (
76+
DATA_SOURCE_UPTIME_SUBSCRIPTION,
77+
GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE,
78+
UptimeMonitorMode,
79+
)
7780
from sentry.utils.cursors import Cursor, StringCursor
78-
from sentry.workflow_engine.models import Detector
81+
from sentry.workflow_engine.models import Detector, DetectorState
82+
from sentry.workflow_engine.types import DetectorPriorityLevel
7983

8084
logger = logging.getLogger(__name__)
8185

@@ -274,12 +278,18 @@ def get(self, request: Request, organization: Organization) -> Response:
274278
project__in=projects,
275279
)
276280

277-
uptime_rules = ProjectUptimeSubscription.objects.filter(
278-
project__in=projects,
279-
mode__in=(
280-
UptimeMonitorMode.MANUAL,
281-
UptimeMonitorMode.AUTO_DETECTED_ACTIVE,
282-
),
281+
uptime_rules = (
282+
Detector.objects.filter(
283+
type=GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE,
284+
project__in=projects,
285+
config__mode__in=(
286+
UptimeMonitorMode.MANUAL.value,
287+
UptimeMonitorMode.AUTO_DETECTED_ACTIVE.value,
288+
),
289+
data_sources__type=DATA_SOURCE_UPTIME_SUBSCRIPTION,
290+
)
291+
.select_related("project")
292+
.prefetch_related("data_sources")
283293
)
284294
crons_rules = (
285295
Monitor.objects.filter(project_id__in=[p.id for p in projects])
@@ -367,15 +377,18 @@ def get(self, request: Request, organization: Organization) -> Response:
367377
incident_status=Value(-2, output_field=IntegerField())
368378
)
369379
uptime_rules = uptime_rules.annotate(
380+
detector_priority=Subquery(
381+
DetectorState.objects.filter(detector=OuterRef("pk")).values("state")[:1]
382+
),
370383
incident_status=Case(
371-
# If an uptime monitor is failing we want to treat it the same as if an alert is failing, so sort
372-
# by the critical status
384+
# If an uptime detector is in HIGH priority (failing)
385+
# state, treat it like a critical incident
373386
When(
374-
uptime_subscription__uptime_status=UptimeStatus.FAILED,
387+
detector_priority=DetectorPriorityLevel.HIGH,
375388
then=IncidentStatus.CRITICAL.value,
376389
),
377390
default=-2,
378-
)
391+
),
379392
)
380393
crons_rules = crons_rules.annotate(
381394
incident_status=Case(

src/sentry/incidents/endpoints/serializers/alert_rule.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@
2929
from sentry.sentry_apps.services.app.model import RpcSentryAppComponentContext
3030
from sentry.snuba.dataset import Dataset
3131
from sentry.snuba.models import SnubaQueryEventType
32-
from sentry.uptime.models import ProjectUptimeSubscription
32+
from sentry.uptime.endpoints.serializers import UptimeDetectorSerializer
33+
from sentry.uptime.types import GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE
3334
from sentry.users.models.user import User
3435
from sentry.users.services.user import RpcUser
3536
from sentry.users.services.user.service import user_service
37+
from sentry.workflow_engine.models import Detector
3638

3739
logger = logging.getLogger(__name__)
3840

@@ -377,12 +379,18 @@ def get_attrs(
377379
serialized_rule["id"]: serialized_rule for serialized_rule in serialized_issue_rules
378380
}
379381

380-
serialized_uptime_monitors = serialize(
381-
[x for x in item_list if isinstance(x, ProjectUptimeSubscription)],
382+
uptime_detectors = [
383+
x
384+
for x in item_list
385+
if isinstance(x, Detector) and x.type == GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE
386+
]
387+
serialized_uptime_detectors = serialize(
388+
uptime_detectors,
382389
user=user,
390+
serializer=UptimeDetectorSerializer(),
383391
)
384-
serialized_uptime_monitor_map_by_id = {
385-
item["id"]: item for item in serialized_uptime_monitors
392+
serialized_uptime_detector_map_by_id = {
393+
item["id"]: item for item in serialized_uptime_detectors
386394
}
387395

388396
serialized_cron_monitors = serialize(
@@ -417,11 +425,12 @@ def get_attrs(
417425
# This is an issue alert rule
418426
results[item] = serialized_issue_rule_map_by_id[item_id]
419427
elif (
420-
isinstance(item, ProjectUptimeSubscription)
421-
and item_id in serialized_uptime_monitor_map_by_id
428+
isinstance(item, Detector)
429+
and item.type == GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE
430+
and item_id in serialized_uptime_detector_map_by_id
422431
):
423-
# This is an uptime monitor
424-
results[item] = serialized_uptime_monitor_map_by_id[item_id]
432+
# This is an uptime detector
433+
results[item] = serialized_uptime_detector_map_by_id[item_id]
425434
elif (
426435
# XXX(epurkhiser): Monitors use their GUID as their IDs
427436
isinstance(item, Monitor)
@@ -436,7 +445,10 @@ def get_attrs(
436445
"id": item_id,
437446
"issue_rule": isinstance(item, Rule),
438447
"metric_rule": isinstance(item, AlertRule),
439-
"uptime_rule": isinstance(item, ProjectUptimeSubscription),
448+
"uptime_rule": (
449+
isinstance(item, Detector)
450+
and item.type == GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE
451+
),
440452
"crons_rule": isinstance(item, Monitor),
441453
},
442454
)
@@ -445,7 +457,7 @@ def get_attrs(
445457

446458
def serialize(
447459
self,
448-
obj: Rule | AlertRule | ProjectUptimeSubscription | Monitor,
460+
obj: Rule | AlertRule | Detector | Monitor,
449461
attrs: Mapping[Any, Any],
450462
user: User | RpcUser | AnonymousUser,
451463
**kwargs: Any,
@@ -455,7 +467,7 @@ def serialize(
455467
updated_attrs["type"] = "alert_rule"
456468
elif isinstance(obj, Rule):
457469
updated_attrs["type"] = "rule"
458-
elif isinstance(obj, ProjectUptimeSubscription):
470+
elif isinstance(obj, Detector) and obj.type == GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE:
459471
updated_attrs["type"] = "uptime"
460472
elif isinstance(obj, Monitor):
461473
updated_attrs["type"] = "monitor"

src/sentry/uptime/detectors/result_handler.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,8 @@
1212
from sentry import audit_log
1313
from sentry.uptime.detectors.ranking import _get_cluster
1414
from sentry.uptime.detectors.tasks import set_failed_url
15-
from sentry.uptime.models import UptimeSubscription, get_project_subscription
16-
from sentry.uptime.subscriptions.subscriptions import (
17-
delete_uptime_detector,
18-
update_project_uptime_subscription,
19-
)
15+
from sentry.uptime.models import UptimeSubscription, get_audit_log_data
16+
from sentry.uptime.subscriptions.subscriptions import delete_uptime_detector, update_uptime_detector
2017
from sentry.uptime.types import UptimeMonitorMode
2118
from sentry.utils import metrics
2219
from sentry.utils.audit import create_system_audit_entry
@@ -86,18 +83,17 @@ def handle_onboarding_result(
8683
if scheduled_check_time - ONBOARDING_MONITOR_PERIOD > detector.date_added:
8784
# If we've had mostly successes throughout the onboarding period then we can graduate the subscription
8885
# to active.
89-
project_subscription = get_project_subscription(detector)
90-
update_project_uptime_subscription(
91-
project_subscription,
86+
update_uptime_detector(
87+
detector,
9288
interval_seconds=int(AUTO_DETECTED_ACTIVE_SUBSCRIPTION_INTERVAL.total_seconds()),
9389
mode=UptimeMonitorMode.AUTO_DETECTED_ACTIVE,
9490
ensure_assignment=True,
9591
)
9692
create_system_audit_entry(
9793
organization=detector.project.organization,
98-
target_object=project_subscription.id,
94+
target_object=detector.id,
9995
event=audit_log.get_event_id("UPTIME_MONITOR_ADD"),
100-
data=project_subscription.get_audit_log_data(),
96+
data=get_audit_log_data(detector),
10197
)
10298

10399
metrics.incr(

src/sentry/uptime/endpoints/bases.py

Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from sentry.api.api_owners import ApiOwner
44
from sentry.api.bases.project import ProjectAlertRulePermission, ProjectEndpoint
55
from sentry.api.exceptions import ResourceDoesNotExist
6-
from sentry.uptime.models import ProjectUptimeSubscription, get_detector
76
from sentry.uptime.types import (
87
DATA_SOURCE_UPTIME_SUBSCRIPTION,
98
GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE,
@@ -18,51 +17,25 @@ class ProjectUptimeAlertEndpoint(ProjectEndpoint):
1817
def convert_args(
1918
self,
2019
request: Request,
21-
# XXX(epurkhiser): There's a getsentry test that depends on this
22-
# argument name, when we change this over to purely detector_id we
23-
# should make the effort to fix the parameter name then
20+
# XXX(epurkhiser): THIS IS A DETECTOR
21+
#
22+
# This name is completely wrong, but there's a test in getsentry that's
23+
# using this parameter name that we'll have to fix first to change it.
2424
uptime_project_subscription_id: str,
2525
*args,
2626
**kwargs,
2727
):
2828
args, kwargs = super().convert_args(request, *args, **kwargs)
2929
project = kwargs["project"]
3030

31-
# Check query parameter to determine if ID should be treated as detector ID
32-
use_detector_id = request.GET.get("useDetectorId") == "1"
33-
34-
# XXX(epurkhiser): We can remove this dual reading logic once all
35-
# endpoints are using the Detector IDs over ProjectUptimeSubscription
36-
# IDs.
37-
if use_detector_id:
38-
# Treat ID as detector ID - find detector and its associated subscription
39-
try:
40-
detector = Detector.objects.get(
41-
project=project,
42-
id=uptime_project_subscription_id,
43-
type=GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE,
44-
data_sources__type=DATA_SOURCE_UPTIME_SUBSCRIPTION,
45-
)
46-
# Get the uptime subscription from the detector's data source
47-
data_source = detector.data_sources.get(type=DATA_SOURCE_UPTIME_SUBSCRIPTION)
48-
uptime_subscription_id = data_source.source_id
49-
uptime_monitor = ProjectUptimeSubscription.objects.get(
50-
project=project, uptime_subscription__id=uptime_subscription_id
51-
)
52-
kwargs["uptime_detector"] = detector
53-
kwargs["uptime_monitor"] = uptime_monitor
54-
except (Detector.DoesNotExist, ProjectUptimeSubscription.DoesNotExist, AttributeError):
55-
raise ResourceDoesNotExist
56-
else:
57-
# Treat ID as project uptime subscription ID (legacy behavior)
58-
try:
59-
uptime_monitor = ProjectUptimeSubscription.objects.get(
60-
project=project, id=uptime_project_subscription_id
61-
)
62-
detector = get_detector(uptime_monitor.uptime_subscription)
63-
kwargs["uptime_detector"] = detector
64-
kwargs["uptime_monitor"] = uptime_monitor
65-
except ProjectUptimeSubscription.DoesNotExist:
66-
raise ResourceDoesNotExist
31+
try:
32+
kwargs["uptime_detector"] = Detector.objects.get(
33+
project=project,
34+
id=uptime_project_subscription_id,
35+
type=GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE,
36+
data_sources__type=DATA_SOURCE_UPTIME_SUBSCRIPTION,
37+
)
38+
except Detector.DoesNotExist:
39+
raise ResourceDoesNotExist
6740

6841
return args, kwargs

src/sentry/uptime/endpoints/organiation_uptime_alert_index.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from django.db.models import Q
2+
from django.db.models.fields import BigIntegerField, CharField
3+
from django.db.models.functions import Cast
24
from drf_spectacular.utils import extend_schema
35
from rest_framework.request import Request
46
from rest_framework.response import Response
@@ -19,10 +21,15 @@
1921
from sentry.search.utils import tokenize_query
2022
from sentry.types.actor import Actor
2123
from sentry.uptime.endpoints.serializers import (
22-
ProjectUptimeSubscriptionSerializer,
23-
ProjectUptimeSubscriptionSerializerResponse,
24+
UptimeDetectorSerializer,
25+
UptimeDetectorSerializerResponse,
2426
)
25-
from sentry.uptime.models import ProjectUptimeSubscription
27+
from sentry.uptime.models import UptimeSubscription
28+
from sentry.uptime.types import (
29+
DATA_SOURCE_UPTIME_SUBSCRIPTION,
30+
GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE,
31+
)
32+
from sentry.workflow_engine.models import Detector
2633

2734

2835
@region_silo_endpoint
@@ -44,7 +51,7 @@ class OrganizationUptimeAlertIndexEndpoint(OrganizationEndpoint):
4451
],
4552
responses={
4653
200: inline_sentry_response_serializer(
47-
"UptimeAlertList", list[ProjectUptimeSubscriptionSerializerResponse]
54+
"UptimeAlertList", list[UptimeDetectorSerializerResponse]
4855
),
4956
401: RESPONSE_UNAUTHORIZED,
5057
403: RESPONSE_FORBIDDEN,
@@ -60,14 +67,17 @@ def get(self, request: Request, organization: Organization) -> Response:
6067
except NoProjects:
6168
return self.respond([])
6269

63-
queryset = ProjectUptimeSubscription.objects.filter(
64-
project__organization_id=organization.id, project_id__in=filter_params["project_id"]
70+
queryset = Detector.objects.filter(
71+
type=GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE,
72+
project__organization_id=organization.id,
73+
project_id__in=filter_params["project_id"],
6574
)
6675
query = request.GET.get("query")
6776
owners = request.GET.getlist("owner")
6877

6978
if "environment" in filter_params:
70-
queryset = queryset.filter(environment__in=filter_params["environment_objects"])
79+
environment_names = [env.name for env in filter_params["environment_objects"]]
80+
queryset = queryset.filter(config__environment__in=environment_names)
7181

7282
if owners:
7383
owners_set = set(owners)
@@ -103,10 +113,20 @@ def get(self, request: Request, organization: Organization) -> Response:
103113
for key, value in tokens.items():
104114
if key == "query":
105115
query_value = " ".join(value)
106-
queryset = queryset.filter(
107-
Q(name__icontains=query_value)
108-
| Q(uptime_subscription__url__icontains=query_value)
116+
117+
linked_subscription_ids = queryset.values_list(
118+
Cast("data_sources__source_id", BigIntegerField()), flat=True
109119
)
120+
matching_subscription_ids = UptimeSubscription.objects.filter(
121+
id__in=linked_subscription_ids, url__icontains=query_value
122+
).values_list(Cast("id", CharField()), flat=True)
123+
124+
url_filter = Q(
125+
data_sources__type=DATA_SOURCE_UPTIME_SUBSCRIPTION,
126+
data_sources__source_id__in=matching_subscription_ids,
127+
)
128+
129+
queryset = queryset.filter(Q(name__icontains=query_value) | url_filter)
110130
elif key == "name":
111131
queryset = queryset.filter(in_iexact("name", value))
112132
else:
@@ -115,6 +135,6 @@ def get(self, request: Request, organization: Organization) -> Response:
115135
return self.paginate(
116136
request=request,
117137
queryset=queryset,
118-
on_results=lambda x: serialize(x, request.user, ProjectUptimeSubscriptionSerializer()),
138+
on_results=lambda x: serialize(x, request.user, UptimeDetectorSerializer()),
119139
paginator_cls=OffsetPaginator,
120140
)

src/sentry/uptime/endpoints/project_uptime_alert_checks_index.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,7 @@
3535
from sentry.uptime.eap_utils import get_columns_for_uptime_trace_item_type
3636
from sentry.uptime.endpoints.bases import ProjectUptimeAlertEndpoint
3737
from sentry.uptime.endpoints.serializers import EapCheckEntrySerializerResponse
38-
from sentry.uptime.models import (
39-
ProjectUptimeSubscription,
40-
UptimeSubscription,
41-
get_uptime_subscription,
42-
)
38+
from sentry.uptime.models import UptimeSubscription, get_uptime_subscription
4339
from sentry.uptime.types import EapCheckEntry, IncidentStatus
4440
from sentry.utils import snuba_rpc
4541
from sentry.workflow_engine.models import Detector
@@ -60,7 +56,6 @@ def get(
6056
request: Request,
6157
project: Project,
6258
uptime_detector: Detector,
63-
uptime_monitor: ProjectUptimeSubscription,
6459
) -> Response:
6560
uptime_subscription = get_uptime_subscription(uptime_detector)
6661

0 commit comments

Comments
 (0)