From 59bcb434a7cce6e652b3d2c87de9d2183f313a2b Mon Sep 17 00:00:00 2001 From: Kyle Consalus Date: Wed, 20 May 2026 16:39:37 -0700 Subject: [PATCH] fix(alerts): Handle gte/lte condition types in metric alert serializers --- .../workflow_engine_data_condition.py | 2 +- .../serializers/workflow_engine_detector.py | 2 +- src/sentry/incidents/endpoints/utils.py | 2 + .../test_organization_alert_rule_index.py | 47 +++++++++++++++++++ .../test_workflow_engine_data_condition.py | 20 ++++++++ 5 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/sentry/incidents/endpoints/serializers/workflow_engine_data_condition.py b/src/sentry/incidents/endpoints/serializers/workflow_engine_data_condition.py index 37f37b56bcae..b56aa08e5346 100644 --- a/src/sentry/incidents/endpoints/serializers/workflow_engine_data_condition.py +++ b/src/sentry/incidents/endpoints/serializers/workflow_engine_data_condition.py @@ -191,7 +191,7 @@ def serialize( else: threshold_type = ( AlertRuleThresholdType.ABOVE.value - if obj.type == Condition.GREATER + if obj.type in (Condition.GREATER, Condition.GREATER_OR_EQUAL) else AlertRuleThresholdType.BELOW.value ) # For static/metric rules, calculate resolve threshold from the resolve condition diff --git a/src/sentry/incidents/endpoints/serializers/workflow_engine_detector.py b/src/sentry/incidents/endpoints/serializers/workflow_engine_detector.py index 2e75b9f9cbfb..5f578f00c5c9 100644 --- a/src/sentry/incidents/endpoints/serializers/workflow_engine_detector.py +++ b/src/sentry/incidents/endpoints/serializers/workflow_engine_detector.py @@ -318,7 +318,7 @@ def get_attrs( else: result[detector]["thresholdType"] = ( AlertRuleThresholdType.ABOVE.value - if trigger_dc.type == Condition.GREATER + if trigger_dc.type in (Condition.GREATER, Condition.GREATER_OR_EQUAL) else AlertRuleThresholdType.BELOW.value ) result[detector]["sensitivity"] = None diff --git a/src/sentry/incidents/endpoints/utils.py b/src/sentry/incidents/endpoints/utils.py index 632295e4c0c1..76911fe4380d 100644 --- a/src/sentry/incidents/endpoints/utils.py +++ b/src/sentry/incidents/endpoints/utils.py @@ -32,7 +32,9 @@ def parse_team_params( data_condition_type_translators = { Condition.GREATER.value: lambda threshold: threshold - 100, + Condition.GREATER_OR_EQUAL.value: lambda threshold: threshold - 100, Condition.LESS.value: lambda threshold: 100 - threshold, + Condition.LESS_OR_EQUAL.value: lambda threshold: 100 - threshold, } diff --git a/tests/sentry/incidents/endpoints/test_organization_alert_rule_index.py b/tests/sentry/incidents/endpoints/test_organization_alert_rule_index.py index 54a7acc97e4f..cfe2fa136cd1 100644 --- a/tests/sentry/incidents/endpoints/test_organization_alert_rule_index.py +++ b/tests/sentry/incidents/endpoints/test_organization_alert_rule_index.py @@ -66,6 +66,8 @@ from sentry.testutils.skips import requires_snuba from sentry.utils.snuba import _snuba_pool from sentry.workflow_engine.models import Action, DataSource, Detector +from sentry.workflow_engine.models.data_condition import Condition +from sentry.workflow_engine.types import DetectorPriorityLevel from tests.sentry.incidents.serializers.test_workflow_engine_base import ( TestWorkflowEngineSerializer, ) @@ -369,6 +371,51 @@ def test_workflow_engine_serializer_scopes_to_project(self) -> None: assert len(resp.data) == 1 assert resp.data[0]["name"] == self.detector.name + def test_workflow_engine_serializer_gte_lte_condition_types(self) -> None: + team = self.create_team(organization=self.organization, members=[self.user]) + ProjectTeam.objects.create(project=self.project, team=team) + + query_subscription = QuerySubscription.objects.get(snuba_query=self.alert_rule.snuba_query) + data_source, _ = DataSource.objects.get_or_create( + type="snuba_query_subscription", + source_id=str(query_subscription.id), + defaults={"organization_id": self.organization.id}, + ) + + for condition_type, expected_threshold_type, name in ( + (Condition.GREATER_OR_EQUAL, AlertRuleThresholdType.ABOVE, "GTE Detector"), + (Condition.LESS_OR_EQUAL, AlertRuleThresholdType.BELOW, "LTE Detector"), + ): + dcg = self.create_data_condition_group() + detector = self.create_detector( + project=self.project, + type=MetricIssue.slug, + name=name, + config={"detection_type": "percent", "comparison_delta": 604800}, + workflow_condition_group=dcg, + ) + data_source.detectors.add(detector) + self.create_data_condition( + type=condition_type, + comparison=150, + condition_result=DetectorPriorityLevel.HIGH, + condition_group=dcg, + ) + + self.login_as(self.user) + with self.feature( + ["organizations:incidents", "organizations:workflow-engine-rule-serializers"] + ): + resp = self.get_success_response(self.organization.slug) + + results_by_name = {item["name"]: item for item in resp.data} + assert ( + results_by_name["GTE Detector"]["thresholdType"] == AlertRuleThresholdType.ABOVE.value + ) + assert ( + results_by_name["LTE Detector"]["thresholdType"] == AlertRuleThresholdType.BELOW.value + ) + @freeze_time("2024-12-11 03:21:34") class AlertRuleListDeltaTest(AlertRuleIndexBase, TestWorkflowEngineSerializer): diff --git a/tests/sentry/incidents/serializers/test_workflow_engine_data_condition.py b/tests/sentry/incidents/serializers/test_workflow_engine_data_condition.py index f638e72af71f..2a92833242a1 100644 --- a/tests/sentry/incidents/serializers/test_workflow_engine_data_condition.py +++ b/tests/sentry/incidents/serializers/test_workflow_engine_data_condition.py @@ -134,6 +134,26 @@ def test_comparison_delta(self) -> None: expected_trigger["alertRuleId"] = str(comparison_delta_rule.id) assert serialized_data_condition == expected_trigger + def test_comparison_delta_with_gte_lte_conditions(self) -> None: + for condition_type, expected_threshold_type in ( + (Condition.GREATER_OR_EQUAL, AlertRuleThresholdType.ABOVE), + (Condition.LESS_OR_EQUAL, AlertRuleThresholdType.BELOW), + ): + rule = self.create_alert_rule(comparison_delta=60) + trigger = self.create_alert_rule_trigger(alert_rule=rule, label="critical") + trigger_action = self.create_alert_rule_trigger_action(alert_rule_trigger=trigger) + _, _, _, detector, _, _, _, _ = migrate_alert_rule(rule) + detector_trigger, _, _ = migrate_metric_data_conditions(trigger) + migrate_resolve_threshold_data_condition(rule) + migrate_metric_action(trigger_action) + + detector_trigger.update(type=condition_type) + + serialized = serialize( + detector_trigger, self.user, WorkflowEngineDataConditionSerializer() + ) + assert serialized["thresholdType"] == expected_threshold_type.value + def test_anomaly_detection(self) -> None: dynamic_rule = self.create_dynamic_alert() critical_trigger = self.create_alert_rule_trigger(