Skip to content

Commit 1d46292

Browse files
authored
feat(crons): Add get_detector_for_monitor function (#97547)
Add a simple function to get the detector associated with a cron monitor, if one exists.
1 parent 65062c8 commit 1d46292

File tree

4 files changed

+64
-68
lines changed

4 files changed

+64
-68
lines changed

src/sentry/monitors/utils.py

Lines changed: 13 additions & 2 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.issues.grouptype import MonitorIncidentType
1213
from sentry.models.group import Group
1314
from sentry.models.project import Project
1415
from sentry.models.rule import Rule, RuleActivity, RuleActivityType, RuleSource
@@ -392,7 +393,6 @@ def update_issue_alert_rule(
392393

393394

394395
def ensure_cron_detector(monitor: Monitor):
395-
from sentry.issues.grouptype import MonitorCheckInFailure
396396

397397
try:
398398
with atomic_transaction(using=router.db_for_write(DataSource)):
@@ -403,7 +403,7 @@ def ensure_cron_detector(monitor: Monitor):
403403
)
404404
if created:
405405
detector = Detector.objects.create(
406-
type=MonitorCheckInFailure.slug,
406+
type=MonitorIncidentType.slug,
407407
project_id=monitor.project_id,
408408
name=monitor.name,
409409
owner_user_id=monitor.owner_user_id,
@@ -412,3 +412,14 @@ def ensure_cron_detector(monitor: Monitor):
412412
DataSourceDetector.objects.create(data_source=data_source, detector=detector)
413413
except Exception:
414414
logger.exception("Error creating cron detector")
415+
416+
417+
def get_detector_for_monitor(monitor: Monitor) -> Detector | None:
418+
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+
)
424+
except Detector.DoesNotExist:
425+
return None

tests/sentry/monitors/consumers/test_monitor_consumer.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@
2929
ScheduleType,
3030
)
3131
from sentry.monitors.processing_errors.errors import ProcessingErrorsException, ProcessingErrorType
32-
from sentry.monitors.types import DATA_SOURCE_CRON_MONITOR, CheckinItem
32+
from sentry.monitors.types import CheckinItem
33+
from sentry.monitors.utils import get_detector_for_monitor
3334
from sentry.testutils.asserts import assert_org_audit_log_exists
3435
from sentry.testutils.cases import TestCase
3536
from sentry.testutils.helpers.options import override_options
3637
from sentry.testutils.outbox import outbox_runner
3738
from sentry.utils import json
3839
from sentry.utils.outcomes import Outcome
39-
from sentry.workflow_engine.models import Detector
4040

4141

4242
class ExpectNoProcessingError:
@@ -573,10 +573,7 @@ def test_monitor_create(self) -> None:
573573
monitor_environment.next_checkin_latest
574574
== monitor_environment.monitor.get_next_expected_checkin_latest(checkin.date_added)
575575
)
576-
assert Detector.objects.filter(
577-
datasource__type=DATA_SOURCE_CRON_MONITOR,
578-
datasource__source_id=str(monitor_environment.monitor_id),
579-
).exists()
576+
assert get_detector_for_monitor(monitor_environment.monitor) is not None
580577

581578
def test_monitor_create_owner(self) -> None:
582579
self.send_checkin(

tests/sentry/monitors/endpoints/test_organization_monitor_index.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212
from sentry.constants import ObjectStatus
1313
from sentry.models.rule import Rule, RuleSource
1414
from sentry.monitors.models import Monitor, MonitorStatus, ScheduleType
15-
from sentry.monitors.types import DATA_SOURCE_CRON_MONITOR
15+
from sentry.monitors.utils import get_detector_for_monitor
1616
from sentry.quotas.base import SeatAssignmentResult
1717
from sentry.slug.errors import DEFAULT_SLUG_ERROR_MESSAGE
1818
from sentry.testutils.asserts import assert_org_audit_log_exists
1919
from sentry.testutils.cases import MonitorTestCase
2020
from sentry.testutils.outbox import outbox_runner
2121
from sentry.utils.outcomes import Outcome
22-
from sentry.workflow_engine.models import Detector
2322

2423

2524
class ListOrganizationMonitorsTest(MonitorTestCase):
@@ -404,11 +403,7 @@ def test_simple(self, mock_record: MagicMock) -> None:
404403
data={"upsert": False, **monitor.get_audit_log_data()},
405404
)
406405

407-
assert Detector.objects.filter(
408-
datasource__type=DATA_SOURCE_CRON_MONITOR,
409-
datasource__source_id=str(monitor.id),
410-
).exists()
411-
406+
assert get_detector_for_monitor(monitor) is not None
412407
self.project.refresh_from_db()
413408
assert self.project.flags.has_cron_monitors
414409

tests/sentry/monitors/test_utils.py

Lines changed: 46 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
from sentry.issues.grouptype import MonitorIncidentType
66
from sentry.monitors.types import DATA_SOURCE_CRON_MONITOR
7-
from sentry.monitors.utils import ensure_cron_detector
7+
from sentry.monitors.utils import ensure_cron_detector, get_detector_for_monitor
88
from sentry.testutils.cases import TestCase
9-
from sentry.workflow_engine.models import DataSource, DataSourceDetector, Detector
9+
from sentry.workflow_engine.models import DataSource, Detector
1010

