Skip to content

Commit 145897b

Browse files
authored
ref(aci): remove passing in detector to action.trigger attempt 2 (#103099)
1 parent e4c362e commit 145897b

File tree

12 files changed

+153
-78
lines changed

12 files changed

+153
-78
lines changed

src/sentry/api/endpoints/project_rule_actions.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from sentry.workflow_engine.migration_helpers.rule_action import (
2626
translate_rule_data_actions_to_notification_actions,
2727
)
28-
from sentry.workflow_engine.models import Detector, Workflow
28+
from sentry.workflow_engine.models import Workflow
2929
from sentry.workflow_engine.types import WorkflowEventData
3030

3131
logger = logging.getLogger(__name__)
@@ -161,14 +161,6 @@ def execute_future_on_test_event_workflow_engine(
161161
organization=rule.project.organization,
162162
)
163163

164-
detector = Detector(
165-
id=TEST_NOTIFICATION_ID,
166-
project=rule.project,
167-
name=rule.label,
168-
enabled=True,
169-
type=ErrorGroupType.slug,
170-
)
171-
172164
event_data = WorkflowEventData(
173165
event=test_event,
174166
group=test_event.group,
@@ -190,7 +182,7 @@ def execute_future_on_test_event_workflow_engine(
190182
action_exceptions.append(f"An unexpected error occurred. Error ID: '{error_id}'")
191183
continue
192184

193-
action_exceptions.extend(test_fire_action(action, event_data, detector))
185+
action_exceptions.extend(test_fire_action(action, event_data))
194186

195187
status = None
196188
data = None

src/sentry/workflow_engine/endpoints/organization_test_fire_action.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
)
2424
from sentry.workflow_engine.endpoints.utils.test_fire_action import test_fire_action
2525
from sentry.workflow_engine.endpoints.validators.base.action import BaseActionValidator
26-
from sentry.workflow_engine.models import Action, Detector
26+
from sentry.workflow_engine.models import Action
2727
from sentry.workflow_engine.types import WorkflowEventData
2828

2929
logger = logging.getLogger(__name__)
@@ -121,14 +121,6 @@ def test_fire_actions(actions: list[dict[str, Any]], project: Project):
121121
group=test_event.group,
122122
)
123123

124-
detector = Detector(
125-
id=TEST_NOTIFICATION_ID,
126-
project=project,
127-
name="Test Detector",
128-
enabled=True,
129-
type="error",
130-
)
131-
132124
for action_data in actions:
133125
# Create a temporary Action object (not saved to database)
134126
action = Action(
@@ -143,7 +135,7 @@ def test_fire_actions(actions: list[dict[str, Any]], project: Project):
143135
setattr(action, "workflow_id", workflow_id)
144136

145137
# Test fire the action and collect any exceptions
146-
exceptions = test_fire_action(action, workflow_event_data, detector)
138+
exceptions = test_fire_action(action, workflow_event_data)
147139
if exceptions:
148140
action_exceptions.extend(exceptions)
149141

src/sentry/workflow_engine/endpoints/utils/test_fire_action.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,20 @@
33
import sentry_sdk
44

55
from sentry.shared_integrations.exceptions import IntegrationFormError
6-
from sentry.workflow_engine.models import Action, Detector
6+
from sentry.workflow_engine.models import Action
77
from sentry.workflow_engine.types import WorkflowEventData
88

99
logger = logging.getLogger(__name__)
1010

1111

12-
def test_fire_action(
13-
action: Action, event_data: WorkflowEventData, detector: Detector
14-
) -> list[str]:
12+
def test_fire_action(action: Action, event_data: WorkflowEventData) -> list[str]:
1513
"""
1614
This function will fire an action and return a list of exceptions that occurred.
1715
"""
1816
action_exceptions = []
1917
try:
2018
action.trigger(
2119
event_data=event_data,
22-
detector=detector,
2320
)
2421
except Exception as exc:
2522
if isinstance(exc, IntegrationFormError):

src/sentry/workflow_engine/models/action.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import builtins
44
import logging
55
from enum import StrEnum
6-
from typing import TYPE_CHECKING, ClassVar, TypedDict
6+
from typing import ClassVar, TypedDict
77

88
from django.db import models
99
from django.db.models import Q
@@ -23,10 +23,6 @@
2323
from sentry.workflow_engine.registry import action_handler_registry
2424
from sentry.workflow_engine.types import ActionHandler, WorkflowEventData
2525

26-
if TYPE_CHECKING:
27-
from sentry.workflow_engine.models import Detector
28-
29-
3026
logger = logging.getLogger(__name__)
3127

3228

@@ -127,7 +123,11 @@ def get_handler(self) -> builtins.type[ActionHandler]:
127123
action_type = Action.Type(self.type)
128124
return action_handler_registry.get(action_type)
129125

130-
def trigger(self, event_data: WorkflowEventData, detector: Detector) -> None:
126+
def trigger(self, event_data: WorkflowEventData) -> None:
127+
from sentry.workflow_engine.processors.detector import get_detector_from_event_data
128+
129+
detector = get_detector_from_event_data(event_data)
130+
131131
with metrics.timer(
132132
"workflow_engine.action.trigger.execution_time",
133133
tags={"action_type": self.type, "detector_type": detector.type},

src/sentry/workflow_engine/processors/detector.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from sentry.issues.issue_occurrence import IssueOccurrence
1818
from sentry.issues.producer import PayloadType, produce_occurrence_to_kafka
1919
from sentry.locks import locks
20+
from sentry.models.activity import Activity
2021
from sentry.models.group import Group
2122
from sentry.models.project import Project
2223
from sentry.services.eventstore.models import GroupEvent
@@ -139,6 +140,53 @@ def get_detector_by_event(event_data: WorkflowEventData) -> Detector:
139140
return detector
140141

141142

143+
def get_detector_by_group(group: Group) -> Detector:
144+
"""
145+
Returns Detector associated with this group, either based on DetectorGroup,
146+
(project, type), or if those fail, returns the Issue Stream detector.
147+
"""
148+
try:
149+
detector = DetectorGroup.objects.get(group=group).detector
150+
if detector is not None:
151+
return detector
152+
except DetectorGroup.DoesNotExist:
153+
logger.exception(
154+
"DetectorGroup not found for group",
155+
extra={"group_id": group.id},
156+
)
157+
pass
158+
159+
try:
160+
return Detector.objects.get(project_id=group.project_id, type=group.issue_type.slug)
161+
except (Detector.DoesNotExist, Detector.MultipleObjectsReturned):
162+
# return issue stream detector
163+
return Detector.objects.get(project_id=group.project_id, type=IssueStreamGroupType.slug)
164+
165+
166+
def get_detector_from_event_data(event_data: WorkflowEventData) -> Detector:
167+
try:
168+
if isinstance(event_data.event, GroupEvent):
169+
return get_detector_by_event(event_data)
170+
elif isinstance(event_data.event, Activity):
171+
return get_detector_by_group(event_data.group)
172+
else:
173+
raise TypeError(f"Cannot determine the detector from {type(event_data.event)}.")
174+
except Detector.DoesNotExist:
175+
logger.exception(
176+
"Detector not found for event data",
177+
extra={
178+
"type": type(event_data.event),
179+
"id": (
180+
event_data.event.event_id
181+
if isinstance(event_data.event, GroupEvent)
182+
else event_data.event.id
183+
),
184+
"group_id": event_data.group.id,
185+
},
186+
)
187+
raise
188+
189+
142190
class _SplitEvents(NamedTuple):
143191
events_with_occurrences: list[tuple[GroupEvent, int]]
144192
error_events: list[GroupEvent]

src/sentry/workflow_engine/tasks/actions.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ def build_trigger_action_task_params(
6767
@retry(timeouts=True, raise_on_no_retries=False, ignore_and_capture=Action.DoesNotExist)
6868
def trigger_action(
6969
action_id: int,
70-
detector_id: int,
7170
workflow_id: int,
7271
event_id: str | None,
7372
activity_id: int | None,
@@ -76,8 +75,10 @@ def trigger_action(
7675
group_state: GroupState,
7776
has_reappeared: bool,
7877
has_escalated: bool,
78+
detector_id: int | None = None,
7979
) -> None:
8080
from sentry.notifications.notification_action.utils import should_fire_workflow_actions
81+
from sentry.workflow_engine.processors.detector import get_detector_from_event_data
8182

8283
# XOR check to ensure exactly one of event_id or activity_id is provided
8384
if (event_id is not None) == (activity_id is not None):
@@ -88,19 +89,14 @@ def trigger_action(
8889
raise ValueError("Exactly one of event_id or activity_id must be provided")
8990

9091
action = Action.objects.annotate(workflow_id=Value(workflow_id)).get(id=action_id)
91-
detector = Detector.objects.get(id=detector_id)
9292

93-
metrics.incr(
94-
"workflow_engine.tasks.trigger_action_task_started",
95-
tags={"action_type": action.type, "detector_type": detector.type},
96-
sample_rate=1.0,
97-
)
98-
99-
project_id = detector.project_id
93+
# TODO: remove detector usage from this task
94+
detector: Detector | None = None
95+
if detector_id is not None:
96+
detector = Detector.objects.get(id=detector_id)
10097

10198
if event_id is not None:
10299
event_data = build_workflow_event_data_from_event(
103-
project_id=project_id,
104100
event_id=event_id,
105101
group_id=group_id,
106102
workflow_id=workflow_id,
@@ -109,7 +105,6 @@ def trigger_action(
109105
has_reappeared=has_reappeared,
110106
has_escalated=has_escalated,
111107
)
112-
113108
elif activity_id is not None:
114109
event_data = build_workflow_event_data_from_activity(
115110
activity_id=activity_id, group_id=group_id
@@ -122,6 +117,15 @@ def trigger_action(
122117
)
123118
raise ValueError("Exactly one of event_id or activity_id must be provided")
124119

120+
if not detector:
121+
detector = get_detector_from_event_data(event_data)
122+
123+
metrics.incr(
124+
"workflow_engine.tasks.trigger_action_task_started",
125+
tags={"action_type": action.type, "detector_type": detector.type},
126+
sample_rate=1.0,
127+
)
128+
125129
should_trigger_actions = should_fire_workflow_actions(
126130
detector.project.organization, event_data.group.type
127131
)
@@ -130,7 +134,7 @@ def trigger_action(
130134
# Set up a timeout grouping context because we want to make sure any Sentry timeout reporting
131135
# in this scope is grouped properly.
132136
with timeout_grouping_context(action.type):
133-
action.trigger(event_data, detector)
137+
action.trigger(event_data)
134138
else:
135139
logger.info(
136140
"workflow_engine.triggered_actions.dry-run",

src/sentry/workflow_engine/tasks/utils.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ def __init__(self, event_id: str, project_id: int):
6464

6565
@scopedstats.timer()
6666
def build_workflow_event_data_from_event(
67-
project_id: int,
6867
event_id: str,
6968
group_id: int,
7069
workflow_id: int | None = None,
@@ -78,14 +77,14 @@ def build_workflow_event_data_from_event(
7877
This method handles all the database fetching and object construction logic.
7978
Raises EventNotFoundError if the event is not found.
8079
"""
81-
80+
group = Group.objects.get_from_cache(id=group_id)
81+
project_id = group.project_id
8282
event = fetch_event(event_id, project_id)
8383
if event is None:
8484
raise EventNotFoundError(event_id, project_id)
8585

8686
occurrence = IssueOccurrence.fetch(occurrence_id, project_id) if occurrence_id else None
8787

88-
group = Group.objects.get_from_cache(id=group_id)
8988
group_event = GroupEvent.from_event(event, group)
9089
group_event.occurrence = occurrence
9190

src/sentry/workflow_engine/tasks/workflows.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,14 @@ def process_workflow_activity(activity_id: int, group_id: int, detector_id: int)
9696
on_silent=DataConditionGroup.DoesNotExist,
9797
)
9898
def process_workflows_event(
99-
project_id: int,
10099
event_id: str,
101100
group_id: int,
102101
occurrence_id: str | None,
103102
group_state: GroupState,
104103
has_reappeared: bool,
105104
has_escalated: bool,
106105
start_timestamp_seconds: float | None = None,
106+
project_id: int | None = None,
107107
**kwargs: dict[str, Any],
108108
) -> None:
109109
from sentry.workflow_engine.processors.workflow import process_workflows
@@ -114,7 +114,6 @@ def process_workflows_event(
114114
with recorder.record():
115115
try:
116116
event_data = build_workflow_event_data_from_event(
117-
project_id=project_id,
118117
event_id=event_id,
119118
group_id=group_id,
120119
occurrence_id=occurrence_id,

tests/sentry/workflow_engine/endpoints/test_organization_test_fire_action.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from sentry.testutils.silo import assume_test_silo_mode
2222
from sentry.testutils.skips import requires_snuba
2323
from sentry.workflow_engine.models import Action
24+
from sentry.workflow_engine.typings.grouptype import IssueStreamGroupType
2425
from tests.sentry.workflow_engine.test_base import BaseWorkflowTest
2526

2627
pytestmark = [requires_snuba]
@@ -34,6 +35,10 @@ def setUp(self) -> None:
3435
super().setUp()
3536
self.login_as(self.user)
3637
self.project = self.create_project(organization=self.organization)
38+
self.detector = self.create_detector(project=self.project)
39+
self.issue_stream_detector = self.create_detector(
40+
project=self.project, type=IssueStreamGroupType.slug
41+
)
3742
self.workflow = self.create_workflow()
3843

3944
def setup_pd_service(self) -> PagerDutyServiceDict:
@@ -94,7 +99,7 @@ def test_pagerduty_action(
9499
assert mock_send_trigger.call_count == 1
95100
pagerduty_data = mock_send_trigger.call_args.kwargs.get("data")
96101
assert pagerduty_data is not None
97-
assert pagerduty_data["payload"]["summary"].startswith("[Test Detector]:")
102+
assert pagerduty_data["payload"]["summary"].startswith(f"[{self.detector.name}]:")
98103

99104
@mock.patch.object(NotifyEventAction, "after")
100105
@mock.patch(

0 commit comments

Comments
 (0)