Skip to content

Commit 8634c54

Browse files
authored
fix: Correct API usage for Crons (#42886)
- Remove legacy docstrings and replace with better descriptions - Missing checkin details endpoint for Hybrid Cloud Refs #42850
1 parent ec012b2 commit 8634c54

File tree

5 files changed

+63
-44
lines changed

5 files changed

+63
-44
lines changed

src/sentry/api/endpoints/monitor_checkin_details.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
from django.db import transaction
24
from django.utils import timezone
35
from drf_spectacular.utils import extend_schema
@@ -41,7 +43,15 @@ class MonitorCheckInDetailsEndpoint(Endpoint):
4143

4244
# TODO(dcramer): this code needs shared with other endpoints as its security focused
4345
# TODO(dcramer): this doesnt handle is_global roles
44-
def convert_args(self, request: Request, monitor_id, checkin_id, *args, **kwargs):
46+
def convert_args(
47+
self,
48+
request: Request,
49+
monitor_id,
50+
checkin_id,
51+
organization_slug: str | None = None,
52+
*args,
53+
**kwargs,
54+
):
4555
try:
4656
monitor = Monitor.objects.get(guid=monitor_id)
4757
except Monitor.DoesNotExist:
@@ -51,6 +61,10 @@ def convert_args(self, request: Request, monitor_id, checkin_id, *args, **kwargs
5161
if project.status != ProjectStatus.VISIBLE:
5262
raise ResourceDoesNotExist
5363

64+
if organization_slug:
65+
if project.organization.slug != organization_slug:
66+
return self.respond_invalid()
67+
5468
if hasattr(request.auth, "project_id") and project.id != request.auth.project_id:
5569
return self.respond(status=400)
5670

@@ -102,12 +116,7 @@ def convert_args(self, request: Request, monitor_id, checkin_id, *args, **kwargs
102116
)
103117
def get(self, request: Request, project, monitor, checkin) -> Response:
104118
"""
105-
Retrieve a check-in
106-
```````````````````
107-
108-
:pparam string monitor_id: the id of the monitor.
109-
:pparam string checkin_id: the id of the check-in.
110-
:auth: required
119+
Retrieves details for a check-in.
111120
112121
You may use `latest` for the `checkin_id` parameter in order to retrieve
113122
the most recent (by creation date) check-in which is still mutable (not marked as finished).
@@ -128,26 +137,22 @@ def get(self, request: Request, project, monitor, checkin) -> Response:
128137
request=MonitorCheckInValidator,
129138
responses={
130139
200: inline_sentry_response_serializer(
131-
"MonitorCheckIn2", MonitorCheckInSerializerResponse
140+
"MonitorCheckIn", MonitorCheckInSerializerResponse
132141
),
133142
208: RESPONSE_ALREADY_REPORTED,
134143
400: RESPONSE_BAD_REQUEST,
135144
401: RESPONSE_UNAUTHORIZED,
136145
403: RESPONSE_FORBIDDEN,
137146
404: RESPONSE_NOTFOUND,
138147
},
139-
examples=[
140-
# OpenApiExample()
141-
],
142148
)
143149
def put(self, request: Request, project, monitor, checkin) -> Response:
144150
"""
145-
Update a check-in
146-
`````````````````
151+
Updates a check-in.
152+
153+
Once a check-in is finished (indicated via an `ok` or `error` status) it can no longer be changed.
147154
148-
:pparam string monitor_id: the id of the monitor.
149-
:pparam string checkin_id: the id of the check-in.
150-
:auth: required
155+
If you simply wish to update that the task is still running, you can simply send an empty payload.
151156
152157
You may use `latest` for the `checkin_id` parameter in order to retrieve
153158
the most recent (by creation date) check-in which is still mutable (not marked as finished).
@@ -165,15 +170,16 @@ def put(self, request: Request, project, monitor, checkin) -> Response:
165170

166171
current_datetime = timezone.now()
167172
params = {"date_updated": current_datetime}
173+
if "status" in result:
174+
params["status"] = getattr(CheckInStatus, result["status"].upper())
175+
168176
if "duration" in result:
169177
params["duration"] = result["duration"]
170-
else:
178+
# if a duration is not defined and we're at a finish state, calculate one
179+
elif params.get("status", checkin.status) in CheckInStatus.FINISHED_VALUES:
171180
duration = int((current_datetime - checkin.date_added).total_seconds() * 1000)
172181
params["duration"] = duration
173182

174-
if "status" in result:
175-
params["status"] = getattr(CheckInStatus, result["status"].upper())
176-
177183
with transaction.atomic():
178184
checkin.update(**params)
179185
if checkin.status == CheckInStatus.ERROR:

src/sentry/api/endpoints/monitor_checkins.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,7 @@ def get(
5151
self, request: Request, project, monitor, organization_slug: str | None = None
5252
) -> Response:
5353
"""
54-
Retrieve check-ins for a monitor
55-
````````````````````````````````
56-
57-
:pparam string monitor_id: the id of the monitor.
58-
:auth: required
54+
Retrieve a list of check-ins for a monitor
5955
"""
6056
# we don't allow read permission with DSNs
6157
if isinstance(request.auth, ProjectKey):
@@ -100,11 +96,12 @@ def post(
10096
self, request: Request, project, monitor, organization_slug: str | None = None
10197
) -> Response:
10298
"""
103-
Create a new check-in for a monitor
104-
```````````````````````````````````
99+
Creates a new check-in for a monitor.
100+
101+
If `status` is not present, it will be assumed that the check-in is starting, and be marked as `in_progress`.
105102
106-
:pparam string monitor_id: the id of the monitor.
107-
:auth: required
103+
To achieve a ping-like behavior, you can simply define `status` and optionally `duration` and
104+
this check-in will be automatically marked as finished.
108105
109106
Note: If a DSN is utilized for authentication, the response will be limited in details.
110107
"""

src/sentry/api/endpoints/monitor_details.py

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,7 @@ def get(
4444
self, request: Request, project, monitor, organization_slug: str | None = None
4545
) -> Response:
4646
"""
47-
Retrieve a monitor
48-
``````````````````
49-
50-
:pparam string monitor_id: the id of the monitor.
51-
:auth: required
47+
Retrieves details for a monitor.
5248
"""
5349
if organization_slug:
5450
if project.organization.slug != organization_slug:
@@ -74,11 +70,7 @@ def put(
7470
self, request: Request, project, monitor, organization_slug: str | None = None
7571
) -> Response:
7672
"""
77-
Update a monitor
78-
````````````````
79-
80-
:pparam string monitor_id: the id of the monitor.
81-
:auth: required
73+
Update a monitor.
8274
"""
8375
if organization_slug:
8476
if project.organization.slug != organization_slug:
@@ -145,12 +137,9 @@ def delete(
145137
self, request: Request, project, monitor, organization_slug: str | None = None
146138
) -> Response:
147139
"""
148-
Delete a monitor
149-
````````````````
150-
151-
:pparam string monitor_id: the id of the monitor.
152-
:auth: required
140+
Delete a monitor.
153141
"""
142+
154143
if organization_slug:
155144
if project.organization.slug != organization_slug:
156145
return self.respond_invalid()

src/sentry/api/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,11 @@
713713
MonitorCheckInsEndpoint.as_view(),
714714
name="sentry-api-0-monitor-check-in-index-with-org",
715715
),
716+
url(
717+
r"^(?P<monitor_id>[^\/]+)/checkins/(?P<checkin_id>[^\/]+)/$",
718+
MonitorCheckInDetailsEndpoint.as_view(),
719+
name="sentry-api-0-monitor-check-in-details-with-org",
720+
),
716721
url(
717722
r"^(?P<monitor_id>[^\/]+)/stats/$",
718723
MonitorStatsEndpoint.as_view(),

tests/sentry/api/endpoints/test_monitor_checkin_details.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,28 @@ def setUp(self):
1616
super().setUp()
1717
self.login_as(self.user)
1818

19+
def test_noop_in_progerss(self):
20+
monitor = Monitor.objects.create(
21+
organization_id=self.organization.id,
22+
project_id=self.project.id,
23+
next_checkin=timezone.now() - timedelta(minutes=1),
24+
type=MonitorType.CRON_JOB,
25+
config={"schedule": "* * * * *"},
26+
date_added=timezone.now() - timedelta(minutes=1),
27+
)
28+
checkin = MonitorCheckIn.objects.create(
29+
monitor=monitor,
30+
project_id=self.project.id,
31+
date_added=monitor.date_added,
32+
status=CheckInStatus.IN_PROGRESS,
33+
)
34+
35+
self.get_success_response(monitor.guid, checkin.guid)
36+
37+
checkin = MonitorCheckIn.objects.get(id=checkin.id)
38+
assert checkin.status == CheckInStatus.IN_PROGRESS
39+
assert checkin.date_updated > checkin.date_added
40+
1941
def test_passing(self):
2042
monitor = Monitor.objects.create(
2143
organization_id=self.organization.id,

0 commit comments

Comments
 (0)