Skip to content

Commit 3e75160

Browse files
committed
feat(uptime): Accept timeout customization in uptime APIs
1 parent ad9a523 commit 3e75160

File tree

6 files changed

+52
-16
lines changed

6 files changed

+52
-16
lines changed

src/sentry/uptime/endpoints/validators.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ class UptimeMonitorValidator(CamelSnakeSerializer):
9393
interval_seconds = serializers.ChoiceField(
9494
required=True, choices=[int(i.total_seconds()) for i in VALID_INTERVALS]
9595
)
96+
timeout_ms = serializers.IntegerField(
97+
required=True,
98+
min_value=1000,
99+
max_value=30 * 1000,
100+
)
96101
mode = serializers.IntegerField(required=False)
97102
method = serializers.ChoiceField(
98103
required=False, choices=list(zip(SUPPORTED_HTTP_METHODS, SUPPORTED_HTTP_METHODS))
@@ -172,6 +177,7 @@ def create(self, validated_data):
172177
environment=environment,
173178
url=validated_data["url"],
174179
interval_seconds=validated_data["interval_seconds"],
180+
timeout_ms=validated_data["timeout_ms"],
175181
name=validated_data["name"],
176182
mode=validated_data.get("mode", ProjectUptimeSubscriptionMode.MANUAL),
177183
owner=validated_data.get("owner"),
@@ -201,6 +207,9 @@ def update(self, instance: ProjectUptimeSubscription, data):
201207
if "interval_seconds" in data
202208
else instance.uptime_subscription.interval_seconds
203209
)
210+
timeout_ms = (
211+
data["timeout_ms"] if "timeout_ms" in data else instance.uptime_subscription.timeout_ms
212+
)
204213
method = data["method"] if "method" in data else instance.uptime_subscription.method
205214
headers = data["headers"] if "headers" in data else instance.uptime_subscription.headers
206215
body = data["body"] if "body" in data else instance.uptime_subscription.body
@@ -223,6 +232,7 @@ def update(self, instance: ProjectUptimeSubscription, data):
223232
environment=environment,
224233
url=url,
225234
interval_seconds=interval_seconds,
235+
timeout_ms=timeout_ms,
226236
method=method,
227237
headers=headers,
228238
body=body,

src/sentry/uptime/subscriptions/subscriptions.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@
2828
UPTIME_SUBSCRIPTION_TYPE = "uptime_monitor"
2929
MAX_AUTO_SUBSCRIPTIONS_PER_ORG = 1
3030
MAX_MANUAL_SUBSCRIPTIONS_PER_ORG = 100
31-
# Default timeout for all subscriptions
32-
DEFAULT_SUBSCRIPTION_TIMEOUT_MS = 10000
3331

3432

