Skip to content

Commit 68c6cf5

Browse files
authored
implement not in issue alert filter (#74595)
corollary to #74517
1 parent cb71631 commit 68c6cf5

File tree

6 files changed

+40
-2
lines changed

6 files changed

+40
-2
lines changed

src/sentry/api/endpoints/project_rules_configuration.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,13 @@ def get(self, request: Request, project) -> Response:
9595
context["id"] == "sentry.rules.filters.tagged_event.TaggedEventFilter"
9696
or context["id"] == "sentry.rules.filters.event_attribute.EventAttributeFilter"
9797
) and not has_is_in:
98-
# Filter the form_fields to strip `is_in` choices
98+
# Filter the form_fields to strip `is_in` and `not_in` choices
9999
match_choices = context["formFields"]["match"]["choices"]
100100
copied_context = copy.deepcopy(context)
101101
copied_context["formFields"]["match"]["choices"] = [
102-
choice for choice in match_choices if choice[0] != MatchType.IS_IN
102+
choice
103+
for choice in match_choices
104+
if (choice[0] != MatchType.IS_IN and choice[0] != MatchType.NOT_IN)
103105
]
104106
filter_list.append(copied_context)
105107
continue

src/sentry/rules/match.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class MatchType:
1717
NOT_EQUAL = "ne"
1818
NOT_SET = "ns"
1919
NOT_STARTS_WITH = "nsw"
20+
NOT_IN = "nin"
2021
STARTS_WITH = "sw"
2122

2223

@@ -43,6 +44,7 @@ class MatchType:
4344
MatchType.NOT_EQUAL: "does not equal",
4445
MatchType.NOT_SET: "is not set",
4546
MatchType.NOT_STARTS_WITH: "does not start with",
47+
MatchType.NOT_IN: "not in (comma separated)",
4648
MatchType.STARTS_WITH: "starts with",
4749
}
4850

@@ -92,4 +94,8 @@ def match_values(group_values: Iterable[Any], match_value: str, match_type: str)
9294
values_set = set(match_value.replace(" ", "").split(","))
9395
return any(g_value in values_set for g_value in group_values)
9496

97+
elif match_type == MatchType.NOT_IN:
98+
values_set = set(match_value.replace(" ", "").split(","))
99+
return not any(g_value in values_set for g_value in group_values)
100+
95101
raise RuntimeError("Invalid Match")

tests/sentry/api/endpoints/test_project_rules_configuration.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ def test_has_in_feature(self):
231231
choice[0] for choice in tagged_event_filter["formFields"]["match"]["choices"]
232232
]
233233
assert MatchType.IS_IN not in filter_list
234+
assert MatchType.NOT_IN not in filter_list
234235

235236
with self.feature({"organizations:issues-alerts-is-in": True}):
236237
response = self.get_success_response(self.organization.slug, self.project.slug)
@@ -247,3 +248,4 @@ def test_has_in_feature(self):
247248
choice[0] for choice in tagged_event_filter["formFields"]["match"]["choices"]
248249
]
249250
assert MatchType.IS_IN in filter_list
251+
assert MatchType.NOT_IN in filter_list

tests/sentry/rules/conditions/test_event_attribute.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,3 +856,15 @@ def test_attr_is_in(self):
856856
data={"match": MatchType.IS_IN, "attribute": "platform", "value": "python"}
857857
)
858858
self.assertDoesNotPass(rule, event)
859+
860+
def test_attr_not_in(self):
861+
event = self.get_event()
862+
rule = self.get_rule(
863+
data={"match": MatchType.NOT_IN, "attribute": "platform", "value": "php, python"}
864+
)
865+
self.assertDoesNotPass(rule, event)
866+
867+
rule = self.get_rule(
868+
data={"match": MatchType.NOT_IN, "attribute": "platform", "value": "python"}
869+
)
870+
self.assertPasses(rule, event)

tests/sentry/rules/conditions/test_tagged_event.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,13 @@ def test_is_in(self):
141141

142142
rule = self.get_rule(data={"match": MatchType.IS_IN, "key": "logger", "value": "foo.bar"})
143143
self.assertPasses(rule, event)
144+
145+
def test_not_in(self):
146+
event = self.get_event()
147+
rule = self.get_rule(
148+
data={"match": MatchType.NOT_IN, "key": "logger", "value": "bar.foo, wee, wow"}
149+
)
150+
self.assertPasses(rule, event)
151+
152+
rule = self.get_rule(data={"match": MatchType.NOT_IN, "key": "logger", "value": "foo.bar"})
153+
self.assertDoesNotPass(rule, event)

tests/sentry/rules/test_match.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,9 @@ def test_does_not_contain(self):
3737
def test_is_in(self):
3838
assert match_values(["sentry.example"], "sentry.example, biz.baz, foo.bar", MatchType.IS_IN)
3939
assert not match_values(["sentry.example"], "biz.baz, foo.bar", MatchType.IS_IN)
40+
41+
def test_not_in(self):
42+
assert match_values(["sentry.example"], "biz.baz, foo.bar", MatchType.NOT_IN)
43+
assert not match_values(
44+
["sentry.example"], "sentry.example, biz.baz, foo.bar", MatchType.NOT_IN
45+
)

0 commit comments

Comments
 (0)