-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(aci): event frequency condition handler #82551
Conversation
Codecov ReportAttention: Patch coverage is ✅ All tests successful. No failed tests found. Additional details and impacted files@@ Coverage Diff @@
## master #82551 +/- ##
==========================================
- Coverage 87.52% 87.49% -0.03%
==========================================
Files 9488 9411 -77
Lines 537867 537366 -501
Branches 21174 21174
==========================================
- Hits 470750 470179 -571
- Misses 66760 66830 +70
Partials 357 357 |
src/sentry/workflow_engine/handlers/condition/event_frequency_handlers.py
Outdated
Show resolved
Hide resolved
src/sentry/workflow_engine/handlers/condition/event_frequency_handlers.py
Outdated
Show resolved
Hide resolved
def batch_query( | ||
self, group_ids: set[int], start: datetime, end: datetime, environment_id: int | ||
) -> dict[int, int]: | ||
batch_sums: dict[int, int] = defaultdict(int) | ||
groups = Group.objects.filter(id__in=group_ids).values( | ||
"id", "type", "project_id", "project__organization_id" | ||
) | ||
error_issue_ids, generic_issue_ids = self.get_error_and_generic_group_ids(groups) | ||
organization_id = self.get_value_from_groups(groups, "project__organization_id") | ||
|
||
if error_issue_ids and organization_id: | ||
error_sums = self.get_chunked_result( | ||
tsdb_function=tsdb.backend.get_sums, | ||
model=get_issue_tsdb_group_model(GroupCategory.ERROR), | ||
group_ids=error_issue_ids, | ||
organization_id=organization_id, | ||
start=start, | ||
end=end, | ||
environment_id=environment_id, | ||
referrer_suffix="batch_alert_event_frequency", | ||
) | ||
batch_sums.update(error_sums) | ||
|
||
if generic_issue_ids and organization_id: | ||
generic_sums = self.get_chunked_result( | ||
tsdb_function=tsdb.backend.get_sums, | ||
# this isn't necessarily performance, just any non-error category | ||
model=get_issue_tsdb_group_model(GroupCategory.PERFORMANCE), | ||
group_ids=generic_issue_ids, | ||
organization_id=organization_id, | ||
start=start, | ||
end=end, | ||
environment_id=environment_id, | ||
referrer_suffix="batch_alert_event_frequency", | ||
) | ||
batch_sums.update(generic_sums) | ||
|
||
return batch_sums |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
copied from
class EventFrequencyCondition(BaseEventFrequencyCondition): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 - i wonder if we should have these defined with the DataSources? I'm trying to think about how this module will grow and change over time.
I think it makes sense to have it here, but as i think about what would we need to change if we do Snuba Query -> EAP, we'd have to make a lot of changes to the workflow engine to support that here. If we had this setup by type, we could move the code into the area and hook in here with a registry.
I'm not sold on ^ as a design, but i'm a little worried about how many condition handlers we'll have here, and how tightly coupled they'll be to the workflow engine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
discussed offline, we should probably discuss how these conditions will look for the different product verticals, especially for those where a single occurrence != there was an issue at X time (crons, metric issues, uptime) 🤔
although i will say it does seem like all kinds of issues go through this logic today.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i generalized it more to account for each kind of GroupCategory
assert dc.condition_group == dcg | ||
|
||
|
||
class EventFrequencyQueryTest(EventFrequencyQueryTestBase): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the same as
class EventFrequencyQueryTest(EventFrequencyQueryTestBase): |
src/sentry/workflow_engine/handlers/condition/event_frequency_handlers.py
Outdated
Show resolved
Hide resolved
888db48
to
eb54786
Compare
src/sentry/workflow_engine/handlers/condition/event_frequency_handlers.py
Outdated
Show resolved
Hide resolved
src/sentry/workflow_engine/handlers/condition/event_frequency_handlers.py
Outdated
Show resolved
Hide resolved
def batch_query( | ||
self, group_ids: set[int], start: datetime, end: datetime, environment_id: int | ||
) -> dict[int, int]: | ||
batch_sums: dict[int, int] = defaultdict(int) | ||
groups = Group.objects.filter(id__in=group_ids).values( | ||
"id", "type", "project_id", "project__organization_id" | ||
) | ||
error_issue_ids, generic_issue_ids = self.get_error_and_generic_group_ids(groups) | ||
organization_id = self.get_value_from_groups(groups, "project__organization_id") | ||
|
||
if error_issue_ids and organization_id: | ||
error_sums = self.get_chunked_result( | ||
tsdb_function=tsdb.backend.get_sums, | ||
model=get_issue_tsdb_group_model(GroupCategory.ERROR), | ||
group_ids=error_issue_ids, | ||
organization_id=organization_id, | ||
start=start, | ||
end=end, | ||
environment_id=environment_id, | ||
referrer_suffix="batch_alert_event_frequency", | ||
) | ||
batch_sums.update(error_sums) | ||
|
||
if generic_issue_ids and organization_id: | ||
generic_sums = self.get_chunked_result( | ||
tsdb_function=tsdb.backend.get_sums, | ||
# this isn't necessarily performance, just any non-error category | ||
model=get_issue_tsdb_group_model(GroupCategory.PERFORMANCE), | ||
group_ids=generic_issue_ids, | ||
organization_id=organization_id, | ||
start=start, | ||
end=end, | ||
environment_id=environment_id, | ||
referrer_suffix="batch_alert_event_frequency", | ||
) | ||
batch_sums.update(generic_sums) | ||
|
||
return batch_sums |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 - i wonder if we should have these defined with the DataSources? I'm trying to think about how this module will grow and change over time.
I think it makes sense to have it here, but as i think about what would we need to change if we do Snuba Query -> EAP, we'd have to make a lot of changes to the workflow engine to support that here. If we had this setup by type, we could move the code into the area and hook in here with a registry.
I'm not sold on ^ as a design, but i'm a little worried about how many condition handlers we'll have here, and how tightly coupled they'll be to the workflow engine.
tests/sentry/workflow_engine/handlers/condition/test_event_frequency_handlers.py
Outdated
Show resolved
Hide resolved
src/sentry/workflow_engine/handlers/condition/event_frequency_handlers.py
Outdated
Show resolved
Hide resolved
0a6dbc1
to
21456b7
Compare
EVENT_FREQUENCY = "event_frequency" | ||
EVENT_UNIQUE_USER_FREQUENCY = "event_unique_user_frequency" | ||
EVENT_FREQUENCY_PERCENT = "event_frequency_percent" | ||
EVENT_UNIQUE_USER_FREQUENCY_WITH_CONDITIONS = "event_unique_user_frequency_with_conditions" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 do we need different condition handlers for each of these to access the specific data to compare against? Are there more to come for event frequency? How would we know which one of these conditions to use in the UI from a single UI component?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these are what the frequency conditions can look like in the UI today... today we do some funky stuff to get it to look like this
https://github.com/getsentry/sentry/blob/0d73e5379eca69ae2da588836732ef1add3aee91/static/app/views/alerts/utils/constants.tsx#L1-L35
but there are actually only these 4 conditions/handlers for event frequency conditions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
after looking at this more i realized each condition class basically handles 2 conditions: event count and percent frequency. i added accounting for this in a single condition class because we are able to share some queries in the percent case with event count in delayed processing (percent has 2 queries, 1 of which can overlap with even count)
878b423
to
5a74923
Compare
src/sentry/workflow_engine/handlers/condition/event_frequency_handlers.py
Outdated
Show resolved
Hide resolved
): | ||
@staticmethod | ||
def evaluate_value(value: list[int], comparison: Any) -> DataConditionResult: | ||
comparison_type = comparison["comparison_type"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you type comparison
as a dict?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 we should probably add a json schema to the comparison
field? it'd be nice if we could do this in the base frequency class to ensure these new conditions are correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because this is so generic, comparison can be anything that reasonably splats into models.JSONField
...
990504d
to
8d75328
Compare
src/sentry/workflow_engine/handlers/condition/event_frequency_handlers.py
Outdated
Show resolved
Hide resolved
@condition_handler_registry.register(Condition.EVENT_FREQUENCY_COUNT) | ||
class EventFrequencyCountHandler(EventFrequencyConditionHandler, DataConditionHandler[int]): | ||
@staticmethod | ||
def evaluate_value(value: int, comparison: Any) -> DataConditionResult: | ||
return value > comparison["value"] | ||
|
||
|
||
@condition_handler_registry.register(Condition.EVENT_FREQUENCY_PERCENT) | ||
class EventFrequencyPercentHandler(EventFrequencyConditionHandler, DataConditionHandler[list[int]]): | ||
@staticmethod | ||
def evaluate_value(value: list[int], comparison: Any) -> DataConditionResult: | ||
if len(value) != 2: | ||
return False | ||
return percent_increase(value[0], value[1]) > comparison["value"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the main part
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@saponifi3d i think i need to add some validation on all DataCondition.comparison fields, will think about it in an upcoming PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall, lgtm, it'd be nice if we address the comparison
schema comment before merging though.
): | ||
@staticmethod | ||
def evaluate_value(value: list[int], comparison: Any) -> DataConditionResult: | ||
comparison_type = comparison["comparison_type"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 we should probably add a json schema to the comparison
field? it'd be nice if we could do this in the base frequency class to ensure these new conditions are correct.
from sentry.workflow_engine.types import DataConditionHandler, DataConditionResult | ||
|
||
|
||
class EventFrequencyConditionHandler(BaseEventFrequencyConditionHandler): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: if we're going to have all these classes in 1 file, might as well include the base here too (otherwise, i'd split each of these classes as a separate file, but that's a bit JS-y too)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there are 4 existing condition classes that are related but represent 2 conditions (count and percent change), and i am grouping together these 2 very similar conditions
self.assert_slow_cond_passes(dc, 1001) | ||
self.assert_slow_cond_does_not_pass(dc, 999) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.assert_slow_cond_passes(dc, 1001) | |
self.assert_slow_cond_does_not_pass(dc, 999) | |
self.assert_slow_cond_passes(dc, [1001]) | |
self.assert_slow_cond_does_not_pass(dc, [999]) |
^ should fix the mypy error.
ffa1ca5
to
e7504cf
Compare
Add base event frequency condition handler, event frequency count and event frequency percent condition handler.
Each existing condition class technically handles two distinct condition types: getting the count for an interval, and comparing percent increase of the count for an interval with the same interval X time in the past.
The base condition handler borrows elements from
BaseEventFrequencyCondition
related to making the bulk Snuba query -- it might not yet contain everything necessary for delayed processing, but is enough for tests.