Skip to content

Commit 96a5851

Browse files
author
David Wang
authored
feat(crons): Serialize active incident onto monitor environment (#66989)
Serializes an active incident onto each monitor environment which may contain information about broken detections that happened during that incident
1 parent 3188438 commit 96a5851

File tree

2 files changed

+123
-3
lines changed

2 files changed

+123
-3
lines changed

src/sentry/monitors/serializers.py

+72-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,59 @@
1111
from sentry.monitors.validators import IntervalNames
1212

1313
from ..models import Environment
14-
from .models import Monitor, MonitorCheckIn, MonitorEnvironment, MonitorStatus
14+
from .models import (
15+
Monitor,
16+
MonitorCheckIn,
17+
MonitorEnvBrokenDetection,
18+
MonitorEnvironment,
19+
MonitorIncident,
20+
MonitorStatus,
21+
)
22+
23+
24+
class MonitorEnvBrokenDetectionSerializerResponse(TypedDict):
25+
userNotifiedTimestamp: datetime
26+
environmentMutedTimestamp: datetime
27+
28+
29+
@register(MonitorEnvBrokenDetection)
30+
class MonitorEnvBrokenDetectionSerializer(Serializer):
31+
def serialize(self, obj, attrs, user, **kwargs) -> MonitorEnvBrokenDetectionSerializerResponse:
32+
return {
33+
"userNotifiedTimestamp": obj.user_notified_timestamp,
34+
"environmentMutedTimestamp": obj.env_muted_timestamp,
35+
}
36+
37+
38+
class MonitorIncidentSerializerResponse(TypedDict):
39+
startingTimestamp: datetime
40+
resolvingTimestamp: datetime
41+
brokenNotice: MonitorEnvBrokenDetectionSerializerResponse | None
42+
43+
44+
@register(MonitorIncident)
45+
class MonitorIncidentSerializer(Serializer):
46+
def get_attrs(
47+
self, item_list: Sequence[Any], user: Any, **kwargs: Any
48+
) -> MutableMapping[Any, Any]:
49+
broken_detections = list(
50+
MonitorEnvBrokenDetection.objects.filter(monitor_incident__in=item_list)
51+
)
52+
serialized_broken_detections = {
53+
detection.monitor_incident_id: serialized
54+
for serialized, detection in zip(serialize(broken_detections, user), broken_detections)
55+
}
56+
return {
57+
incident: {"broken_detection": serialized_broken_detections.get(incident.id)}
58+
for incident in item_list
59+
}
60+
61+
def serialize(self, obj, attrs, user, **kwargs) -> MonitorIncidentSerializerResponse:
62+
return {
63+
"startingTimestamp": obj.starting_timestamp,
64+
"resolvingTimestamp": obj.resolving_timestamp,
65+
"brokenNotice": attrs["broken_detection"],
66+
}
1567

1668

1769
class MonitorEnvironmentSerializerResponse(TypedDict):
@@ -22,6 +74,7 @@ class MonitorEnvironmentSerializerResponse(TypedDict):
2274
lastCheckIn: datetime
2375
nextCheckIn: datetime
2476
nextCheckInLatest: datetime
77+
activeIncident: MonitorIncidentSerializerResponse | None
2578

2679

2780
@register(MonitorEnvironment)
@@ -34,8 +87,24 @@ def get_attrs(
3487
]
3588
environments = {env.id: env for env in Environment.objects.filter(id__in=env_ids)}
3689

90+
active_incidents = list(
91+
MonitorIncident.objects.filter(
92+
monitor_environment__in=item_list,
93+
resolving_checkin=None,
94+
)
95+
)
96+
serialized_incidents = {
97+
incident.monitor_environment_id: serialized_incident
98+
for incident, serialized_incident in zip(
99+
active_incidents, serialize(active_incidents, user)
100+
)
101+
}
102+
37103
return {
38-
monitor_env: {"environment": environments[monitor_env.environment_id]}
104+
monitor_env: {
105+
"environment": environments[monitor_env.environment_id],
106+
"active_incident": serialized_incidents.get(monitor_env.id),
107+
}
39108
for monitor_env in item_list
40109
}
41110

@@ -48,6 +117,7 @@ def serialize(self, obj, attrs, user, **kwargs) -> MonitorEnvironmentSerializerR
48117
"lastCheckIn": obj.last_checkin,
49118
"nextCheckIn": obj.next_checkin,
50119
"nextCheckInLatest": obj.next_checkin_latest,
120+
"activeIncident": attrs["active_incident"],
51121
}
52122

