Browse Source

fix(alerts): support in operator for new tag condition (#79974)

- [x] use the match constants file instead of hardcoding strings
- [x] support in / not in operators
- [x] support "not starts with"

Fixes https://sentry.sentry.io/issues/6027566680/
Josh Ferge 4 months ago
parent
commit
a3c548fbf3

+ 24 - 11
src/sentry/rules/conditions/event_frequency.py

@@ -23,6 +23,7 @@ from sentry.models.group import Group
 from sentry.models.project import Project
 from sentry.rules import EventState
 from sentry.rules.conditions.base import EventCondition, GenericCondition
+from sentry.rules.match import MatchType
 from sentry.tsdb.base import TSDBModel
 from sentry.types.condition_activity import (
     FREQUENCY_CONDITION_BUCKET_SIZE,
@@ -672,7 +673,7 @@ class EventUniqueUserFrequencyConditionWithConditions(EventUniqueUserFrequencyCo
         end: datetime,
         environment_id: int,
         referrer_suffix: str,
-        conditions: list[tuple[str, str, str]] | None = None,
+        conditions: list[tuple[str, str, str | list[str]]] | None = None,
     ) -> Mapping[int, int]:
         result: Mapping[int, int] = tsdb_function(
             model=model,
@@ -698,7 +699,7 @@ class EventUniqueUserFrequencyConditionWithConditions(EventUniqueUserFrequencyCo
         end: datetime,
         environment_id: int,
         referrer_suffix: str,
-        conditions: list[tuple[str, str, str]] | None = None,
+        conditions: list[tuple[str, str, str | list[str]]] | None = None,
     ) -> dict[int, int]:
         batch_totals: dict[int, int] = defaultdict(int)
         group_id = group_ids[0]
@@ -721,34 +722,46 @@ class EventUniqueUserFrequencyConditionWithConditions(EventUniqueUserFrequencyCo
     @staticmethod
     def convert_rule_condition_to_snuba_condition(
         condition: dict[str, Any]
-    ) -> tuple[str, str, str] | None:
+    ) -> tuple[str, str, str | list[str]] | None:
         if condition["id"] != "sentry.rules.filters.tagged_event.TaggedEventFilter":
             return None
         lhs = f"tags[{condition['key']}]"
         rhs = condition["value"]
         match condition["match"]:
-            case "eq":
+            case MatchType.EQUAL:
                 operator = Op.EQ
-            case "ne":
+            case MatchType.NOT_EQUAL:
                 operator = Op.NEQ
-            case "sw":
+            case MatchType.STARTS_WITH:
                 operator = Op.LIKE
                 rhs = f"{rhs}%"
-            case "ew":
+            case MatchType.NOT_STARTS_WITH:
+                operator = Op.NOT_LIKE
+                rhs = f"{rhs}%"
+            case MatchType.ENDS_WITH:
                 operator = Op.LIKE
                 rhs = f"%{rhs}"
-            case "co":
+            case MatchType.NOT_ENDS_WITH:
+                operator = Op.NOT_LIKE
+                rhs = f"%{rhs}"
+            case MatchType.CONTAINS:
                 operator = Op.LIKE
                 rhs = f"%{rhs}%"
-            case "nc":
+            case MatchType.NOT_CONTAINS:
                 operator = Op.NOT_LIKE
                 rhs = f"%{rhs}%"
-            case "is":
+            case MatchType.IS_SET:
                 operator = Op.IS_NOT_NULL
                 rhs = None
-            case "ns":
+            case MatchType.NOT_SET:
                 operator = Op.IS_NULL
                 rhs = None
+            case MatchType.IS_IN:
+                operator = Op.IN
+                rhs = rhs.split(",")
+            case MatchType.NOT_IN:
+                operator = Op.NOT_IN
+                rhs = rhs.split(",")
             case _:
                 raise ValueError(f"Unsupported match type: {condition['match']}")
 

+ 73 - 10
tests/snuba/rules/conditions/test_event_frequency.py

@@ -18,6 +18,7 @@ from sentry.rules.conditions.event_frequency import (
     EventUniqueUserFrequencyCondition,
     EventUniqueUserFrequencyConditionWithConditions,
 )
+from sentry.rules.match import MatchType
 from sentry.testutils.abstract import Abstract
 from sentry.testutils.cases import (
     BaseMetricsTestCase,
@@ -725,7 +726,7 @@ def test_convert_rule_condition_to_snuba_condition():
     }
 
     # Test equality
-    eq_condition = {**base_condition, "match": "eq"}
+    eq_condition = {**base_condition, "match": MatchType.EQUAL}
     assert (
         EventUniqueUserFrequencyConditionWithConditions.convert_rule_condition_to_snuba_condition(
             eq_condition
@@ -738,7 +739,7 @@ def test_convert_rule_condition_to_snuba_condition():
     )
 
     # Test inequality
-    ne_condition = {**base_condition, "match": "ne"}
+    ne_condition = {**base_condition, "match": MatchType.NOT_EQUAL}
     assert (
         EventUniqueUserFrequencyConditionWithConditions.convert_rule_condition_to_snuba_condition(
             ne_condition
@@ -751,7 +752,7 @@ def test_convert_rule_condition_to_snuba_condition():
     )
 
     # Test starts with
-    sw_condition = {**base_condition, "match": "sw"}
+    sw_condition = {**base_condition, "match": MatchType.STARTS_WITH}
     assert (
         EventUniqueUserFrequencyConditionWithConditions.convert_rule_condition_to_snuba_condition(
             sw_condition
@@ -763,8 +764,21 @@ def test_convert_rule_condition_to_snuba_condition():
         )
     )
 
+    # Test not starts with
+    nsw_condition = {**base_condition, "match": MatchType.NOT_STARTS_WITH}
+    assert (
+        EventUniqueUserFrequencyConditionWithConditions.convert_rule_condition_to_snuba_condition(
+            nsw_condition
+        )
+        == (
+            "tags[test_key]",
+            Op.NOT_LIKE.value,
+            "test_value%",
+        )
+    )
+
     # Test ends with
-    ew_condition = {**base_condition, "match": "ew"}
+    ew_condition = {**base_condition, "match": MatchType.ENDS_WITH}
     assert (
         EventUniqueUserFrequencyConditionWithConditions.convert_rule_condition_to_snuba_condition(
             ew_condition
@@ -776,8 +790,21 @@ def test_convert_rule_condition_to_snuba_condition():
         )
     )
 
+    # Test not ends with
+    new_condition = {**base_condition, "match": MatchType.NOT_ENDS_WITH}
+    assert (
+        EventUniqueUserFrequencyConditionWithConditions.convert_rule_condition_to_snuba_condition(
+            new_condition
+        )
+        == (
+            "tags[test_key]",
+            Op.NOT_LIKE.value,
+            "%test_value",
+        )
+    )
+
     # Test contains
-    co_condition = {**base_condition, "match": "co"}
+    co_condition = {**base_condition, "match": MatchType.CONTAINS}
     assert (
         EventUniqueUserFrequencyConditionWithConditions.convert_rule_condition_to_snuba_condition(
             co_condition
@@ -790,7 +817,7 @@ def test_convert_rule_condition_to_snuba_condition():
     )
 
     # Test not contains
-    nc_condition = {**base_condition, "match": "nc"}
+    nc_condition = {**base_condition, "match": MatchType.NOT_CONTAINS}
     assert (
         EventUniqueUserFrequencyConditionWithConditions.convert_rule_condition_to_snuba_condition(
             nc_condition
@@ -802,8 +829,8 @@ def test_convert_rule_condition_to_snuba_condition():
         )
     )
 
-    # Test is not null
-    is_condition = {**base_condition, "match": "is"}
+    # Test is set
+    is_condition = {**base_condition, "match": MatchType.IS_SET}
     assert (
         EventUniqueUserFrequencyConditionWithConditions.convert_rule_condition_to_snuba_condition(
             is_condition
@@ -815,8 +842,8 @@ def test_convert_rule_condition_to_snuba_condition():
         )
     )
 
-    # Test is null
-    ns_condition = {**base_condition, "match": "ns"}
+    # Test not set
+    ns_condition = {**base_condition, "match": MatchType.NOT_SET}
     assert (
         EventUniqueUserFrequencyConditionWithConditions.convert_rule_condition_to_snuba_condition(
             ns_condition
@@ -828,6 +855,42 @@ def test_convert_rule_condition_to_snuba_condition():
         )
     )
 
+    # Test is in
+    in_condition = {
+        "id": "sentry.rules.filters.tagged_event.TaggedEventFilter",
+        "key": "test_key",
+        "value": "test_value_1,test_value_2",
+        "match": MatchType.IS_IN,
+    }
+    assert (
+        EventUniqueUserFrequencyConditionWithConditions.convert_rule_condition_to_snuba_condition(
+            in_condition
+        )
+        == (
+            "tags[test_key]",
+            Op.IN.value,
+            ["test_value_1", "test_value_2"],
+        )
+    )
+
+    # Test not in
+    not_in_condition = {
+        "id": "sentry.rules.filters.tagged_event.TaggedEventFilter",
+        "key": "test_key",
+        "value": "test_value_1,test_value_2",
+        "match": MatchType.NOT_IN,
+    }
+    assert (
+        EventUniqueUserFrequencyConditionWithConditions.convert_rule_condition_to_snuba_condition(
+            not_in_condition
+        )
+        == (
+            "tags[test_key]",
+            Op.NOT_IN.value,
+            ["test_value_1", "test_value_2"],
+        )
+    )
+
     # Test unsupported match type
     with pytest.raises(ValueError, match="Unsupported match type: unsupported"):
         EventUniqueUserFrequencyConditionWithConditions.convert_rule_condition_to_snuba_condition(