Skip to content

Commit e52e00f

Browse files
wedamijaconstantinius
authored andcommitted
feat(crons): Send detector_id along with the occurrence, when available (#97963)
This starts sending the `detector_id` to the issue platform, so that we can trigger workflows.
1 parent 880ac13 commit e52e00f

File tree

3 files changed

+146
-51
lines changed

3 files changed

+146
-51
lines changed

src/sentry/monitors/logic/incident_occurrence.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
MonitorEnvironment,
2828
MonitorIncident,
2929
)
30+
from sentry.monitors.utils import get_detector_for_monitor
3031
from sentry.utils.arroyo_producer import SingletonProducer
3132
from sentry.utils.kafka_config import get_kafka_producer_cluster_options, get_topic_definition
3233

@@ -144,6 +145,11 @@ def send_incident_occurrence(
144145
if last_successful_checkin:
145146
last_successful_checkin_timestamp = last_successful_checkin.date_added.isoformat()
146147

148+
detector = get_detector_for_monitor(monitor_env.monitor)
149+
evidence_data = {}
150+
if detector:
151+
evidence_data["detector_id"] = detector.id
152+
147153
occurrence = IssueOccurrence(
148154
id=uuid.uuid4().hex,
149155
resource_id=None,
@@ -170,7 +176,7 @@ def send_incident_occurrence(
170176
important=False,
171177
),
172178
],
173-
evidence_data={},
179+
evidence_data=evidence_data,
174180
culprit="",
175181
detection_time=current_timestamp,
176182
level="error",

src/sentry/monitors/utils.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from sentry import audit_log
1010
from sentry.api.serializers.rest_framework.rule import RuleSerializer
1111
from sentry.db.models import BoundedPositiveIntegerField
12+
from sentry.db.postgres.transactions import in_test_hide_transaction_boundary
1213
from sentry.models.group import Group
1314
from sentry.models.project import Project
1415
from sentry.models.rule import Rule, RuleActivity, RuleActivityType, RuleSource
@@ -416,10 +417,11 @@ def ensure_cron_detector(monitor: Monitor):
416417

417418
def get_detector_for_monitor(monitor: Monitor) -> Detector | None:
418419
try:
419-
return Detector.objects.get(
420-
datasource__type=DATA_SOURCE_CRON_MONITOR,
421-
datasource__source_id=str(monitor.id),
422-
datasource__organization_id=monitor.organization_id,
423-
)
420+
with in_test_hide_transaction_boundary():
421+
return Detector.objects.get(
422+
datasource__type=DATA_SOURCE_CRON_MONITOR,
423+
datasource__source_id=str(monitor.id),
424+
datasource__organization_id=monitor.organization_id,
425+
)
424426
except Detector.DoesNotExist:
425427
return None

tests/sentry/monitors/logic/test_incident_occurrence.py

Lines changed: 132 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,13 @@
2525
MonitorStatus,
2626
ScheduleType,
2727
)
28+
from sentry.monitors.utils import ensure_cron_detector, get_detector_for_monitor
2829
from sentry.testutils.cases import TestCase
2930

3031

