Skip to content

Commit a558175

Browse files
committed
refactor to have a consistent return type from the evaluation
1 parent bfda71a commit a558175

File tree

3 files changed

+41
-33
lines changed

3 files changed

+41
-33
lines changed

src/sentry/workflow_engine/processors/workflow.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from django.db.models import Q
1010

1111
from sentry import features
12-
from sentry.db.models.manager.base_query_set import BaseQuerySet
1312
from sentry.models.activity import Activity
1413
from sentry.models.environment import Environment
1514
from sentry.services.eventstore.models import GroupEvent
@@ -34,7 +33,7 @@
3433
)
3534
from sentry.workflow_engine.processors.detector import get_detector_by_event
3635
from sentry.workflow_engine.processors.workflow_fire_history import create_workflow_fire_histories
37-
from sentry.workflow_engine.types import WorkflowEventData, WorkflowNotProcessable
36+
from sentry.workflow_engine.types import WorkflowEvaluation, WorkflowEventData
3837
from sentry.workflow_engine.utils import log_context, scopedstats
3938
from sentry.workflow_engine.utils.metrics import metrics_incr
4039

@@ -465,7 +464,7 @@ def process_workflows(
465464
event_data: WorkflowEventData,
466465
event_start_time: datetime,
467466
detector: Detector | None = None,
468-
) -> tuple[set[Workflow], BaseQuerySet[Action]] | WorkflowNotProcessable:
467+
) -> WorkflowEvaluation:
469468
"""
470469
This method will get the detector based on the event, and then gather the associated workflows.
471470
Next, it will evaluate the "when" (or trigger) conditions for each workflow, if the conditions are met,
@@ -479,7 +478,7 @@ def process_workflows(
479478
fire_actions,
480479
)
481480

482-
# This dictionary stores the data for WorkflowNotProcessable
481+
# This dictionary stores the data for WorkflowEvaluation
483482
# and is eventually unpacked to pass into the frozen dataclass
484483
# TODO -- should this be a workflow event context data instead?
485484
extra_data: dict[str, Any] = {
@@ -504,7 +503,8 @@ def process_workflows(
504503
)
505504
)
506505
except Detector.DoesNotExist:
507-
return WorkflowNotProcessable(
506+
return WorkflowEvaluation(
507+
tainted=True,
508508
message="No Detectors associated with the issue were found",
509509
**extra_data,
510510
)
@@ -523,7 +523,8 @@ def process_workflows(
523523
)
524524
)
525525
except Environment.DoesNotExist:
526-
return WorkflowNotProcessable(
526+
return WorkflowEvaluation(
527+
tainted=True,
527528
message="Environment for event not found",
528529
**extra_data,
529530
)
@@ -535,7 +536,8 @@ def process_workflows(
535536
extra_data["workflows"] = workflows
536537

537538
if not workflows:
538-
return WorkflowNotProcessable(
539+
return WorkflowEvaluation(
540+
tainted=True,
539541
message="No workflows are associated with the detector in the event",
540542
**extra_data,
541543
)
@@ -547,7 +549,8 @@ def process_workflows(
547549
extra_data["triggered_workflows"] = triggered_workflows
548550

549551
if not triggered_workflows and not queue_items_by_workflow_id:
550-
return WorkflowNotProcessable(
552+
return WorkflowEvaluation(
553+
tainted=True,
551554
message="No items were triggered or queued for slow evaluation",
552555
**extra_data,
553556
)
@@ -570,7 +573,8 @@ def process_workflows(
570573
}
571574

572575
if not actions:
573-
return WorkflowNotProcessable(
576+
return WorkflowEvaluation(
577+
tainted=True,
574578
message="No actions to evaluate; filtered or not triggered",
575579
**extra_data,
576580
)
@@ -586,18 +590,18 @@ def process_workflows(
586590
)
587591

588592
fire_actions(actions, detector, event_data)
589-
return triggered_workflows, actions
593+
return WorkflowEvaluation(tainted=False, **extra_data)
590594

591595

592596
def process_workflows_with_logs(
593597
batch_client: DelayedWorkflowClient,
594598
event_data: WorkflowEventData,
595599
event_start_time: datetime,
596600
detector: Detector | None = None,
597-
) -> None:
601+
) -> WorkflowEvaluation:
598602
"""
599603
This method is used to create improved logging for `process_workflows`,
600-
where we can capture the `WorkflowNotProcessable` results, and log them.
604+
where we can capture the `WorkflowEvaluation` results, and log them.
601605
602606
This should create a log for each issue, so we can determine why a workflow
603607
did or did not trigger.
@@ -608,7 +612,7 @@ def process_workflows_with_logs(
608612

609613
evaluation = process_workflows(batch_client, event_data, event_start_time, detector)
610614

611-
if isinstance(evaluation, WorkflowNotProcessable):
615+
if isinstance(evaluation, WorkflowEvaluation):
612616
# Attempted to evaluate, but no actions were triggered.
613617

614618
if evaluation.triggered_workflows is None:
@@ -621,8 +625,6 @@ def process_workflows_with_logs(
621625
logger.info("process_workflows.evaluation.workflow.triggered", extra=asdict(evaluation))
622626
else:
623627
# Workflow was fully processed, and actions were triggered
624-
workflows, actions = evaluation
625-
logger.info(
626-
"process_workflows.evaluation.actions.triggered",
627-
extra={"workflows": workflows, "actions": actions},
628-
)
628+
logger.info("process_workflows.evaluation.actions.triggered", extra=asdict(evaluation))
629+
630+
return evaluation

src/sentry/workflow_engine/types.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,22 @@ class WorkflowEventData:
7373

7474

7575
@dataclass(frozen=True)
76-
class WorkflowNotProcessable:
77-
group_event: GroupEvent
78-
message: str
76+
class WorkflowEvaluation:
77+
"""
78+
The result of when a workflow is evaluated,
79+
the only required property is the `tainted` field,
80+
this is used to determine if the evaluation was
81+
successfully executed or not.
82+
83+
The rest of the data is meant to include debug information
84+
so we can easily determine why a workflow was or was not triggered.
85+
"""
86+
87+
tainted: bool
88+
89+
group_event: GroupEvent | None = None
90+
message: str | None = None
91+
7992
actions: list[Action] | None = None
8093
workflows: list[Workflow] | None = None
8194
triggered_actions: list[Action] | None = None

tests/sentry/workflow_engine/processors/test_workflow.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
process_workflows,
4040
)
4141
from sentry.workflow_engine.tasks.workflows import process_workflows_event
42-
from sentry.workflow_engine.types import WorkflowEventData, WorkflowNotProcessable
42+
from sentry.workflow_engine.types import WorkflowEventData
4343
from tests.sentry.workflow_engine.test_base import BaseWorkflowTest
4444

4545
FROZEN_TIME = before_now(days=1).replace(hour=1, minute=30, second=0, microsecond=0)
@@ -89,12 +89,10 @@ def test_skips_disabled_workflows(self) -> None:
8989
)
9090

