Skip to content

Commit b93ac29

Browse files
committed
started to add get_snapshot to the models, this will simplify the layers of trying to get debuggable output.
1 parent 627b781 commit b93ac29

File tree

6 files changed

+132
-12
lines changed

6 files changed

+132
-12
lines changed

src/sentry/workflow_engine/models/action.py

Lines changed: 7 additions & 1 deletion
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
6+
from typing import TYPE_CHECKING, Any, ClassVar
77

88
from django.db import models
99
from django.db.models import Q
@@ -112,6 +112,12 @@ class Meta:
112112
),
113113
]
114114

115+
def get_snapshot(self) -> dict[str, Any]:
116+
return {
117+
"id": self.id,
118+
"type": self.type,
119+
}
120+
115121
def get_handler(self) -> builtins.type[ActionHandler]:
116122
action_type = Action.Type(self.type)
117123
return action_handler_registry.get(action_type)

src/sentry/workflow_engine/models/data_condition_group.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from enum import StrEnum
2-
from typing import ClassVar, Self
2+
from typing import Any, ClassVar, Self
33

44
from django.db import models
55

@@ -36,3 +36,14 @@ class Type(StrEnum):
3636
max_length=200, choices=[(t.value, t.value) for t in Type], default=Type.ANY
3737
)
3838
organization = models.ForeignKey("sentry.Organization", on_delete=models.CASCADE)
39+
40+
def get_snapshot(self) -> dict[str, Any]:
41+
conditions = []
42+
if hasattr(self, "conditions"):
43+
conditions = [cond.get_snapshot() for cond in self.conditions.all()]
44+
45+
return {
46+
"id": self.id,
47+
"logic_type": self.logic_type,
48+
"conditions": conditions,
49+
}

src/sentry/workflow_engine/models/detector.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,18 @@ def settings(self) -> DetectorSettings:
141141

142142
return settings
143143

144+
def get_snapshot(self) -> dict[str, Any]:
145+
trigger_conditions = None
146+
if self.workflow_condition_group:
147+
trigger_conditions = self.workflow_condition_group.get_snapshot()
148+
149+
return {
150+
"id": self.id,
151+
"enabled": self.enabled,
152+
"status": self.status,
153+
"trigger_conditions": trigger_conditions,
154+
}
155+
144156
def get_audit_log_data(self) -> dict[str, Any]:
145157
return {"name": self.name}
146158

src/sentry/workflow_engine/models/workflow.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,23 @@ class Meta:
9292
def get_audit_log_data(self) -> dict[str, Any]:
9393
return {"name": self.name}
9494

95+
def get_snapshot(self) -> dict[str, Any]:
96+
when_condition_group = None
97+
if self.when_condition_group:
98+
when_condition_group = self.when_condition_group.get_snapshot()
99+
100+
environment_id = None
101+
if self.environment:
102+
environment_id = self.environment.id
103+
104+
return {
105+
"id": self.id,
106+
"enabled": self.enabled,
107+
"environment_id": environment_id,
108+
"status": self.status,
109+
"triggers": when_condition_group,
110+
}
111+
95112
def evaluate_trigger_conditions(
96113
self, event_data: WorkflowEventData, when_data_conditions: list[DataCondition] | None = None
97114
) -> tuple[TriggerResult, list[DataCondition]]:

src/sentry/workflow_engine/types.py

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from abc import ABC, abstractmethod
4-
from dataclasses import asdict, dataclass, field
4+
from dataclasses import dataclass, field
55
from enum import IntEnum, StrEnum
66
from logging import Logger
77
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypedDict, TypeVar
@@ -96,6 +96,43 @@ class WorkflowEvaluationData:
9696
triggered_workflows: set[Workflow] | None = None
9797
associated_detector: Detector | None = None
9898