1111

1212
class EnsureCronDetectorTest(TestCase):
@@ -15,66 +15,24 @@ def setUp(self):
1515
self.monitor = self.create_monitor(owner_user_id=None)
1616

1717
def test_creates_data_source_and_detector_for_new_monitor(self):
18-
assert not DataSource.objects.filter(
19-
type=DATA_SOURCE_CRON_MONITOR,
20-
organization_id=self.monitor.organization_id,
21-
source_id=str(self.monitor.id),
22-
).exists()
23-
18+
assert not get_detector_for_monitor(self.monitor)
2419
ensure_cron_detector(self.monitor)
25-
data_source = DataSource.objects.get(
26-
type=DATA_SOURCE_CRON_MONITOR,
27-
organization_id=self.monitor.organization_id,
28-
source_id=str(self.monitor.id),
29-
)
30-
assert data_source is not None
31-
detector = Detector.objects.get(
32-
type=MonitorIncidentType.slug,
33-
project_id=self.monitor.project_id,
34-
name=self.monitor.name,
35-
)
20+
detector = get_detector_for_monitor(self.monitor)
3621
assert detector is not None
22+
assert detector.type == "monitor_check_in_failure"
23+
assert detector.project_id == self.monitor.project_id
24+
assert detector.name == self.monitor.name
3725
assert detector.owner_user_id == self.monitor.owner_user_id
3826
assert detector.owner_team_id == self.monitor.owner_team_id
39-
assert DataSourceDetector.objects.filter(
40-
data_source=data_source,
41-
detector=detector,
42-
).exists()
4327

4428
def test_idempotent_for_existing_data_source(self):
4529
ensure_cron_detector(self.monitor)
46-
data_source = DataSource.objects.get(
47-
type=DATA_SOURCE_CRON_MONITOR,
48-
organization_id=self.monitor.organization_id,
49-
source_id=str(self.monitor.id),
50-
)
51-
detector = Detector.objects.get(
52-
type=MonitorIncidentType.slug,
53-
project_id=self.monitor.project_id,
54-
name=self.monitor.name,
55-
)
56-
link = DataSourceDetector.objects.get(
57-
data_source=data_source,
58-
detector=detector,
59-
)
30+
detector = get_detector_for_monitor(self.monitor)
31+
assert detector
6032
ensure_cron_detector(self.monitor)
61-
data_source_after = DataSource.objects.get(
62-
type=DATA_SOURCE_CRON_MONITOR,
63-
organization_id=self.monitor.organization_id,
64-
source_id=str(self.monitor.id),
65-
)
66-
detector_after = Detector.objects.get(
67-
type=MonitorIncidentType.slug,
68-
project_id=self.monitor.project_id,
69-
name=self.monitor.name,
70-
)
71-
link_after = DataSourceDetector.objects.get(
72-
data_source=data_source,
73-
detector=detector,
74-
)
75-
assert data_source.id == data_source_after.id
33+
detector_after = get_detector_for_monitor(self.monitor)
34+
assert detector_after is not None
7635
assert detector.id == detector_after.id
77-
assert link.id == link_after.id
7836

7937
def test_with_owner_user(self):
8038
self.monitor.owner_user_id = self.user.id
@@ -118,3 +76,38 @@ def test_atomic_transaction_rollback(self):
11876
assert not DataSource.objects.filter(
11977
type=DATA_SOURCE_CRON_MONITOR, source_id=str(self.monitor.id)
12078
).exists()
79+
80+
81+
class GetDetectorForMonitorTest(TestCase):
82+
def setUp(self):
83+
super().setUp()
84+
self.monitor = self.create_monitor()
85+
86+
def test_returns_none_when_no_detector_exists(self):
87+
detector = get_detector_for_monitor(self.monitor)
88+
assert detector is None
89+
90+
def test_returns_detector_when_exists(self):
91+
ensure_cron_detector(self.monitor)
92+
93+
detector = get_detector_for_monitor(self.monitor)
94+
assert detector is not None
95+
assert detector.type == "monitor_check_in_failure"
96+
assert detector.project_id == self.monitor.project_id
97+
assert detector.name == self.monitor.name
98+
99+
def test_returns_correct_detector_for_specific_monitor(self):
100+
monitor1 = self.monitor
101+
monitor2 = self.create_monitor(name="Monitor 2")
102+
103+
ensure_cron_detector(monitor1)
104+
ensure_cron_detector(monitor2)
105+
106+
detector1 = get_detector_for_monitor(monitor1)
107+
detector2 = get_detector_for_monitor(monitor2)
108+
109+
assert detector1 is not None
110+
assert detector2 is not None
111+
assert detector1.id != detector2.id
112+
assert detector1.name == monitor1.name
113+
assert detector2.name == monitor2.name

0 commit comments

Comments
 (0)