9191
result = process_workflows(self.batch_client, self.event_data, FROZEN_TIME)
92-
assert isinstance(result, WorkflowNotProcessable)
9392
assert result.triggered_workflows == {self.error_workflow}
9493

9594
def test_error_event(self) -> None:
9695
result = process_workflows(self.batch_client, self.event_data, FROZEN_TIME)
97-
assert isinstance(result, WorkflowNotProcessable)
9896
assert result.triggered_workflows == {self.error_workflow}
9997

10098
@patch("sentry.workflow_engine.processors.action.fire_actions")
@@ -163,9 +161,9 @@ def test_populate_workflow_env_for_filters(self, mock_filter: MagicMock) -> None
163161
assert self.event_data.group_state
164162
self.event_data.group_state["is_new"] = True
165163

166-
process_workflows(self.batch_client, self.event_data, FROZEN_TIME)
167-
164+
result = process_workflows(self.batch_client, self.event_data, FROZEN_TIME)
168165
mock_filter.assert_called_with({workflow_filters}, self.event_data)
166+
assert result.tainted is False
169167

170168
def test_same_environment_only(self) -> None:
171169
env = self.create_environment(project=self.project)
@@ -211,7 +209,6 @@ def test_same_environment_only(self) -> None:
211209
)
212210

213211
result = process_workflows(self.batch_client, self.event_data, FROZEN_TIME)
214-
assert isinstance(result, WorkflowNotProcessable)
215212
assert result.triggered_workflows == {self.error_workflow, matching_env_workflow}
216213
assert result.message == "No actions to evaluate; filtered or not triggered"
217214

@@ -220,7 +217,6 @@ def test_issue_occurrence_event(self) -> None:
220217
self.group_event.occurrence = issue_occurrence
221218

222219
result = process_workflows(self.batch_client, self.event_data, FROZEN_TIME)
223-
assert isinstance(result, WorkflowNotProcessable)
224220
assert result.triggered_workflows == {self.workflow}
225221

226222
def test_regressed_event(self) -> None:
@@ -239,7 +235,7 @@ def test_regressed_event(self) -> None:
239235
)
240236

241237
result = process_workflows(self.batch_client, self.event_data, FROZEN_TIME)
242-
assert isinstance(result, WorkflowNotProcessable)
238+
assert result.tainted is True
243239
assert result.triggered_workflows == {self.error_workflow, workflow}
244240
assert result.message == "No actions to evaluate; filtered or not triggered"
245241

@@ -249,7 +245,6 @@ def test_no_detector(self, mock_logger: MagicMock, mock_incr: MagicMock) -> None
249245
self.group_event.occurrence = self.build_occurrence(evidence_data={})
250246

251247
result = process_workflows(self.batch_client, self.event_data, FROZEN_TIME)
252-
assert isinstance(result, WorkflowNotProcessable)
253248
assert result.message == "No Detectors associated with the issue were found"
254249

255250
mock_incr.assert_called_once_with("workflow_engine.detectors.error")
@@ -269,7 +264,6 @@ def test_no_environment(self, mock_logger: MagicMock, mock_incr: MagicMock) -> N
269264
cache.clear()
270265
result = process_workflows(self.batch_client, self.event_data, FROZEN_TIME)
271266

272-
assert isinstance(result, WorkflowNotProcessable)
273267
assert not result.triggered_workflows
274268
assert result.message == "Environment for event not found"
275269

@@ -349,9 +343,8 @@ def test_defaults_to_error_workflows(self) -> None:
349343

350344
result = process_workflows(self.batch_client, self.event_data, FROZEN_TIME)
351345

352-
assert isinstance(result, WorkflowNotProcessable)
346+
assert result.tainted is True
353347
assert result.triggered_workflows == {self.error_workflow}
354-
355348
assert result.triggered_actions is not None
356349
assert len(result.triggered_actions) == 0
357350

0 commit comments

Comments
 (0)