Skip to content

Commit

Permalink
feat(crons): Serialize active incident onto monitor environment (#66989)
Browse files Browse the repository at this point in the history
Serializes an active incident onto each monitor environment which may
contain information about broken detections that happened during that
incident
  • Loading branch information
David Wang authored Mar 21, 2024
1 parent 3188438 commit 96a5851
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 3 deletions.
74 changes: 72 additions & 2 deletions src/sentry/monitors/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,59 @@
from sentry.monitors.validators import IntervalNames

from ..models import Environment
from .models import Monitor, MonitorCheckIn, MonitorEnvironment, MonitorStatus
from .models import (
Monitor,
MonitorCheckIn,
MonitorEnvBrokenDetection,
MonitorEnvironment,
MonitorIncident,
MonitorStatus,
)


class MonitorEnvBrokenDetectionSerializerResponse(TypedDict):
userNotifiedTimestamp: datetime
environmentMutedTimestamp: datetime


@register(MonitorEnvBrokenDetection)
class MonitorEnvBrokenDetectionSerializer(Serializer):
def serialize(self, obj, attrs, user, **kwargs) -> MonitorEnvBrokenDetectionSerializerResponse:
return {
"userNotifiedTimestamp": obj.user_notified_timestamp,
"environmentMutedTimestamp": obj.env_muted_timestamp,
}


class MonitorIncidentSerializerResponse(TypedDict):
startingTimestamp: datetime
resolvingTimestamp: datetime
brokenNotice: MonitorEnvBrokenDetectionSerializerResponse | None


@register(MonitorIncident)
class MonitorIncidentSerializer(Serializer):
def get_attrs(
self, item_list: Sequence[Any], user: Any, **kwargs: Any
) -> MutableMapping[Any, Any]:
broken_detections = list(
MonitorEnvBrokenDetection.objects.filter(monitor_incident__in=item_list)
)
serialized_broken_detections = {
detection.monitor_incident_id: serialized
for serialized, detection in zip(serialize(broken_detections, user), broken_detections)
}
return {
incident: {"broken_detection": serialized_broken_detections.get(incident.id)}
for incident in item_list
}

def serialize(self, obj, attrs, user, **kwargs) -> MonitorIncidentSerializerResponse:
return {
"startingTimestamp": obj.starting_timestamp,
"resolvingTimestamp": obj.resolving_timestamp,
"brokenNotice": attrs["broken_detection"],
}


class MonitorEnvironmentSerializerResponse(TypedDict):
Expand All @@ -22,6 +74,7 @@ class MonitorEnvironmentSerializerResponse(TypedDict):
lastCheckIn: datetime
nextCheckIn: datetime
nextCheckInLatest: datetime
activeIncident: MonitorIncidentSerializerResponse | None


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

active_incidents = list(
MonitorIncident.objects.filter(
monitor_environment__in=item_list,
resolving_checkin=None,
)
)
serialized_incidents = {
incident.monitor_environment_id: serialized_incident
for incident, serialized_incident in zip(
active_incidents, serialize(active_incidents, user)
)
}

return {
monitor_env: {"environment": environments[monitor_env.environment_id]}
monitor_env: {
"environment": environments[monitor_env.environment_id],
"active_incident": serialized_incidents.get(monitor_env.id),
}
for monitor_env in item_list
}

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


Expand Down
52 changes: 51 additions & 1 deletion tests/sentry/monitors/endpoints/test_base_monitor_details.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import timedelta
from datetime import datetime, timedelta, timezone
from unittest.mock import patch

import pytest
Expand All @@ -13,7 +13,9 @@
CheckInStatus,
Monitor,
MonitorCheckIn,
MonitorEnvBrokenDetection,
MonitorEnvironment,
MonitorIncident,
ScheduleType,
)
from sentry.monitors.utils import get_timeout_at
Expand Down Expand Up @@ -69,6 +71,7 @@ def test_filtering_monitor_environment(self):
"lastCheckIn": jungle.last_checkin,
"nextCheckIn": jungle.next_checkin,
"nextCheckInLatest": jungle.next_checkin_latest,
"activeIncident": None,
},
{
"name": prod_env,
Expand All @@ -78,6 +81,7 @@ def test_filtering_monitor_environment(self):
"lastCheckIn": prod.last_checkin,
"nextCheckIn": prod.next_checkin,
"nextCheckInLatest": prod.next_checkin_latest,
"activeIncident": None,
},
]

Expand All @@ -93,6 +97,7 @@ def test_filtering_monitor_environment(self):
"lastCheckIn": prod.last_checkin,
"nextCheckIn": prod.next_checkin,
"nextCheckInLatest": prod.next_checkin_latest,
"activeIncident": None,
}
]

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

def test_with_active_incident_and_detection(self):
monitor = self._create_monitor()
monitor_env = self._create_monitor_environment(monitor)

resp = self.get_success_response(self.organization.slug, monitor.slug)
assert resp.data["environments"][0]["activeIncident"] is None

starting_timestamp = datetime(2023, 12, 15, tzinfo=timezone.utc)
monitor_incident = MonitorIncident.objects.create(
monitor=monitor, monitor_environment=monitor_env, starting_timestamp=starting_timestamp
)
detection_timestamp = datetime(2024, 1, 1, tzinfo=timezone.utc)
MonitorEnvBrokenDetection.objects.create(
monitor_incident=monitor_incident, user_notified_timestamp=detection_timestamp
)

resp = self.get_success_response(self.organization.slug, monitor.slug)
assert resp.data["environments"][0]["activeIncident"] == {
"startingTimestamp": monitor_incident.starting_timestamp,
"resolvingTimestamp": monitor_incident.resolving_timestamp,
"brokenNotice": {
"userNotifiedTimestamp": detection_timestamp,
"environmentMutedTimestamp": None,
},
}

def test_with_active_incident_no_detection(self):
monitor = self._create_monitor()
monitor_env = self._create_monitor_environment(monitor)

resp = self.get_success_response(self.organization.slug, monitor.slug)
assert resp.data["environments"][0]["activeIncident"] is None

starting_timestamp = datetime(2023, 12, 15, tzinfo=timezone.utc)
monitor_incident = MonitorIncident.objects.create(
monitor=monitor, monitor_environment=monitor_env, starting_timestamp=starting_timestamp
)

resp = self.get_success_response(self.organization.slug, monitor.slug)
assert resp.data["environments"][0]["activeIncident"] == {
"startingTimestamp": monitor_incident.starting_timestamp,
"resolvingTimestamp": monitor_incident.resolving_timestamp,
"brokenNotice": None,
}


@freeze_time()
class BaseUpdateMonitorTest(MonitorTestCase):
Expand Down

0 comments on commit 96a5851

Please sign in to comment.