Skip to content

Commit f120079

Browse files
authored
feat(aci milestone 3): resolution action filters (#90898)
Add the resolution action filter to dual write/update. This data condition evaluates to True if an issue's priority is de-escalating; see #89832 for more details.
1 parent 99fff06 commit f120079

File tree

6 files changed

+71
-40
lines changed

6 files changed

+71
-40
lines changed

src/sentry/workflow_engine/handlers/condition/issue_priority_deescalating_handler.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,19 @@ class IssuePriorityDeescalatingConditionHandler(DataConditionHandler[WorkflowEve
1212
group = DataConditionHandler.Group.ACTION_FILTER
1313
subgroup = DataConditionHandler.Subgroup.ISSUE_ATTRIBUTES
1414

15-
comparison_json_schema = {
16-
"type": "object",
17-
"properties": {
18-
"priority": {"type": "integer", "minimum": 0},
19-
},
20-
"required": ["priority"],
21-
"additionalProperties": False,
22-
}
23-
2415
@staticmethod
2516
def evaluate_value(event_data: WorkflowEventData, comparison: Any) -> bool:
2617
group = event_data.event.group
2718

2819
# we will fire actions on de-escalation if the priority seen is >= the threshold
2920
# priority specified in the comparison
30-
comparison_priority = comparison.get("priority")
3121
current_priority = group.priority
3222
open_period = get_latest_open_period(group)
3323
if open_period is None:
3424
raise Exception("No open period found")
3525
# use this to determine if we've breached the comparison priority before
3626
highest_seen_priority = open_period.data.get("highest_seen_priority", current_priority)
3727

38-
return comparison_priority <= highest_seen_priority and (
39-
current_priority < comparison_priority or group.status == GroupStatus.RESOLVED
28+
return comparison <= highest_seen_priority and (
29+
current_priority < comparison or group.status == GroupStatus.RESOLVED
4030
)

src/sentry/workflow_engine/migration_helpers/alert_rule.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ def get_action_filter(
212212
action_filter = DataCondition.objects.get(
213213
condition_group__in=workflow_dcgs,
214214
comparison=priority,
215+
type=Condition.ISSUE_PRIORITY_GREATER_OR_EQUAL,
215216
)
216217
return action_filter
217218

@@ -262,7 +263,7 @@ def migrate_metric_action(
262263

263264
def migrate_metric_data_conditions(
264265
alert_rule_trigger: AlertRuleTrigger,
265-
) -> tuple[DataCondition, DataCondition]:
266+
) -> tuple[DataCondition, DataCondition, DataCondition]:
266267
alert_rule = alert_rule_trigger.alert_rule
267268
# create a data condition for the Detector's data condition group with the
268269
# threshold and associated priority level
@@ -313,7 +314,15 @@ def migrate_metric_data_conditions(
313314
type=Condition.ISSUE_PRIORITY_GREATER_OR_EQUAL,
314315
condition_group=data_condition_group,
315316
)
316-
return detector_trigger, action_filter
317+
# finally, create a "resolution action filter": the condition result is set to true
318+
# if we're de-escalating from the priority specified in the comparison
319+
resolve_action_filter = DataCondition.objects.create(
320+
comparison=PRIORITY_MAP.get(alert_rule_trigger.label, DetectorPriorityLevel.HIGH),
321+
condition_result=True,
322+
type=Condition.ISSUE_PRIORITY_DEESCALATING,
323+
condition_group=data_condition_group,
324+
)
325+
return detector_trigger, action_filter, resolve_action_filter
317326

318327

319328
def get_resolve_threshold(detector_data_condition_group: DataConditionGroup) -> float:
@@ -719,7 +728,10 @@ def dual_update_migrated_alert_rule_trigger(
719728
# we won't reach this path
720729
return None
721730
action_filter = get_action_filter(alert_rule_trigger, priority)
722-
731+
resolve_action_filter = DataCondition.objects.filter(
732+
condition_group=action_filter.condition_group,
733+
type=Condition.ISSUE_PRIORITY_DEESCALATING,
734+
).first()
723735
updated_detector_trigger_fields: dict[str, Any] = {}
724736
updated_action_filter_fields: dict[str, Any] = {}
725737
label = alert_rule_trigger.label
@@ -731,7 +743,10 @@ def dual_update_migrated_alert_rule_trigger(
731743

732744
detector_trigger.update(**updated_detector_trigger_fields)
733745
if updated_action_filter_fields:
746+
# these are updated together
734747
action_filter.update(**updated_action_filter_fields)
748+
if resolve_action_filter is not None:
749+
resolve_action_filter.update(**updated_action_filter_fields)
735750

736751
return detector_trigger, action_filter
737752

@@ -835,8 +850,7 @@ def dual_delete_migrated_alert_rule_trigger(alert_rule_trigger: AlertRuleTrigger
835850
action.delete()
836851

837852
detector_trigger.delete()
838-
action_filter.delete()
839-
action_filter_dcg.delete()
853+
action_filter_dcg.delete() # deletes the action filter and resolve action filter
840854

841855
return None
842856

tests/sentry/incidents/serializers/test_workflow_engine_base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def setUp(self) -> None:
3333
alert_rule_trigger=self.critical_trigger
3434
)
3535
_, _, _, self.detector, _, _, _, _ = migrate_alert_rule(self.alert_rule)
36-
self.critical_detector_trigger, _ = migrate_metric_data_conditions(self.critical_trigger)
36+
self.critical_detector_trigger, _, _ = migrate_metric_data_conditions(self.critical_trigger)
3737

3838
self.critical_action, _, _ = migrate_metric_action(self.critical_trigger_action)
3939
self.resolve_trigger_data_condition = migrate_resolve_threshold_data_condition(
@@ -94,7 +94,7 @@ def add_warning_trigger(self) -> None:
9494
self.warning_trigger_action = self.create_alert_rule_trigger_action(
9595
alert_rule_trigger=self.warning_trigger
9696
)
97-
self.warning_detector_trigger, _ = migrate_metric_data_conditions(self.warning_trigger)
97+
self.warning_detector_trigger, _, _ = migrate_metric_data_conditions(self.warning_trigger)
9898
self.warning_action, _, _ = migrate_metric_action(self.warning_trigger_action)
9999
self.expected_warning_action = [
100100
{

tests/sentry/incidents/serializers/test_workflow_engine_data_condition.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def test_comparison_delta(self) -> None:
101101
alert_rule_trigger=comparison_delta_trigger
102102
)
103103
_, _, _, detector, _, _, _, _ = migrate_alert_rule(comparison_delta_rule)
104-
comparison_detector_trigger, _ = migrate_metric_data_conditions(comparison_delta_trigger)
104+
comparison_detector_trigger, _, _ = migrate_metric_data_conditions(comparison_delta_trigger)
105105
migrate_resolve_threshold_data_condition(comparison_delta_rule)
106106
action, _, _ = migrate_metric_action(comparison_delta_trigger_action)
107107

@@ -131,8 +131,8 @@ def test_multiple_rules(self) -> None:
131131
self.create_rule_triggers_and_actions()
132132
)
133133
migrate_alert_rule(alert_rule)
134-
critical_detector_trigger, _ = migrate_metric_data_conditions(critical_trigger)
135-
warning_detector_trigger, _ = migrate_metric_data_conditions(warning_trigger)
134+
critical_detector_trigger, _, _ = migrate_metric_data_conditions(critical_trigger)
135+
warning_detector_trigger, _, _ = migrate_metric_data_conditions(warning_trigger)
136136
migrate_resolve_threshold_data_condition(alert_rule)
137137
migrate_metric_action(critical_action)
138138
migrate_metric_action(warning_action)

tests/sentry/workflow_engine/handlers/condition/test_issue_priority_deescalating_handler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ def setUp(self):
4040
dc_critical = data_condition_critical_tuple[1]
4141

4242
self.deescalating_dc_warning = self.create_data_condition(
43-
comparison={"priority": DetectorPriorityLevel.MEDIUM.value},
43+
comparison=DetectorPriorityLevel.MEDIUM,
4444
type=self.condition,
4545
condition_result=True,
4646
condition_group=dc_warning.condition_group,
4747
)
4848

4949
self.deescalating_dc_critical = self.create_data_condition(
50-
comparison={"priority": DetectorPriorityLevel.HIGH.value},
50+
comparison=DetectorPriorityLevel.HIGH,
5151
type=self.condition,
5252
condition_result=True,
5353
condition_group=dc_critical.condition_group,

tests/sentry/workflow_engine/migration_helpers/test_migrate_alert_rule.py

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,28 @@ def assert_alert_rule_trigger_migrated(alert_rule_trigger):
167167
workflow_dcgs = DataConditionGroup.objects.filter(workflowdataconditiongroup__workflow=workflow)
168168

169169
data_condition = DataCondition.objects.get(
170-
comparison=condition_result, condition_result=True, condition_group__in=workflow_dcgs
170+
comparison=condition_result,
171+
condition_result=True,
172+
condition_group__in=workflow_dcgs,
173+
type=Condition.ISSUE_PRIORITY_GREATER_OR_EQUAL,
171174
)
172175
assert data_condition.type == Condition.ISSUE_PRIORITY_GREATER_OR_EQUAL
173176
assert data_condition.condition_result is True
174177
assert WorkflowDataConditionGroup.objects.filter(
175178
condition_group=data_condition.condition_group
176179
).exists()
177180

181+
resolve_data_condition = DataCondition.objects.get(
182+
comparison=condition_result,
183+
condition_result=True,
184+
condition_group__in=workflow_dcgs,
185+
type=Condition.ISSUE_PRIORITY_DEESCALATING,
186+
)
187+
assert resolve_data_condition.condition_group == data_condition.condition_group
188+
assert WorkflowDataConditionGroup.objects.filter(
189+
condition_group=resolve_data_condition.condition_group
190+
).exists()
191+
178192

179193
def build_sentry_app_compare_blob(
180194
sentry_app_config: list[dict[str, Any]],
@@ -333,7 +347,7 @@ def create_migrated_metric_alert_rule_trigger_objects(
333347
alert_rule_trigger: AlertRuleTrigger,
334348
priority: DetectorPriorityLevel,
335349
detector_trigger_type: Condition,
336-
) -> tuple[DataCondition, DataCondition]:
350+
) -> tuple[DataCondition, DataCondition, DataCondition]:
337351
"""
338352
Set up all the necessary ACI objects for a dual written metric alert trigger.
339353
"""
@@ -365,7 +379,13 @@ def create_migrated_metric_alert_rule_trigger_objects(
365379
condition_group=data_condition_group,
366380
)
367381

368-
return detector_trigger, action_filter
382+
resolve_action_filter = self.create_data_condition(
383+
comparison=priority,
384+
condition_result=True,
385+
type=Condition.ISSUE_PRIORITY_DEESCALATING,
386+
condition_group=data_condition_group,
387+
)
388+
return detector_trigger, action_filter, resolve_action_filter
369389

370390
def create_migrated_metric_alert_rule_resolve_objects(
371391
self, alert_rule: AlertRule, resolve_threshold, detector_trigger_type: Condition
@@ -516,8 +536,10 @@ def test_dual_delete_comprehensive(self):
516536
alert_rule_trigger=alert_rule_trigger
517537
)
518538

519-
detector_trigger, action_filter = self.create_migrated_metric_alert_rule_trigger_objects(
520-
alert_rule_trigger, DetectorPriorityLevel.HIGH, Condition.GREATER
539+
detector_trigger, action_filter, resolve_action_filter = (
540+
self.create_migrated_metric_alert_rule_trigger_objects(
541+
alert_rule_trigger, DetectorPriorityLevel.HIGH, Condition.GREATER
542+
)
521543
)
522544
action_filter_dcg = action_filter.condition_group
523545
action, data_condition_group_action, aarta = (
@@ -541,6 +563,7 @@ def test_dual_delete_comprehensive(self):
541563
# check trigger objects
542564
assert not DataConditionGroup.objects.filter(id=action_filter_dcg.id).exists()
543565
assert not DataCondition.objects.filter(id=detector_trigger.id).exists()
566+
assert not DataCondition.objects.filter(id=resolve_action_filter.id).exists()
544567
assert not DataConditionAlertRuleTrigger.objects.filter(
545568
data_condition=detector_trigger
546569
).exists()
@@ -594,10 +617,12 @@ def setUp(self):
594617
self.alert_rule_trigger = self.create_alert_rule_trigger(
595618
alert_rule=self.metric_alert, label="critical", alert_threshold=200
596619
)
597-
self.critical_detector_trigger, self.critical_action_filter = (
598-
self.create_migrated_metric_alert_rule_trigger_objects(
599-
self.alert_rule_trigger, DetectorPriorityLevel.HIGH, Condition.GREATER
600-
)
620+
(
621+
self.critical_detector_trigger,
622+
self.critical_action_filter,
623+
self.critical_resolve_action_filter,
624+
) = self.create_migrated_metric_alert_rule_trigger_objects(
625+
self.alert_rule_trigger, DetectorPriorityLevel.HIGH, Condition.GREATER
601626
)
602627
self.resolve_detector_trigger = self.create_migrated_metric_alert_rule_resolve_objects(
603628
self.metric_alert, 200, Condition.LESS_OR_EQUAL
@@ -736,7 +761,7 @@ def setUp(self):
736761
alert_rule=self.metric_alert, label="critical"
737762
)
738763
self.create_migrated_metric_alert_objects(self.metric_alert)
739-
self.detector_trigger, self.action_filter = (
764+
self.detector_trigger, self.action_filter, self.resolve_action_filter = (
740765
self.create_migrated_metric_alert_rule_trigger_objects(
741766
self.alert_rule_trigger, DetectorPriorityLevel.HIGH, Condition.GREATER
742767
)
@@ -792,10 +817,12 @@ def setUp(self):
792817
alert_rule=self.metric_alert, label="critical", alert_threshold=200
793818
)
794819
self.create_migrated_metric_alert_objects(self.metric_alert)
795-
self.critical_detector_trigger, self.critical_action_filter = (
796-
self.create_migrated_metric_alert_rule_trigger_objects(
797-
self.alert_rule_trigger, DetectorPriorityLevel.HIGH, Condition.GREATER
798-
)
820+
(
821+
self.critical_detector_trigger,
822+
self.critical_action_filter,
823+
self.critical_resolve_action_filter,
824+
) = self.create_migrated_metric_alert_rule_trigger_objects(
825+
self.alert_rule_trigger, DetectorPriorityLevel.HIGH, Condition.GREATER
799826
)
800827
self.resolve_detector_trigger = self.create_migrated_metric_alert_rule_resolve_objects(
801828
self.metric_alert, 200, Condition.LESS_OR_EQUAL
@@ -1254,7 +1281,7 @@ def setUp(self):
12541281
alert_rule=self.metric_alert, label="critical"
12551282
)
12561283
self.create_migrated_metric_alert_objects(self.metric_alert)
1257-
self.detector_trigger, self.action_filter = (
1284+
self.detector_trigger, self.action_filter, self.resolve_action_filter = (
12581285
self.create_migrated_metric_alert_rule_trigger_objects(
12591286
self.alert_rule_trigger, DetectorPriorityLevel.HIGH, Condition.GREATER
12601287
)
@@ -1337,7 +1364,7 @@ def setUp(self):
13371364
)
13381365

13391366
self.create_migrated_metric_alert_objects(self.dual_written_alert)
1340-
self.detector_trigger, self.action_filter = (
1367+
self.detector_trigger, self.action_filter, self.resolve_action_filter = (
13411368
self.create_migrated_metric_alert_rule_trigger_objects(
13421369
self.dual_written_trigger, DetectorPriorityLevel.HIGH, Condition.GREATER
13431370
)

0 commit comments

Comments
 (0)