99+
def get_snapshot(self) -> dict[str, Any]:
100+
"""
101+
This method will take the complex data structures, like models / list of models,
102+
and turn them into the critical attributes of a model or lists of IDs.
103+
"""
104+
105+
associated_detector = None
106+
if self.associated_detector:
107+
associated_detector = self.associated_detector.get_snapshot()
108+
109+
workflow_ids = []
110+
if self.workflows:
111+
workflow_ids = [workflow.id for workflow in self.workflows]
112+
113+
triggered_workflows = []
114+
if self.triggered_workflows:
115+
triggered_workflows = [workflow.get_snapshot() for workflow in self.triggered_workflows]
116+
117+
action_filter_conditions = []
118+
if self.action_groups:
119+
action_filter_conditions = [group.get_snapshot() for group in self.action_groups]
120+
121+
triggered_actions = []
122+
if self.triggered_actions:
123+
triggered_actions = [action.get_snapshot() for action in self.triggered_actions]
124+
125+
return {
126+
"workflow_ids": workflow_ids,
127+
"associated_detector": associated_detector,
128+
"event": self.event,
129+
"group": self.event.group,
130+
"event_data": self.event.data,
131+
"action_filter_conditions": action_filter_conditions,
132+
"triggered_actions": triggered_actions,
133+
"triggered_workflows": triggered_workflows,
134+
}
135+
99136

100137
@dataclass(frozen=True)
101138
class WorkflowEvaluation:
@@ -134,15 +171,7 @@ def to_log(self, logger: Logger) -> None:
134171
else:
135172
log_str = f"{log_str}.actions.triggered"
136173

137-
logger.info(
138-
log_str,
139-
extra={
140-
**asdict(self.data),
141-
"debug_msg": self.msg,
142-
"group": self.data.event.group,
143-
"data": self.data.event.data,
144-
},
145-
)
174+
logger.info(log_str, extra={**self.data.get_snapshot(), "debug_msg": self.msg})
146175

147176

148177
class ConfigTransformer(ABC):

tests/sentry/workflow_engine/test_task.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,51 @@ def test_process_workflow_activity(
254254
},
255255
)
256256

257+
@mock.patch("sentry.workflow_engine.processors.workflow.evaluate_workflow_triggers")
258+
@mock.patch("sentry.workflow_engine.tasks.workflows.logger")
259+
def test_process_workflow_activity__success_logs(
260+
self, mock_logger, mock_evaluate_workflow_triggers
261+
) -> None:
262+
self.workflow = self.create_workflow(organization=self.organization)
263+
self.workflow.when_condition_group = self.create_data_condition_group()
264+
self.create_data_condition(condition_group=self.workflow.when_condition_group)
265+
self.workflow.save()
266+
267+
self.action_group = self.create_data_condition_group(logic_type="any-short")
268+
self.action = self.create_action()
269+
self.create_data_condition_group_action(
270+
condition_group=self.action_group,
271+
action=self.action,
272+
)
273+
self.create_workflow_data_condition_group(self.workflow, self.action_group)
274+
275+
self.create_detector_workflow(
276+
detector=self.detector,
277+
workflow=self.workflow,
278+
)
279+
280+
mock_evaluate_workflow_triggers.return_value = ({self.workflow}, {})
281+
process_workflow_activity(
282+
activity_id=self.activity.id,
283+
group_id=self.group.id,
284+
detector_id=self.detector.id,
285+
)
286+
287+
mock_logger.info.assert_called_once_with(
288+
"workflow_engine.process_workflows.evaluation.actions.triggered",
289+
extra={
290+
"workflow_ids": [self.workflow.id],
291+
"associated_detector": self.detector.get_snapshot(),
292+
"event": self.activity,
293+
"group": self.activity.group,
294+
"event_data": self.activity.data,
295+
"action_filter_conditions": [self.action_group.get_snapshot()],
296+
"triggered_actions": [self.action.get_snapshot()],
297+
"triggered_workflows": [self.workflow.get_snapshot()],
298+
"debug_msg": None,
299+
},
300+
)
301+
257302
@mock.patch(
258303
"sentry.workflow_engine.models.incident_groupopenperiod.update_incident_based_on_open_period_status_change"
259304
) # rollout code that is independently tested

0 commit comments

Comments
 (0)