Skip to content

Commit 368de5d

Browse files
authored
ref(rules): Handle action match in delayed rule processor (#69797)
Another follow up to #69167, specifically the [comment about handling any and all action matches](#69167 (comment)) to check if the rule is for "any" or "all" conditions and only add to the list to be fired if it matches accordingly.
1 parent c72d0c5 commit 368de5d

File tree

2 files changed

+73
-22
lines changed

2 files changed

+73
-22
lines changed

src/sentry/rules/processing/delayed_processing.py

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,8 @@ def get_rules_to_groups(rulegroup_to_events: dict[str, str]) -> DefaultDict[int,
6161

6262
def get_rule_to_slow_conditions(
6363
alert_rules: list[Rule],
64-
) -> DefaultDict[Rule, list[MutableMapping[str, str] | None]]:
65-
rule_to_slow_conditions: DefaultDict[Rule, list[MutableMapping[str, str] | None]] = defaultdict(
66-
list
67-
)
64+
) -> DefaultDict[Rule, list[MutableMapping[str, str]]]:
65+
rule_to_slow_conditions: DefaultDict[Rule, list[MutableMapping[str, str]]] = defaultdict(list)
6866
for rule in alert_rules:
6967
slow_conditions = get_slow_conditions(rule)
7068
for condition_data in slow_conditions:
@@ -141,25 +139,34 @@ def get_condition_group_results(
141139

142140
def get_rules_to_fire(
143141
condition_group_results: dict[UniqueCondition, dict[int, int]],
144-
rule_to_slow_conditions: DefaultDict[Rule, list[MutableMapping[str, str] | None]],
142+
rule_to_slow_conditions: DefaultDict[Rule, list[MutableMapping[str, str]]],
145143
rules_to_groups: DefaultDict[int, set[int]],
146144
) -> DefaultDict[Rule, set[int]]:
147145
rules_to_fire = defaultdict(set)
148146
for alert_rule, slow_conditions in rule_to_slow_conditions.items():
149-
for slow_condition in slow_conditions:
150-
if slow_condition:
151-
condition_id = slow_condition.get("id")
152-
condition_interval = slow_condition.get("interval")
153-
target_value = int(str(slow_condition.get("value")))
154-
for condition_data, results in condition_group_results.items():
155-
if (
156-
alert_rule.environment_id == condition_data.environment_id
157-
and condition_id == condition_data.cls_id
158-
and condition_interval == condition_data.interval
159-
):
160-
for group_id in rules_to_groups[alert_rule.id]:
161-
if results[group_id] > target_value:
162-
rules_to_fire[alert_rule].add(group_id)
147+
action_match = alert_rule.data.get("action_match", "any")
148+
for group_id in rules_to_groups[alert_rule.id]:
149+
conditions_matched = 0
150+
for slow_condition in slow_conditions:
151+
unique_condition = UniqueCondition(
152+
str(slow_condition.get("id")),
153+
str(slow_condition.get("interval")),
154+
alert_rule.environment_id,
155+
)
156+
results = condition_group_results.get(unique_condition, {})
157+
if results:
158+
target_value = int(str(slow_condition.get("value")))
159+
if results[group_id] > target_value:
160+
if action_match == "any":
161+
rules_to_fire[alert_rule].add(group_id)
162+
break
163+
conditions_matched += 1
164+
else:
165+
if action_match == "all":
166+
# We failed to match all conditions for this group, skip
167+
break
168+
if action_match == "all" and conditions_matched == len(slow_conditions):
169+
rules_to_fire[alert_rule].add(group_id)
163170
return rules_to_fire
164171

165172

tests/sentry/rules/processing/test_delayed_processing.py

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222

2323

2424
class ProcessDelayedAlertConditionsTest(TestCase, APITestCase, BaseEventFrequencyPercentTest):
25-
def create_event(self, project_id, timestamp, fingerprint, environment=None) -> Event:
25+
def create_event(
26+
self, project_id, timestamp, fingerprint, environment=None, user: bool = True
27+
) -> Event:
2628
data = {
2729
"timestamp": iso_format(timestamp),
2830
"stacktrace": copy.deepcopy(DEFAULT_EVENT_DATA["stacktrace"]),
@@ -70,7 +72,7 @@ def setUp(self):
7072
self.event_frequency_condition3 = self.create_event_frequency_condition(
7173
interval="1h", value=1
7274
)
73-
user_frequency_condition = self.create_event_frequency_condition(
75+
self.user_frequency_condition = self.create_event_frequency_condition(
7476
interval="1m",
7577
id="EventUniqueUserFrequencyCondition",
7678
)
@@ -91,7 +93,7 @@ def setUp(self):
9193
assert self.group1
9294

9395
self.rule2 = self.create_project_rule(
94-
project=self.project, condition_match=[user_frequency_condition]
96+
project=self.project, condition_match=[self.user_frequency_condition]
9597
)
9698
self.event2 = self.create_event(self.project, self.now, "group-2", self.environment.name)
9799
self.create_event(self.project, self.now, "group-2", self.environment.name)
@@ -266,3 +268,45 @@ def test_apply_delayed_two_rules_one_fires(self):
266268
rules = apply_delayed(self.project.id)
267269
assert self.rule1 in rules
268270
assert no_fire_rule not in rules
271+
272+
def test_apply_delayed_action_match_all(self):
273+
"""
274+
Test that a rule with multiple conditions and an action match of 'all' is scheduled to fire
275+
"""
276+
two_conditions_match_all_rule = self.create_project_rule(
277+
project=self.project,
278+
condition_match=[self.event_frequency_condition, self.user_frequency_condition],
279+
environment_id=self.environment.id,
280+
)
281+
event5 = self.create_event(self.project.id, self.now, "group-5", self.environment.name)
282+
self.create_event(self.project.id, self.now, "group-5", self.environment.name)
283+
group5 = event5.group
284+
assert group5
285+
event6 = self.create_event(
286+
self.project.id, self.now, "group-6", self.environment.name, user=False
287+
)
288+
self.create_event(self.project.id, self.now, "group-5", self.environment.name, user=False)
289+
group6 = event6.group
290+
assert group6
291+
assert self.group1
292+
assert self.group2
293+
condition_wont_pass_rule = self.create_project_rule(
294+
project=self.project,
295+
condition_match=[self.create_event_frequency_condition(value=100)],
296+
environment_id=self.environment.id,
297+
)
298+
299+
self.push_to_hash(
300+
self.project.id, two_conditions_match_all_rule.id, group5.id, event5.event_id
301+
)
302+
303+
with patch("sentry.buffer.backend.get_hash", self.redis_buffer.get_hash):
304+
rules = apply_delayed(self.project.id)
305+
assert self.rule1 in rules
306+
assert self.group1.id in rules[self.rule1]
307+
assert self.rule2 in rules
308+
assert self.group2.id in rules[self.rule2]
309+
assert two_conditions_match_all_rule in rules
310+
assert group5.id in rules[two_conditions_match_all_rule]
311+
assert group6.id not in rules[two_conditions_match_all_rule]
312+
assert condition_wont_pass_rule not in rules

0 commit comments

Comments
 (0)