Skip to content

Commit 4db5947

Browse files
authored
fix(workflow-engine): Handle non-numeric DataCondition comparisons in combined-rules serializer (#111743)
The WorkflowEngineDataConditionSerializer assumed all DataCondition comparison values in workflow DCGs were numeric, which holds for dual-written rules but not for single-written rules where anomaly detection conditions can store dict comparisons. Filter to numeric values only when building the comparison lookup. Fixes SENTRY-5MHW.
1 parent cb2ed4c commit 4db5947

File tree

2 files changed

+37
-3
lines changed

2 files changed

+37
-3
lines changed

src/sentry/incidents/endpoints/serializers/workflow_engine_data_condition.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,12 @@ def get_attrs(
8080
# Map (condition_group_id, comparison) → action-filter DC exists in that DCG
8181
# We need: for a given detector's DCGs + priority level → matching DCG IDs
8282
# NOTE: Assumes DataConditions are limited to what would be dual written.
83-
dcg_comparison_pairs: dict[int, set[int]] = defaultdict(set)
83+
dcg_comparison_pairs: dict[int, set[int | float]] = defaultdict(set)
8484
for dc in DataCondition.objects.filter(condition_group__in=all_dcg_ids):
85-
# Map comparison value → set of DCG IDs that have an action filter at that level
86-
dcg_comparison_pairs[dc.condition_group_id].add(dc.comparison)
85+
# Only collect numeric comparison values; non-numeric values (e.g. dicts
86+
# from anomaly detection conditions) don't match condition_result levels.
87+
if isinstance(dc.comparison, (int, float)):
88+
dcg_comparison_pairs[dc.condition_group_id].add(dc.comparison)
8789

8890
# Bulk-fetch all DCG → action mappings
8991
dcg_to_action_ids: dict[int, list[int]] = defaultdict(list)

tests/sentry/incidents/serializers/test_workflow_engine_data_condition.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
migrate_metric_data_conditions,
1616
migrate_resolve_threshold_data_condition,
1717
)
18+
from sentry.workflow_engine.models import DataCondition, WorkflowDataConditionGroup
19+
from sentry.workflow_engine.models.data_condition import Condition
1820
from tests.sentry.incidents.serializers.test_workflow_engine_base import (
1921
TestWorkflowEngineSerializer,
2022
)
@@ -153,6 +155,36 @@ def test_anomaly_detection(self) -> None:
153155
assert serialized_data_condition["alertThreshold"] == 0
154156
assert serialized_data_condition["resolveThreshold"] is None
155157

158+
def test_anomaly_detection_with_workflow_actions(self) -> None:
159+
dynamic_rule = self.create_dynamic_alert()
160+
critical_trigger = self.create_alert_rule_trigger(
161+
alert_rule=dynamic_rule, label="critical", alert_threshold=0
162+
)
163+
trigger_action = self.create_alert_rule_trigger_action(alert_rule_trigger=critical_trigger)
164+
_, _, _, detector, _, _, _, _ = migrate_alert_rule(dynamic_rule)
165+
detector_trigger, _, _ = migrate_metric_data_conditions(critical_trigger)
166+
migrate_metric_action(trigger_action)
167+
168+
workflow_dcg = WorkflowDataConditionGroup.objects.filter(
169+
workflow__detectorworkflow__detector=detector,
170+
).first()
171+
assert workflow_dcg is not None
172+
DataCondition.objects.create(
173+
type=Condition.ANOMALY_DETECTION,
174+
comparison={"sensitivity": "high", "seasonality": "auto", "threshold_type": 2},
175+
condition_result=True,
176+
condition_group=workflow_dcg.condition_group,
177+
)
178+
179+
serialized = serialize(
180+
detector_trigger,
181+
self.user,
182+
WorkflowEngineDataConditionSerializer(),
183+
)
184+
assert serialized["thresholdType"] == AlertRuleThresholdType.ABOVE_AND_BELOW.value
185+
assert serialized["alertThreshold"] == 0
186+
assert serialized["resolveThreshold"] is None
187+
156188
def test_multiple_rules(self) -> None:
157189
# create another comprehensive alert rule in the DB
158190
alert_rule, critical_trigger, warning_trigger, critical_action, warning_action = (

0 commit comments

Comments
 (0)