Skip to content

Commit 4d39e60

Browse files
fix(workflow-engine): Link cron monitor detectors to shadow alert workflows (#103919)
When creating a cron monitor with alert notifications via the old APIs, a "shadow" alert rule is created which generates a Workflow, but the DetectorWorkflow link between the cron Detector and the Workflow was not being created. Changes: - Modified IssueAlertMigrator._create_detector_lookups() to find cron detectors by extracting monitor.slug from rule conditions and querying for the associated detector via DataSource - Removed conditional check that was skipping _connect_default_detectors() for CRON_MONITOR rules, allowing detector linking to happen - Used in_test_hide_transaction_boundary() to safely query across databases Part of [NEW-593: Cron Monitor Alerts are not linked to monitor when created via the old flow](https://linear.app/getsentry/issue/NEW-593/cron-monitor-alerts-are-not-linked-to-monitor-when-created-via-the-old)
1 parent aff01be commit 4d39e60

File tree

2 files changed

+67
-3
lines changed

2 files changed

+67
-3
lines changed

src/sentry/workflow_engine/migration_helpers/issue_alert_migration.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
from typing import Any
33

44
from sentry.constants import ObjectStatus
5+
from sentry.db.postgres.transactions import in_test_hide_transaction_boundary
56
from sentry.grouping.grouptype import ErrorGroupType
67
from sentry.models.rule import Rule, RuleSource
78
from sentry.models.rulesnooze import RuleSnooze
9+
from sentry.monitors.models import Monitor
10+
from sentry.monitors.types import DATA_SOURCE_CRON_MONITOR
811
from sentry.rules.conditions.event_frequency import EventUniqueUserFrequencyConditionWithConditions
912
from sentry.rules.conditions.every_event import EveryEventCondition
1013
from sentry.rules.processing.processor import split_conditions_and_filters
@@ -76,7 +79,32 @@ def run(self) -> Workflow:
7679

7780
def _create_detector_lookups(self) -> list[Detector | None]:
7881
if self.rule.source == RuleSource.CRON_MONITOR:
79-
return [None, None]
82+
# Find the cron detector that was created before the rule
83+
monitor_slug = None
84+
for condition in self.data.get("conditions", []):
85+
if condition.get("key") == "monitor.slug":
86+
monitor_slug = condition.get("value")
87+
break
88+
89+
if not monitor_slug:
90+
return [None]
91+
92+
try:
93+
with in_test_hide_transaction_boundary():
94+
monitor = Monitor.objects.get(
95+
slug=monitor_slug,
96+
organization_id=self.organization.id,
97+
)
98+
detector = Detector.objects.get(
99+
datasource__type=DATA_SOURCE_CRON_MONITOR,
100+
datasource__source_id=str(monitor.id),
101+
datasource__organization_id=self.organization.id,
102+
)
103+
return [detector]
104+
except (Monitor.DoesNotExist, Detector.DoesNotExist):
105+
pass
106+
107+
return [None]
80108

81109
if self.is_dry_run:
82110
error_detector = Detector.objects.filter(
@@ -247,8 +275,7 @@ def _create_workflow_and_lookup(
247275
else:
248276
workflow = Workflow.objects.create(**kwargs)
249277
workflow.update(date_added=self.rule.date_added)
250-
if not self.rule.source == RuleSource.CRON_MONITOR:
251-
self._connect_default_detectors(workflow=workflow)
278+
self._connect_default_detectors(workflow=workflow)
252279
AlertRuleWorkflow.objects.create(rule_id=self.rule.id, workflow=workflow)
253280

254281
return workflow

tests/sentry/workflow_engine/migration_helpers/test_issue_alert_migration.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from sentry.grouping.grouptype import ErrorGroupType
88
from sentry.models.rule import Rule, RuleSource
99
from sentry.models.rulesnooze import RuleSnooze
10+
from sentry.monitors.models import Monitor, ScheduleType
11+
from sentry.monitors.utils import ensure_cron_detector, get_detector_for_monitor
1012
from sentry.rules.age import AgeComparisonType
1113
from sentry.rules.conditions.event_frequency import (
1214
ComparisonType,
@@ -439,6 +441,41 @@ def test_run__cron_rule(self) -> None:
439441
assert AlertRuleWorkflow.objects.filter(rule_id=self.issue_alert.id).exists()
440442
assert not DetectorWorkflow.objects.filter(workflow=workflow).exists()
441443

444+
def test_run__cron_rule_with_monitor(self) -> None:
445+
"""
446+
Cron rule WITH monitor.slug filter should be connected to the cron detector
447+
"""
448+
monitor = Monitor.objects.create(
449+
organization_id=self.organization.id,
450+
project_id=self.project.id,
451+
name="Test Monitor",
452+
slug="test-monitor",
453+
config={"schedule_type": ScheduleType.CRONTAB, "schedule": "0 * * * *"},
454+
)
455+
ensure_cron_detector(monitor)
456+
457+
# Update rule to be cron monitor source with monitor.slug filter
458+
self.issue_alert.source = RuleSource.CRON_MONITOR
459+
self.issue_alert.data["conditions"].append(
460+
{
461+
"id": "sentry.rules.filters.tagged_event.TaggedEventFilter",
462+
"key": "monitor.slug",
463+
"match": "eq",
464+
"value": "test-monitor",
465+
}
466+
)
467+
self.issue_alert.save()
468+
469+
workflow = IssueAlertMigrator(self.issue_alert, self.user.id).run()
470+
471+
# Verify workflow created
472+
assert AlertRuleWorkflow.objects.filter(rule_id=self.issue_alert.id).exists()
473+
474+
# Verify detector is linked to workflow
475+
detector = get_detector_for_monitor(monitor)
476+
assert detector is not None
477+
assert DetectorWorkflow.objects.filter(detector=detector, workflow=workflow).exists()
478+
442479
def test_dry_run(self) -> None:
443480
IssueAlertMigrator(self.issue_alert, self.user.id, is_dry_run=True).run()
444481

0 commit comments

Comments
 (0)