Skip to content

Commit 333cd43

Browse files
kconssaponifi3dgetsantry[bot]
authored andcommitted
fix(aci): Use filter_recently_fired_workflow_actions exclusively (#93382)
Use WorkflowActionGroupStatus for our recency filtering rather than ActionGroupStatus. While we're at it, remove any remaining active uses of ActionGroupStatus. --------- Co-authored-by: Josh Callender <[email protected]> Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
1 parent da97eca commit 333cd43

File tree

11 files changed

+33
-214
lines changed

11 files changed

+33
-214
lines changed

src/sentry/incidents/endpoints/serializers/workflow_engine_detector.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@
2424
from sentry.users.services.user.service import user_service
2525
from sentry.workflow_engine.models import (
2626
Action,
27-
ActionGroupStatus,
2827
AlertRuleDetector,
2928
DataCondition,
3029
DataConditionGroupAction,
3130
DataSourceDetector,
3231
Detector,
3332
)
33+
from sentry.workflow_engine.models.workflow_action_group_status import WorkflowActionGroupStatus
3434
from sentry.workflow_engine.types import DetectorPriorityLevel
3535

3636

@@ -177,20 +177,22 @@ def add_latest_incident(
177177
for action_ids in detector_to_action_ids.values():
178178
all_action_ids.extend(action_ids)
179179

180-
action_group_statuses = ActionGroupStatus.objects.filter(action_id__in=all_action_ids)
180+
wf_action_group_statuses = WorkflowActionGroupStatus.objects.filter(
181+
action_id__in=all_action_ids
182+
)
181183

182-
detector_to_group_ids = defaultdict(list)
183-
for action_group_status in action_group_statuses:
184+
detector_to_group_ids = defaultdict(set)
185+
for wf_action_group_status in wf_action_group_statuses:
184186
for detector, action_ids in detector_to_action_ids.items():
185-
if action_group_status.action_id in action_ids:
186-
detector_to_group_ids[detector].append(action_group_status.group_id)
187+
if wf_action_group_status.action_id in action_ids:
188+
detector_to_group_ids[detector].add(wf_action_group_status.group_id)
187189

188190
open_periods = None
189-
group_ids = [action_group_status.group_id for action_group_status in action_group_statuses]
191+
group_ids = {
192+
wf_action_group_status.group_id for wf_action_group_status in wf_action_group_statuses
193+
}
190194
if group_ids:
191-
open_periods = GroupOpenPeriod.objects.filter(
192-
group__in=[group_id for group_id in group_ids]
193-
)
195+
open_periods = GroupOpenPeriod.objects.filter(group__in=group_ids)
194196

195197
for detector in detectors.values():
196198
# TODO: this serializer is half baked

src/sentry/incidents/endpoints/serializers/workflow_engine_incident.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
from sentry.users.services.user.model import RpcUser
2828
from sentry.workflow_engine.models import (
2929
Action,
30-
ActionGroupStatus,
3130
AlertRuleDetector,
3231
DataCondition,
3332
DataConditionGroupAction,
@@ -37,6 +36,7 @@
3736
IncidentGroupOpenPeriod,
3837
WorkflowDataConditionGroup,
3938
)
39+
from sentry.workflow_engine.models.workflow_action_group_status import WorkflowActionGroupStatus
4040

4141

4242
class WorkflowEngineIncidentSerializer(Serializer):
@@ -191,14 +191,14 @@ def get_open_period_activities(self, open_period: GroupOpenPeriod) -> list[dict[
191191
def get_open_periods_to_detectors(
192192
self, open_periods: Sequence[GroupOpenPeriod]
193193
) -> dict[GroupOpenPeriod, Detector]:
194-
action_group_statuses = ActionGroupStatus.objects.filter(
194+
wf_action_group_statuses = WorkflowActionGroupStatus.objects.filter(
195195
group__in=[open_period.group for open_period in open_periods]
196196
)
197197
open_periods_to_actions: DefaultDict[GroupOpenPeriod, Action] = defaultdict()
198198
for open_period in open_periods:
199-
for action_group_status in action_group_statuses:
200-
if action_group_status.group == open_period.group:
201-
open_periods_to_actions[open_period] = action_group_status.action
199+
for wf_action_group_status in wf_action_group_statuses:
200+
if wf_action_group_status.group == open_period.group:
201+
open_periods_to_actions[open_period] = wf_action_group_status.action
202202
break
203203

204204
dcgas = DataConditionGroupAction.objects.filter(

src/sentry/testutils/helpers/backups.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110
from sentry.users.models.userrole import UserRole, UserRoleUser
111111
from sentry.utils import json
112112
from sentry.workflow_engine.models import Action, DataConditionAlertRuleTrigger, DataConditionGroup
113-
from sentry.workflow_engine.models.action_group_status import ActionGroupStatus
113+
from sentry.workflow_engine.models.workflow_action_group_status import WorkflowActionGroupStatus
114114

115115
__all__ = [
116116
"export_to_file",
@@ -669,7 +669,9 @@ def create_exhaustive_organization(
669669

670670
self.create_alert_rule_detector(detector=detector, alert_rule_id=alert.id)
671671
self.create_alert_rule_workflow(workflow=workflow, alert_rule_id=alert.id)
672-
ActionGroupStatus.objects.create(action=send_notification_action, group=group)
672+
WorkflowActionGroupStatus.objects.create(
673+
action=send_notification_action, group=group, workflow=workflow
674+
)
673675
DataConditionAlertRuleTrigger.objects.create(
674676
data_condition=data_condition, alert_rule_trigger_id=trigger.id
675677
)

src/sentry/workflow_engine/models/action_group_status.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@region_silo_model
88
class ActionGroupStatus(DefaultFieldsModel):
99
"""
10+
DEPRECATED: Use WorkflowActionGroupStatus instead. This will be removed soon.
1011
Stores when an action last fired for a Group.
1112
"""
1213

src/sentry/workflow_engine/processors/action.py

Lines changed: 0 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22
from collections import defaultdict
33
from datetime import datetime, timedelta
44

5-
from django.db import models
6-
from django.db.models import DurationField, ExpressionWrapper, F, IntegerField, Value
7-
from django.db.models.fields.json import KeyTextTransform
8-
from django.db.models.functions import Cast, Coalesce
95
from django.utils import timezone
106

117
from sentry import features
@@ -24,7 +20,6 @@
2420
from sentry.rules.actions.services import PluginService
2521
from sentry.workflow_engine.models import (
2622
Action,
27-
ActionGroupStatus,
2823
DataCondition,
2924
DataConditionGroup,
3025
DataConditionGroupAction,
@@ -41,41 +36,6 @@
4136
EnqueuedAction = tuple[DataConditionGroup, list[DataCondition]]
4237

4338

44-
def get_action_last_updated_statuses(now: datetime, actions: BaseQuerySet[Action], group: Group):
45-
# Annotate the actions with the amount of time since the last update
46-
statuses = ActionGroupStatus.objects.filter(group=group, action__in=actions)
47-
48-
check_workflow_frequency = Cast(
49-
Coalesce(
50-
KeyTextTransform(
51-
"frequency",
52-
F(
53-
"action__dataconditiongroupaction__condition_group__workflowdataconditiongroup__workflow__config"
54-
),
55-
),
56-
Value("0"), # default 0
57-
),
58-
output_field=IntegerField(),
59-
)
60-
61-
frequency_in_minutes = ExpressionWrapper(
62-
F("frequency") * timedelta(minutes=1), # convert to timedelta
63-
output_field=DurationField(),
64-
)
65-
66-
time_since_last_update = ExpressionWrapper(
67-
Value(now) - F("date_updated"), output_field=DurationField()
68-
)
69-
70-
statuses = statuses.annotate(
71-
frequency=check_workflow_frequency,
72-
frequency_minutes=frequency_in_minutes,
73-
difference=time_since_last_update,
74-
)
75-
76-
return statuses
77-
78-
7939
def create_workflow_fire_histories(
8040
actions_to_fire: BaseQuerySet[Action], event_data: WorkflowEventData
8141
) -> list[WorkflowFireHistory]:
@@ -101,52 +61,6 @@ def create_workflow_fire_histories(
10161
return WorkflowFireHistory.objects.bulk_create(fire_histories)
10262

10363

104-
# TODO(cathy): only reinforce workflow frequency for certain issue types
105-
def filter_recently_fired_actions(
106-
filtered_action_groups: set[DataConditionGroup], event_data: WorkflowEventData
107-
) -> BaseQuerySet[Action]:
108-
# get the actions for any of the triggered data condition groups
109-
actions = (
110-
Action.objects.filter(dataconditiongroupaction__condition_group__in=filtered_action_groups)
111-
.annotate(
112-
workflow_id=models.F(
113-
"dataconditiongroupaction__condition_group__workflowdataconditiongroup__workflow__id"
114-
)
115-
)
116-
.distinct()
117-
)
118-
119-
group = event_data.event.group
120-
121-
now = timezone.now()
122-
statuses = get_action_last_updated_statuses(now, actions, group)
123-
124-
actions_without_statuses = actions.exclude(id__in=statuses.values_list("action_id", flat=True))
125-
actions_to_include = set(
126-
statuses.filter(difference__gt=F("frequency_minutes")).values_list("action_id", flat=True)
127-
)
128-
129-
ActionGroupStatus.objects.filter(
130-
action__in=actions_to_include, group=group, date_updated__lt=now
131-
).order_by("id").update(date_updated=now)
132-
ActionGroupStatus.objects.bulk_create(
133-
[
134-
ActionGroupStatus(action=action, group=group, date_updated=now)
135-
for action in actions_without_statuses
136-
],
137-
batch_size=1000,
138-
ignore_conflicts=True,
139-
)
140-
141-
actions_without_statuses_ids = {action.id for action in actions_without_statuses}
142-
filtered_actions = actions.filter(id__in=actions_to_include | actions_without_statuses_ids)
143-
144-
# dual write to WorkflowActionGroupStatus, ignoring results for now until they are canonical
145-
_ = filter_recently_fired_workflow_actions(filtered_action_groups, event_data)
146-
147-
return filtered_actions
148-
149-
15064
def get_workflow_action_group_statuses(
15165
action_to_workflows_ids: dict[int, set[int]], group: Group, workflow_ids: set[int]
15266
) -> dict[int, list[WorkflowActionGroupStatus]]:

src/sentry/workflow_engine/processors/delayed_workflow.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
)
4949
from sentry.workflow_engine.processors.action import (
5050
create_workflow_fire_histories,
51-
filter_recently_fired_actions,
51+
filter_recently_fired_workflow_actions,
5252
)
5353
from sentry.workflow_engine.processors.data_condition_group import (
5454
evaluate_data_conditions,
@@ -483,7 +483,7 @@ def fire_actions_for_groups(
483483
threshold_seconds=1,
484484
):
485485
workflows_actions = evaluate_workflows_action_filters(workflows, event_data)
486-
filtered_actions = filter_recently_fired_actions(
486+
filtered_actions = filter_recently_fired_workflow_actions(
487487
action_filters | workflows_actions, event_data
488488
)
489489
create_workflow_fire_histories(filtered_actions, event_data)

src/sentry/workflow_engine/processors/workflow.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
)
2020
from sentry.workflow_engine.processors.action import (
2121
create_workflow_fire_histories,
22-
filter_recently_fired_actions,
22+
filter_recently_fired_workflow_actions,
2323
)
2424
from sentry.workflow_engine.processors.contexts.workflow_event_context import (
2525
WorkflowEventContext,
@@ -283,7 +283,7 @@ def process_workflows(event_data: WorkflowEventData) -> set[Workflow]:
283283
return set()
284284

285285
actions_to_trigger = evaluate_workflows_action_filters(triggered_workflows, event_data)
286-
actions = filter_recently_fired_actions(actions_to_trigger, event_data)
286+
actions = filter_recently_fired_workflow_actions(actions_to_trigger, event_data)
287287
if not actions:
288288
# If there aren't any actions on the associated workflows, there's nothing to trigger
289289
return triggered_workflows

tests/sentry/incidents/serializers/test_workflow_engine_base.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
migrate_metric_data_conditions,
1818
migrate_resolve_threshold_data_condition,
1919
)
20-
from sentry.workflow_engine.models import ActionGroupStatus, IncidentGroupOpenPeriod
20+
from sentry.workflow_engine.models import IncidentGroupOpenPeriod
21+
from sentry.workflow_engine.models.workflow_action_group_status import WorkflowActionGroupStatus
2122

2223

2324
@freeze_time("2024-12-11 03:21:34")
@@ -133,7 +134,10 @@ def add_incident_data(self) -> None:
133134

134135
self.group.priority = PriorityLevel.HIGH
135136
self.group.save()
136-
ActionGroupStatus.objects.create(action=self.critical_action, group=self.group)
137+
workflow = self.create_workflow()
138+
WorkflowActionGroupStatus.objects.create(
139+
action=self.critical_action, group=self.group, workflow=workflow
140+
)
137141
self.group_open_period = GroupOpenPeriod.objects.get(
138142
group=self.group, project=self.detector.project
139143
)

tests/sentry/workflow_engine/models/test_action_group_status.py

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)