53123

tests/sentry/monitors/endpoints/test_base_monitor_details.py

+51-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import timedelta
1+
from datetime import datetime, timedelta, timezone
22
from unittest.mock import patch
33

44
import pytest
@@ -13,7 +13,9 @@
1313
CheckInStatus,
1414
Monitor,
1515
MonitorCheckIn,
16+
MonitorEnvBrokenDetection,
1617
MonitorEnvironment,
18+
MonitorIncident,
1719
ScheduleType,
1820
)
1921
from sentry.monitors.utils import get_timeout_at
@@ -69,6 +71,7 @@ def test_filtering_monitor_environment(self):
6971
"lastCheckIn": jungle.last_checkin,
7072
"nextCheckIn": jungle.next_checkin,
7173
"nextCheckInLatest": jungle.next_checkin_latest,
74+
"activeIncident": None,
7275
},
7376
{
7477
"name": prod_env,
@@ -78,6 +81,7 @@ def test_filtering_monitor_environment(self):
7881
"lastCheckIn": prod.last_checkin,
7982
"nextCheckIn": prod.next_checkin,
8083
"nextCheckInLatest": prod.next_checkin_latest,
84+
"activeIncident": None,
8185
},
8286
]
8387

@@ -93,6 +97,7 @@ def test_filtering_monitor_environment(self):
9397
"lastCheckIn": prod.last_checkin,
9498
"nextCheckIn": prod.next_checkin,
9599
"nextCheckInLatest": prod.next_checkin_latest,
100+
"activeIncident": None,
96101
}
97102
]
98103

@@ -108,6 +113,51 @@ def test_expand_issue_alert_rule(self):
108113
assert issue_alert_rule is not None
109114
assert issue_alert_rule["environment"] is not None
110115

116+
def test_with_active_incident_and_detection(self):
117+
monitor = self._create_monitor()
118+
monitor_env = self._create_monitor_environment(monitor)
119+
120+
resp = self.get_success_response(self.organization.slug, monitor.slug)
121+
assert resp.data["environments"][0]["activeIncident"] is None
122+
123+
starting_timestamp = datetime(2023, 12, 15, tzinfo=timezone.utc)
124+
monitor_incident = MonitorIncident.objects.create(
125+
monitor=monitor, monitor_environment=monitor_env, starting_timestamp=starting_timestamp
126+
)
127+
detection_timestamp = datetime(2024, 1, 1, tzinfo=timezone.utc)
128+
MonitorEnvBrokenDetection.objects.create(
129+
monitor_incident=monitor_incident, user_notified_timestamp=detection_timestamp
130+
)
131+
132+
resp = self.get_success_response(self.organization.slug, monitor.slug)
133+
assert resp.data["environments"][0]["activeIncident"] == {
134+
"startingTimestamp": monitor_incident.starting_timestamp,
135+
"resolvingTimestamp": monitor_incident.resolving_timestamp,
136+
"brokenNotice": {
137+
"userNotifiedTimestamp": detection_timestamp,
138+
"environmentMutedTimestamp": None,
139+
},
140+
}
141+
142+
def test_with_active_incident_no_detection(self):
143+
monitor = self._create_monitor()
144+
monitor_env = self._create_monitor_environment(monitor)
145+
146+
resp = self.get_success_response(self.organization.slug, monitor.slug)
147+
assert resp.data["environments"][0]["activeIncident"] is None
148+
149+
starting_timestamp = datetime(2023, 12, 15, tzinfo=timezone.utc)
150+
monitor_incident = MonitorIncident.objects.create(
151+
monitor=monitor, monitor_environment=monitor_env, starting_timestamp=starting_timestamp
152+
)
153+
154+
resp = self.get_success_response(self.organization.slug, monitor.slug)
155+
assert resp.data["environments"][0]["activeIncident"] == {
156+
"startingTimestamp": monitor_incident.starting_timestamp,
157+
"resolvingTimestamp": monitor_incident.resolving_timestamp,
158+
"brokenNotice": None,
159+
}
160+
111161

112162
@freeze_time()
113163
class BaseUpdateMonitorTest(MonitorTestCase):

0 commit comments

Comments
 (0)