3533
class MaxManualUptimeSubscriptionsReached(ValueError):
@@ -39,14 +37,18 @@ class MaxManualUptimeSubscriptionsReached(ValueError):
3937
def retrieve_uptime_subscription(
4038
url: str,
4139
interval_seconds: int,
40+
timeout_ms: int,
4241
method: str,
4342
headers: Sequence[tuple[str, str]],
4443
body: str | None,
4544
) -> UptimeSubscription | None:
4645
try:
4746
subscription = (
4847
UptimeSubscription.objects.filter(
49-
url=url, interval_seconds=interval_seconds, method=method
48+
url=url,
49+
interval_seconds=interval_seconds,
50+
timeout_ms=timeout_ms,
51+
method=method,
5052
)
5153
.annotate(
5254
headers_md5=MD5("headers", output_field=TextField()),
@@ -66,7 +68,7 @@ def retrieve_uptime_subscription(
6668
def get_or_create_uptime_subscription(
6769
url: str,
6870
interval_seconds: int,
69-
timeout_ms: int = DEFAULT_SUBSCRIPTION_TIMEOUT_MS,
71+
timeout_ms: int,
7072
method: str = "GET",
7173
headers: Sequence[tuple[str, str]] | None = None,
7274
body: str | None = None,
@@ -81,7 +83,9 @@ def get_or_create_uptime_subscription(
8183
# domain.
8284
result = extract_domain_parts(url)
8385

84-
subscription = retrieve_uptime_subscription(url, interval_seconds, method, headers, body)
86+
subscription = retrieve_uptime_subscription(
87+
url, interval_seconds, timeout_ms, method, headers, body
88+
)
8589
created = False
8690

8791
if subscription is None:
@@ -102,7 +106,7 @@ def get_or_create_uptime_subscription(
102106
except IntegrityError:
103107
# Handle race condition where we tried to retrieve an existing subscription while it was being created
104108
subscription = retrieve_uptime_subscription(
105-
url, interval_seconds, method, headers, body
109+
url, interval_seconds, timeout_ms, method, headers, body
106110
)
107111

108112
if subscription is None:
@@ -146,7 +150,7 @@ def get_or_create_project_uptime_subscription(
146150
environment: Environment | None,
147151
url: str,
148152
interval_seconds: int,
149-
timeout_ms: int = DEFAULT_SUBSCRIPTION_TIMEOUT_MS,
153+
timeout_ms: int,
150154
method: str = "GET",
151155
headers: Sequence[tuple[str, str]] | None = None,
152156
body: str | None = None,
@@ -190,6 +194,7 @@ def update_project_uptime_subscription(
190194
environment: Environment | None,
191195
url: str,
192196
interval_seconds: int,
197+
timeout_ms: int,
193198
method: str,
194199
headers: Sequence[tuple[str, str]],
195200
body: str | None,
@@ -201,7 +206,7 @@ def update_project_uptime_subscription(
201206
"""
202207
cur_uptime_subscription = uptime_monitor.uptime_subscription
203208
new_uptime_subscription = get_or_create_uptime_subscription(
204-
url, interval_seconds, cur_uptime_subscription.timeout_ms, method, headers, body
209+
url, interval_seconds, timeout_ms, method, headers, body
205210
)
206211
updated_subscription = cur_uptime_subscription.id != new_uptime_subscription.id
207212

static/app/views/monitors/components/detailsTimeline.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type {Organization} from 'sentry/types/organization';
1313
import {setApiQueryData, useQueryClient} from 'sentry/utils/queryClient';
1414
import useApi from 'sentry/utils/useApi';
1515
import {useDimensions} from 'sentry/utils/useDimensions';
16-
import useRouter from 'sentry/utils/useRouter';
16+
import {useLocation} from 'sentry/utils/useLocation';
1717
import type {Monitor} from 'sentry/views/monitors/types';
1818
import {makeMonitorDetailsQueryKey} from 'sentry/views/monitors/utils';
1919

@@ -27,7 +27,7 @@ interface Props {
2727
}
2828

2929
export function DetailsTimeline({monitor, organization}: Props) {
30-
const {location} = useRouter();
30+
const location = useLocation();
3131
const api = useApi();
3232
const queryClient = useQueryClient();
3333

tests/sentry/uptime/endpoints/test_project_uptime_alert_details.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def test_all(self):
4141
owner=f"user:{self.user.id}",
4242
url="https://santry.io",
4343
interval_seconds=300,
44+
timeoout_ms=1500,
4445
headers=[["hello", "world"]],
4546
body="something",
4647
)
@@ -56,6 +57,7 @@ def test_all(self):
5657
uptime_sub.refresh_from_db()
5758
assert uptime_sub.url == "https://santry.io"
5859
assert uptime_sub.interval_seconds == 300
60+
assert uptime_sub.timeout_ms == 1500
5961
assert uptime_sub.headers == [["hello", "world"]]
6062
assert uptime_sub.body == "something"
6163

@@ -67,6 +69,7 @@ def test_all(self):
6769
owner=f"user:{self.user.id}",
6870
url="https://santry.io",
6971
interval_seconds=300,
72+
timeoout_ms=1500,
7073
headers=[["hello", "world"]],
7174
body=None,
7275
)
@@ -79,6 +82,7 @@ def test_all(self):
7982
uptime_sub.refresh_from_db()
8083
assert uptime_sub.url == "https://santry.io"
8184
assert uptime_sub.interval_seconds == 300
85+
assert uptime_sub.timeout_ms == 1500
8286
assert uptime_sub.headers == [["hello", "world"]]
8387
assert uptime_sub.body is None
8488

tests/sentry/uptime/endpoints/test_project_uptime_alert_index.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from sentry.testutils.helpers import with_feature
77
from sentry.uptime.endpoints.validators import MAX_REQUEST_SIZE_BYTES
88
from sentry.uptime.models import ProjectUptimeSubscription, ProjectUptimeSubscriptionMode
9-
from sentry.uptime.subscriptions.subscriptions import DEFAULT_SUBSCRIPTION_TIMEOUT_MS
109
from tests.sentry.uptime.endpoints import UptimeAlertBaseEndpointTest
1110

1211

@@ -25,6 +24,7 @@ def test_no_feature(self):
2524
owner=f"user:{self.user.id}",
2625
url="http://sentry.io",
2726
interval_seconds=60,
27+
timeout_ms=1000,
2828
status_code=404,
2929
)
3030

@@ -38,6 +38,7 @@ def test(self):
3838
owner=f"user:{self.user.id}",
3939
url="http://sentry.io",
4040
interval_seconds=60,
41+
timeout_ms=1500,
4142
body=None,
4243
)
4344
uptime_monitor = ProjectUptimeSubscription.objects.get(id=resp.data["id"])
@@ -51,7 +52,7 @@ def test(self):
5152
assert uptime_monitor.mode == ProjectUptimeSubscriptionMode.MANUAL
5253
assert uptime_subscription.url == "http://sentry.io"
5354
assert uptime_subscription.interval_seconds == 60
54-
assert uptime_subscription.timeout_ms == DEFAULT_SUBSCRIPTION_TIMEOUT_MS
55+
assert uptime_subscription.timeout_ms == 1500
5556
assert uptime_subscription.body is None
5657

5758
@with_feature("organizations:uptime-api-create-update")
@@ -63,6 +64,7 @@ def test_no_environment(self):
6364
owner=f"user:{self.user.id}",
6465
url="http://sentry.io",
6566
interval_seconds=60,
67+
timeout_ms=1000,
6668
body=None,
6769
)
6870
uptime_monitor = ProjectUptimeSubscription.objects.get(id=resp.data["id"])
@@ -74,7 +76,7 @@ def test_no_environment(self):
7476
assert uptime_monitor.mode == ProjectUptimeSubscriptionMode.MANUAL
7577
assert uptime_subscription.url == "http://sentry.io"
7678
assert uptime_subscription.interval_seconds == 60
77-
assert uptime_subscription.timeout_ms == DEFAULT_SUBSCRIPTION_TIMEOUT_MS
79+
assert uptime_subscription.timeout_ms == 1000
7880
assert uptime_subscription.body is None
7981

8082
@with_feature("organizations:uptime-api-create-update")
@@ -87,6 +89,7 @@ def test_no_owner(self):
8789
url="http://sentry.io",
8890
owner=None,
8991
interval_seconds=60,
92+
timeout_ms=1000,
9093
)
9194
uptime_monitor = ProjectUptimeSubscription.objects.get(id=resp.data["id"])
9295
uptime_subscription = uptime_monitor.uptime_subscription
@@ -96,7 +99,7 @@ def test_no_owner(self):
9699
assert uptime_monitor.mode == ProjectUptimeSubscriptionMode.MANUAL
97100
assert uptime_subscription.url == "http://sentry.io"
98101
assert uptime_subscription.interval_seconds == 60
99-
assert uptime_subscription.timeout_ms == DEFAULT_SUBSCRIPTION_TIMEOUT_MS
102+
assert uptime_subscription.timeout_ms == 1000
100103

101104
# Test without passing the owner
102105
resp = self.get_success_response(
@@ -106,6 +109,7 @@ def test_no_owner(self):
106109
name="test",
107110
url="http://getsentry.com",
108111
interval_seconds=60,
112+
timeout_ms=1000,
109113
)
110114
uptime_monitor = ProjectUptimeSubscription.objects.get(id=resp.data["id"])
111115
assert uptime_monitor.owner_user_id is None
@@ -121,6 +125,7 @@ def test_mode_no_superadmin(self):
121125
owner=f"user:{self.user.id}",
122126
url="http://sentry.io",
123127
interval_seconds=60,
128+
timeout_ms=1000,
124129
mode=ProjectUptimeSubscriptionMode.AUTO_DETECTED_ACTIVE,
125130
status_code=400,
126131
)
@@ -139,6 +144,7 @@ def test_mode_superadmin(self):
139144
owner=f"user:{self.user.id}",
140145
url="http://sentry.io",
141146
interval_seconds=60,
147+
timeout_ms=1000,
142148
mode=ProjectUptimeSubscriptionMode.AUTO_DETECTED_ACTIVE,
143149
)
144150
uptime_monitor = ProjectUptimeSubscription.objects.get(id=resp.data["id"])
@@ -149,7 +155,7 @@ def test_mode_superadmin(self):
149155
assert uptime_monitor.mode == ProjectUptimeSubscriptionMode.AUTO_DETECTED_ACTIVE
150156
assert uptime_subscription.url == "http://sentry.io"
151157
assert uptime_subscription.interval_seconds == 60
152-
assert uptime_subscription.timeout_ms == DEFAULT_SUBSCRIPTION_TIMEOUT_MS
158+
assert uptime_subscription.timeout_ms == 1000
153159

154160
@with_feature("organizations:uptime-api-create-update")
155161
def test_headers_body_method(self):
@@ -161,6 +167,7 @@ def test_headers_body_method(self):
161167
owner=f"user:{self.user.id}",
162168
url="http://sentry.io",
163169
interval_seconds=60,
170+
timeout_ms=1000,
164171
method="POST",
165172
body='{"key": "value"}',
166173
headers=[["header", "value"]],
@@ -173,7 +180,7 @@ def test_headers_body_method(self):
173180
assert uptime_monitor.mode == ProjectUptimeSubscriptionMode.MANUAL
174181
assert uptime_subscription.url == "http://sentry.io"
175182
assert uptime_subscription.interval_seconds == 60
176-
assert uptime_subscription.timeout_ms == DEFAULT_SUBSCRIPTION_TIMEOUT_MS
183+
assert uptime_subscription.timeout_ms == 1000
177184
assert uptime_subscription.body == '{"key": "value"}'
178185
assert uptime_subscription.headers == [["header", "value"]]
179186

@@ -187,6 +194,7 @@ def test_headers_body_method_already_exists(self):
187194
owner=f"user:{self.user.id}",
188195
url="http://sentry.io",
189196
interval_seconds=60,
197+
timeout_ms=1000,
190198
method="POST",
191199
body='{"key": "value"}',
192200
headers=[["header", "value"]],
@@ -201,6 +209,7 @@ def test_headers_body_method_already_exists(self):
201209
owner=f"user:{self.user.id}",
202210
url="http://sentry.io",
203211
interval_seconds=60,
212+
timeout_ms=1000,
204213
method="POST",
205214
body='{"key": "value"}',
206215
headers=[["header", "value"]],
@@ -216,6 +225,7 @@ def test_headers_body_method_already_exists(self):
216225
owner=f"user:{self.user.id}",
217226
url="http://sentry.io",
218227
interval_seconds=60,
228+
timeout_ms=1000,
219229
method="POST",
220230
body='{"key": "value"}',
221231
headers=[["header", "different value"]],
@@ -235,6 +245,7 @@ def test_headers_invalid_format(self):
235245
owner=f"user:{self.user.id}",
236246
url="http://sentry.io",
237247
interval_seconds=60,
248+
timeout_ms=1000,
238249
method="POST",
239250
body='{"key": "value"}',
240251
headers={"header", "value"},
@@ -254,6 +265,7 @@ def test_size_too_big(self):
254265
owner=f"user:{self.user.id}",
255266
url="http://sentry.io",
256267
interval_seconds=60,
268+
timeout_ms=1000,
257269
method="POST",
258270
body="body" * 250,
259271
headers=[["header", "value"]],
@@ -279,6 +291,7 @@ def test_over_limit(self):
279291
name="test",
280292
url="http://sentry.io",
281293
interval_seconds=60,
294+
timeout_ms=1000,
282295
owner=f"user:{self.user.id}",
283296
)
284297
self.get_error_response(
@@ -288,5 +301,6 @@ def test_over_limit(self):
288301
name="test",
289302
url="http://santry.io",
290303
interval_seconds=60,
304+
timeout_ms=1000,
291305
owner=f"user:{self.user.id}",
292306
)

tests/sentry/uptime/subscriptions/test_subscriptions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ def test(self):
245245
environment=self.environment,
246246
url="https://santry.io",
247247
interval_seconds=60,
248+
timeout_ms=1000,
248249
method="POST",
249250
headers=[("some", "header")],
250251
body="a body",
@@ -286,6 +287,7 @@ def test_removes_old(self):
286287
environment=self.environment,
287288
url="https://santry.io",
288289
interval_seconds=proj_sub.uptime_subscription.interval_seconds,
290+
timeout_ms=1000,
289291
method=proj_sub.uptime_subscription.method,
290292
headers=proj_sub.uptime_subscription.headers,
291293
body=proj_sub.uptime_subscription.body,
@@ -329,6 +331,7 @@ def test_already_exists(self):
329331
environment=self.environment,
330332
url=proj_sub.uptime_subscription.url,
331333
interval_seconds=other_proj_sub.uptime_subscription.interval_seconds,
334+
timeout_ms=1000,
332335
method=other_proj_sub.uptime_subscription.method,
333336
headers=other_proj_sub.uptime_subscription.headers,
334337
body=other_proj_sub.uptime_subscription.body,

0 commit comments

Comments
 (0)