3132
class IncidentOccurrenceTestCase(TestCase):
32-
@mock.patch("sentry.monitors.logic.incident_occurrence.produce_occurrence_to_kafka")
33-
def test_send_incident_occurrence(
34-
self, mock_produce_occurrence_to_kafka: mock.MagicMock
35-
) -> None:
36-
monitor = Monitor.objects.create(
33+
def build_occurrence_test_data(self):
34+
self.monitor = Monitor.objects.create(
3735
name="test monitor",
3836
organization_id=self.organization.id,
3937
project_id=self.project.id,
@@ -44,51 +42,56 @@ def test_send_incident_occurrence(
4442
"checkin_margin": None,
4543
},
4644
)
47-
monitor_environment = MonitorEnvironment.objects.create(
48-
monitor=monitor,
45+
self.monitor_environment = MonitorEnvironment.objects.create(
46+
monitor=self.monitor,
4947
environment_id=self.environment.id,
5048
status=MonitorStatus.ERROR,
5149
)
5250

53-
successful_checkin = MonitorCheckIn.objects.create(
54-
monitor=monitor,
55-
monitor_environment=monitor_environment,
51+
self.successful_checkin = MonitorCheckIn.objects.create(
52+
monitor=self.monitor,
53+
monitor_environment=self.monitor_environment,
5654
project_id=self.project.id,
5755
status=CheckInStatus.OK,
5856
)
5957

60-
last_checkin = timezone.now()
61-
trace_id = uuid.uuid4()
58+
self.last_checkin = timezone.now()
59+
self.trace_id = uuid.uuid4()
6260

63-
timeout_checkin = MonitorCheckIn.objects.create(
64-
monitor=monitor,
65-
monitor_environment=monitor_environment,
61+
self.timeout_checkin = MonitorCheckIn.objects.create(
62+
monitor=self.monitor,
63+
monitor_environment=self.monitor_environment,
6664
project_id=self.project.id,
6765
status=CheckInStatus.TIMEOUT,
6866
trace_id=uuid.uuid4(),
69-
date_added=last_checkin - timedelta(minutes=1),
67+
date_added=self.last_checkin - timedelta(minutes=1),
7068
)
71-
failed_checkin = MonitorCheckIn.objects.create(
72-
monitor=monitor,
73-
monitor_environment=monitor_environment,
69+
self.failed_checkin = MonitorCheckIn.objects.create(
70+
monitor=self.monitor,
71+
monitor_environment=self.monitor_environment,
7472
project_id=self.project.id,
7573
status=CheckInStatus.ERROR,
76-
trace_id=trace_id,
77-
date_added=last_checkin,
74+
trace_id=self.trace_id,
75+
date_added=self.last_checkin,
7876
)
79-
incident = MonitorIncident.objects.create(
80-
monitor=monitor,
81-
monitor_environment=monitor_environment,
82-
starting_checkin=failed_checkin,
83-
starting_timestamp=last_checkin,
77+
self.incident = MonitorIncident.objects.create(
78+
monitor=self.monitor,
79+
monitor_environment=self.monitor_environment,
80+
starting_checkin=self.failed_checkin,
81+
starting_timestamp=self.last_checkin,
8482
grouphash="abcd",
8583
)
8684

85+
@mock.patch("sentry.monitors.logic.incident_occurrence.produce_occurrence_to_kafka")
86+
def test_send_incident_occurrence(
87+
self, mock_produce_occurrence_to_kafka: mock.MagicMock
88+
) -> None:
89+
self.build_occurrence_test_data()
8790
send_incident_occurrence(
88-
failed_checkin,
89-
[timeout_checkin, failed_checkin],
90-
incident,
91-
last_checkin,
91+
self.failed_checkin,
92+
[self.timeout_checkin, self.failed_checkin],
93+
self.incident,
94+
self.last_checkin,
9295
)
9396

9497
assert mock_produce_occurrence_to_kafka.call_count == 1
@@ -102,8 +105,8 @@ def test_send_incident_occurrence(
102105
occurrence,
103106
**{
104107
"project_id": self.project.id,
105-
"fingerprint": [incident.grouphash],
106-
"issue_title": f"Monitor failure: {monitor.name}",
108+
"fingerprint": [self.incident.grouphash],
109+
"issue_title": f"Monitor failure: {self.monitor.name}",
107110
"subtitle": "Your monitor has reached its failure threshold.",
108111
"resource_id": None,
109112
"evidence_data": {},
@@ -115,12 +118,96 @@ def test_send_incident_occurrence(
115118
},
116119
{
117120
"name": "Environment",
118-
"value": monitor_environment.get_environment().name,
121+
"value": self.monitor_environment.get_environment().name,
122+
"important": False,
123+
},
124+
{
125+
"name": "Last successful check-in",
126+
"value": self.successful_checkin.date_added.isoformat(),
127+
"important": False,
128+
},
129+
],
130+
"type": MonitorIncidentType.type_id,
131+
"level": "error",
132+
"culprit": "",
133+
},
134+
) == dict(occurrence)
135+
136+
assert dict(
137+
event,
138+
**{
139+
"contexts": {
140+
"monitor": {
141+
"status": "error",
142+
"config": self.monitor.config,
143+
"id": str(self.monitor.guid),
144+
"name": self.monitor.name,
145+
"slug": self.monitor.slug,
146+
},
147+
"trace": {
148+
"trace_id": self.trace_id.hex,
149+
"span_id": None,
150+
},
151+
},
152+
"environment": self.monitor_environment.get_environment().name,
153+
"event_id": occurrence["event_id"],
154+
"fingerprint": [self.incident.grouphash],
155+
"platform": "other",
156+
"project_id": self.monitor.project_id,
157+
"sdk": None,
158+
"tags": {
159+
"monitor.id": str(self.monitor.guid),
160+
"monitor.slug": str(self.monitor.slug),
161+
"monitor.incident": str(self.incident.id),
162+
},
163+
},
164+
) == dict(event)
165+
166+
@mock.patch("sentry.monitors.logic.incident_occurrence.produce_occurrence_to_kafka")
167+
def test_send_incident_occurrence_detector(
168+
self, mock_produce_occurrence_to_kafka: mock.MagicMock
169+
) -> None:
170+
self.build_occurrence_test_data()
171+
ensure_cron_detector(self.monitor)
172+
send_incident_occurrence(
173+
self.failed_checkin,
174+
[self.timeout_checkin, self.failed_checkin],
175+
self.incident,
176+
self.last_checkin,
177+
)
178+
179+
assert mock_produce_occurrence_to_kafka.call_count == 1
180+
kwargs = mock_produce_occurrence_to_kafka.call_args.kwargs
181+
182+
occurrence = kwargs["occurrence"]
183+
event = kwargs["event_data"]
184+
occurrence = occurrence.to_dict()
185+
186+
detector = get_detector_for_monitor(self.monitor)
187+
assert detector
188+
assert dict(
189+
occurrence,
190+
**{
191+
"project_id": self.project.id,
192+
"fingerprint": [self.incident.grouphash],
193+
"issue_title": f"Monitor failure: {self.monitor.name}",
194+
"subtitle": "Your monitor has reached its failure threshold.",
195+
"resource_id": None,
196+
"evidence_data": {"detector_id": detector.id},
197+
"evidence_display": [
198+
{
199+
"name": "Failure reason",
200+
"value": "1 timeout and 1 error check-ins detected",
201+
"important": True,
202+
},
203+
{
204+
"name": "Environment",
205+
"value": self.monitor_environment.get_environment().name,
119206
"important": False,
120207
},
121208
{
122209
"name": "Last successful check-in",
123-
"value": successful_checkin.date_added.isoformat(),
210+
"value": self.successful_checkin.date_added.isoformat(),
124211
"important": False,
125212
},
126213
],
@@ -136,26 +223,26 @@ def test_send_incident_occurrence(
136223
"contexts": {
137224
"monitor": {
138225
"status": "error",
139-
"config": monitor.config,
140-
"id": str(monitor.guid),
141-
"name": monitor.name,
142-
"slug": monitor.slug,
226+
"config": self.monitor.config,
227+
"id": str(self.monitor.guid),
228+
"name": self.monitor.name,
229+
"slug": self.monitor.slug,
143230
},
144231
"trace": {
145-
"trace_id": trace_id.hex,
232+
"trace_id": self.trace_id.hex,
146233
"span_id": None,
147234
},
148235
},
149-
"environment": monitor_environment.get_environment().name,
236+
"environment": self.monitor_environment.get_environment().name,
150237
"event_id": occurrence["event_id"],
151-
"fingerprint": [incident.grouphash],
238+
"fingerprint": [self.incident.grouphash],
152239
"platform": "other",
153-
"project_id": monitor.project_id,
240+
"project_id": self.monitor.project_id,
154241
"sdk": None,
155242
"tags": {
156-
"monitor.id": str(monitor.guid),
157-
"monitor.slug": str(monitor.slug),
158-
"monitor.incident": str(incident.id),
243+
"monitor.id": str(self.monitor.guid),
244+
"monitor.slug": str(self.monitor.slug),
245+
"monitor.incident": str(self.incident.id),
159246
},
160247
},
161248
) == dict(event)

0 commit comments

Comments
 (0)