Skip to content

Commit 526a059

Browse files
authored
feat(alerts): ACI dual write alert rule helpers (#82400)
A smaller chunk of #81953 that creates the helper methods to migrate the `AlertRule` and not the `AlertRuleTrigger` or `AlertRuleTriggerAction` just yet.
1 parent bac442e commit 526a059

File tree

2 files changed

+239
-0
lines changed

2 files changed

+239
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
from sentry.incidents.grouptype import MetricAlertFire
2+
from sentry.incidents.models.alert_rule import AlertRule
3+
from sentry.snuba.models import QuerySubscription, SnubaQuery
4+
from sentry.users.services.user import RpcUser
5+
from sentry.workflow_engine.models import (
6+
AlertRuleDetector,
7+
AlertRuleWorkflow,
8+
DataConditionGroup,
9+
DataSource,
10+
Detector,
11+
DetectorState,
12+
DetectorWorkflow,
13+
Workflow,
14+
WorkflowDataConditionGroup,
15+
)
16+
from sentry.workflow_engine.types import DetectorPriorityLevel
17+
18+
19+
def create_metric_alert_lookup_tables(
20+
alert_rule: AlertRule,
21+
detector: Detector,
22+
workflow: Workflow,
23+
data_source: DataSource,
24+
data_condition_group: DataConditionGroup,
25+
) -> tuple[AlertRuleDetector, AlertRuleWorkflow, DetectorWorkflow, WorkflowDataConditionGroup]:
26+
alert_rule_detector = AlertRuleDetector.objects.create(alert_rule=alert_rule, detector=detector)
27+
alert_rule_workflow = AlertRuleWorkflow.objects.create(alert_rule=alert_rule, workflow=workflow)
28+
detector_workflow = DetectorWorkflow.objects.create(detector=detector, workflow=workflow)
29+
workflow_data_condition_group = WorkflowDataConditionGroup.objects.create(
30+
condition_group=data_condition_group, workflow=workflow
31+
)
32+
return (
33+
alert_rule_detector,
34+
alert_rule_workflow,
35+
detector_workflow,
36+
workflow_data_condition_group,
37+
)
38+
39+
40+
def create_data_source(
41+
organization_id: int, snuba_query: SnubaQuery | None = None
42+
) -> DataSource | None:
43+
if not snuba_query:
44+
return None
45+
46+
try:
47+
query_subscription = QuerySubscription.objects.get(snuba_query=snuba_query.id)
48+
except QuerySubscription.DoesNotExist:
49+
return None
50+
51+
return DataSource.objects.create(
52+
organization_id=organization_id,
53+
query_id=query_subscription.id,
54+
type="snuba_query_subscription",
55+
)
56+
57+
58+
def create_data_condition_group(organization_id: int) -> DataConditionGroup:
59+
return DataConditionGroup.objects.create(
60+
logic_type=DataConditionGroup.Type.ANY,
61+
organization_id=organization_id,
62+
)
63+
64+
65+
def create_workflow(
66+
name: str,
67+
organization_id: int,
68+
data_condition_group: DataConditionGroup,
69+
user: RpcUser | None = None,
70+
) -> Workflow:
71+
return Workflow.objects.create(
72+
name=name,
73+
organization_id=organization_id,
74+
when_condition_group=data_condition_group,
75+
enabled=True,
76+
created_by_id=user.id if user else None,
77+
)
78+
79+
80+
def create_detector(
81+
alert_rule: AlertRule,
82+
project_id: int,
83+
data_condition_group: DataConditionGroup,
84+
user: RpcUser | None = None,
85+
) -> Detector:
86+
return Detector.objects.create(
87+
project_id=project_id,
88+
enabled=True,
89+
created_by_id=user.id if user else None,
90+
name=alert_rule.name,
91+
workflow_condition_group=data_condition_group,
92+
type=MetricAlertFire.slug,
93+
description=alert_rule.description,
94+
owner_user_id=alert_rule.user_id,
95+
owner_team=alert_rule.team,
96+
config={ # TODO create a schema
97+
"threshold_period": alert_rule.threshold_period,
98+
"sensitivity": alert_rule.sensitivity,
99+
"seasonality": alert_rule.seasonality,
100+
"comparison_delta": alert_rule.comparison_delta,
101+
},
102+
)
103+
104+
105+
def migrate_alert_rule(
106+
alert_rule: AlertRule,
107+
user: RpcUser | None = None,
108+
) -> (
109+
tuple[
110+
DataSource,
111+
DataConditionGroup,
112+
Workflow,
113+
Detector,
114+
DetectorState,
115+
AlertRuleDetector,
116+
AlertRuleWorkflow,
117+
DetectorWorkflow,
118+
WorkflowDataConditionGroup,
119+
]
120+
| None
121+
):
122+
organization_id = alert_rule.organization_id
123+
project = alert_rule.projects.first()
124+
if not project:
125+
return None
126+
127+
data_source = create_data_source(organization_id, alert_rule.snuba_query)
128+
if not data_source:
129+
return None
130+
131+
data_condition_group = create_data_condition_group(organization_id)
132+
workflow = create_workflow(alert_rule.name, organization_id, data_condition_group, user)
133+
detector = create_detector(alert_rule, project.id, data_condition_group, user)
134+
135+
data_source.detectors.set([detector])
136+
detector_state = DetectorState.objects.create(
137+
detector=detector,
138+
active=False,
139+
state=DetectorPriorityLevel.OK,
140+
)
141+
alert_rule_detector, alert_rule_workflow, detector_workflow, workflow_data_condition_group = (
142+
create_metric_alert_lookup_tables(
143+
alert_rule, detector, workflow, data_source, data_condition_group
144+
)
145+
)
146+
return (
147+
data_source,
148+
data_condition_group,
149+
workflow,
150+
detector,
151+
detector_state,
152+
alert_rule_detector,
153+
alert_rule_workflow,
154+
detector_workflow,
155+
workflow_data_condition_group,
156+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from sentry.incidents.grouptype import MetricAlertFire
2+
from sentry.snuba.models import QuerySubscription
3+
from sentry.testutils.cases import APITestCase
4+
from sentry.users.services.user.service import user_service
5+
from sentry.workflow_engine.migration_helpers.alert_rule import migrate_alert_rule
6+
from sentry.workflow_engine.models import (
7+
AlertRuleDetector,
8+
AlertRuleWorkflow,
9+
DataSource,
10+
DataSourceDetector,
11+
Detector,
12+
DetectorState,
13+
DetectorWorkflow,
14+
Workflow,
15+
WorkflowDataConditionGroup,
16+
)
17+
from sentry.workflow_engine.types import DetectorPriorityLevel
18+
19+
20+
class AlertRuleMigrationHelpersTest(APITestCase):
21+
def setUp(self):
22+
self.metric_alert = self.create_alert_rule()
23+
self.rpc_user = user_service.get_user(user_id=self.user.id)
24+
25+
def test_create_metric_alert(self):
26+
"""
27+
Test that when we call the helper methods we create all the ACI models correctly for an alert rule
28+
"""
29+
migrate_alert_rule(self.metric_alert, self.rpc_user)
30+
31+
alert_rule_workflow = AlertRuleWorkflow.objects.get(alert_rule=self.metric_alert)
32+
alert_rule_detector = AlertRuleDetector.objects.get(alert_rule=self.metric_alert)
33+
34+
workflow = Workflow.objects.get(id=alert_rule_workflow.workflow.id)
35+
assert workflow.name == self.metric_alert.name
36+
assert self.metric_alert.organization
37+
assert workflow.organization_id == self.metric_alert.organization.id
38+
detector = Detector.objects.get(id=alert_rule_detector.detector.id)
39+
assert detector.name == self.metric_alert.name
40+
assert detector.project_id == self.project.id
41+
assert detector.enabled is True
42+
assert detector.description == self.metric_alert.description
43+
assert detector.owner_user_id == self.metric_alert.user_id
44+
assert detector.owner_team == self.metric_alert.team
45+
assert detector.type == MetricAlertFire.slug
46+
assert detector.config == {
47+
"threshold_period": self.metric_alert.threshold_period,
48+
"sensitivity": None,
49+
"seasonality": None,
50+
"comparison_delta": None,
51+
}
52+
53+
detector_workflow = DetectorWorkflow.objects.get(detector=detector)
54+
assert detector_workflow.workflow == workflow
55+
56+
workflow_data_condition_group = WorkflowDataConditionGroup.objects.get(workflow=workflow)
57+
assert workflow_data_condition_group.condition_group == workflow.when_condition_group
58+
59+
assert self.metric_alert.snuba_query
60+
query_subscription = QuerySubscription.objects.get(
61+
snuba_query=self.metric_alert.snuba_query.id
62+
)
63+
data_source = DataSource.objects.get(
64+
organization_id=self.metric_alert.organization_id, query_id=query_subscription.id
65+
)
66+
assert data_source.type == "snuba_query_subscription"
67+
detector_state = DetectorState.objects.get(detector=detector)
68+
assert detector_state.active is False
69+
assert detector_state.state == str(DetectorPriorityLevel.OK.value)
70+
71+
data_source_detector = DataSourceDetector.objects.get(data_source=data_source)
72+
assert data_source_detector.detector == detector
73+
74+
def test_create_metric_alert_no_data_source(self):
75+
"""
76+
Test that when we return None and don't create any ACI models if the data source can't be created
77+
"""
78+
self.metric_alert.update(snuba_query=None)
79+
migrated = migrate_alert_rule(self.metric_alert, self.rpc_user)
80+
assert migrated is None
81+
assert len(DataSource.objects.all()) == 0
82+
assert not AlertRuleWorkflow.objects.filter(alert_rule=self.metric_alert).exists()
83+
assert not AlertRuleDetector.objects.filter(alert_rule=self.metric_alert).exists()

0 commit comments

Comments
 (0)