Skip to content

Commit d9418b3

Browse files
authored
feat(workflow_engine): workflow and action serializers (#83146)
Implement the serializer for the `Action` and `Workflow` APIs. I'm thinking that I will make a separate PR to alphabetize these serializers after this merges.
1 parent f7c13f9 commit d9418b3

File tree

2 files changed

+252
-3
lines changed

2 files changed

+252
-3
lines changed

src/sentry/workflow_engine/endpoints/serializers.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,31 @@
55
from sentry.api.serializers import Serializer, register, serialize
66
from sentry.grouping.grouptype import ErrorGroupType
77
from sentry.models.options.project_option import ProjectOption
8+
from sentry.utils import json
89
from sentry.workflow_engine.models import (
10+
Action,
911
DataCondition,
1012
DataConditionGroup,
1113
DataSource,
1214
DataSourceDetector,
1315
Detector,
16+
Workflow,
17+
WorkflowDataConditionGroup,
1418
)
19+
from sentry.workflow_engine.models.data_condition_group_action import DataConditionGroupAction
1520
from sentry.workflow_engine.types import DataSourceTypeHandler
1621

1722

23+
@register(Action)
24+
class ActionSerializer(Serializer):
25+
def serialize(self, obj: Action, *args, **kwargs):
26+
return {
27+
"id": str(obj.id),
28+
"type": obj.type,
29+
"data": json.dumps(obj.data),
30+
}
31+
32+
1833
@register(DataSource)
1934
class DataSourceSerializer(Serializer):
2035
def get_attrs(
@@ -79,9 +94,20 @@ def get_attrs(
7994
for condition, serialized in zip(condition_list, serialize(condition_list, user=user)):
8095
conditions[condition.condition_group_id].append(serialized)
8196

97+
dcga_list = list(DataConditionGroupAction.objects.filter(condition_group__in=item_list))
98+
actions = {dcga.action for dcga in dcga_list}
99+
100+
serialized_actions = {
101+
action.id: serialized
102+
for action, serialized in zip(actions, serialize(actions, user=user))
103+
}
104+
action_map = defaultdict(list)
105+
for dcga in dcga_list:
106+
action_map[dcga.condition_group_id].append(serialized_actions[dcga.action_id])
107+
82108
for item in item_list:
83109
attrs[item]["conditions"] = conditions.get(item.id, [])
84-
110+
attrs[item]["actions"] = action_map.get(item.id, [])
85111
return attrs
86112

87113
def serialize(
@@ -92,6 +118,7 @@ def serialize(
92118
"organizationId": str(obj.organization_id),
93119
"logicType": obj.logic_type,
94120
"conditions": attrs.get("conditions"),
121+
"actions": attrs.get("actions"),
95122
}
96123

97124

@@ -167,3 +194,52 @@ def serialize(self, obj: Detector, attrs: Mapping[str, Any], user, **kwargs) ->
167194
"conditionGroup": attrs.get("condition_group"),
168195
"config": attrs.get("config"),
169196
}
197+
198+
199+
@register(Workflow)
200+
class WorkflowSerializer(Serializer):
201+
def get_attrs(self, item_list, user, **kwargs):
202+
attrs: MutableMapping[Workflow, dict[str, Any]] = defaultdict(dict)
203+
trigger_conditions = list(
204+
DataConditionGroup.objects.filter(
205+
id__in=[w.when_condition_group_id for w in item_list if w.when_condition_group_id]
206+
)
207+
)
208+
trigger_condition_map = {
209+
group.id: serialized
210+
for group, serialized in zip(
211+
trigger_conditions, serialize(trigger_conditions, user=user)
212+
)
213+
}
214+
215+
wdcg_list = list(WorkflowDataConditionGroup.objects.filter(workflow__in=item_list))
216+
condition_groups = {wdcg.condition_group for wdcg in wdcg_list}
217+
218+
serialized_condition_groups = {
219+
dcg.id: serialized
220+
for dcg, serialized in zip(condition_groups, serialize(condition_groups, user=user))
221+
}
222+
dcg_map = defaultdict(list)
223+
for wdcg in wdcg_list:
224+
dcg_map[wdcg.workflow_id].append(serialized_condition_groups[wdcg.condition_group_id])
225+
226+
for item in item_list:
227+
attrs[item]["trigger_condition_group"] = trigger_condition_map.get(
228+
item.when_condition_group_id
229+
) # when condition group
230+
attrs[item]["data_condition_groups"] = dcg_map.get(
231+
item.id, []
232+
) # data condition groups associated with workflow via WorkflowDataConditionGroup lookup table
233+
return attrs
234+
235+
def serialize(self, obj: Workflow, attrs: Mapping[str, Any], user, **kwargs) -> dict[str, Any]:
236+
# WHAT TO DO ABOUT CONFIG?
237+
return {
238+
"id": str(obj.id),
239+
"organizationId": str(obj.organization_id),
240+
"dateCreated": obj.date_added,
241+
"dateUpdated": obj.date_updated,
242+
"triggerConditionGroup": attrs.get("trigger_condition_group"),
243+
"dataConditionGroups": attrs.get("data_condition_groups"),
244+
"environment": obj.environment.name if obj.environment else None,
245+
}

tests/sentry/workflow_engine/endpoints/test_serializers.py

Lines changed: 175 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,25 @@
33
from sentry.api.serializers import serialize
44
from sentry.incidents.grouptype import MetricAlertFire
55
from sentry.incidents.utils.constants import INCIDENTS_SNUBA_SUBSCRIPTION_TYPE
6+
from sentry.integrations.models.integration import Integration
7+
from sentry.notifications.models.notificationaction import ActionTarget
8+
from sentry.silo.base import SiloMode
69
from sentry.snuba.dataset import Dataset
710
from sentry.snuba.models import QuerySubscriptionDataSourceHandler, SnubaQuery
811
from sentry.snuba.subscriptions import create_snuba_query, create_snuba_subscription
912
from sentry.testutils.cases import TestCase
10-
from sentry.workflow_engine.models import DataCondition, DataConditionGroup, DataSource, Detector
13+
from sentry.testutils.silo import assume_test_silo_mode
14+
from sentry.workflow_engine.models import (
15+
Action,
16+
DataCondition,
17+
DataConditionGroup,
18+
DataSource,
19+
Detector,
20+
Workflow,
21+
)
1122
from sentry.workflow_engine.models.data_condition import Condition
23+
from sentry.workflow_engine.models.data_condition_group_action import DataConditionGroupAction
24+
from sentry.workflow_engine.models.workflow_data_condition_group import WorkflowDataConditionGroup
1225
from sentry.workflow_engine.registry import data_source_type_registry
1326
from sentry.workflow_engine.types import DetectorPriorityLevel
1427

@@ -46,6 +59,11 @@ def test_serialize_full(self):
4659
comparison=100,
4760
condition_result=DetectorPriorityLevel.HIGH,
4861
)
62+
63+
action = Action.objects.create(type=Action.Type.EMAIL, data={"foo": "bar"})
64+
65+
DataConditionGroupAction.objects.create(condition_group=condition_group, action=action)
66+
4967
detector = Detector.objects.create(
5068
organization_id=self.organization.id,
5169
name="Test Detector",
@@ -114,6 +132,13 @@ def test_serialize_full(self):
114132
"result": DetectorPriorityLevel.HIGH,
115133
}
116134
],
135+
"actions": [
136+
{
137+
"id": str(action.id),
138+
"type": "email",
139+
"data": '{"foo":"bar"}',
140+
}
141+
],
117142
},
118143
"config": {},
119144
}
@@ -192,9 +217,10 @@ def test_serialize_simple(self):
192217
"organizationId": str(self.organization.id),
193218
"logicType": DataConditionGroup.Type.ANY,
194219
"conditions": [],
220+
"actions": [],
195221
}
196222

197-
def test_serialize_with_conditions(self):
223+
def test_serialize_full(self):
198224
condition_group = DataConditionGroup.objects.create(
199225
organization_id=self.organization.id,
200226
logic_type=DataConditionGroup.Type.ANY,
@@ -206,6 +232,10 @@ def test_serialize_with_conditions(self):
206232
condition_result=DetectorPriorityLevel.HIGH,
207233
)
208234

235+
action = Action.objects.create(type=Action.Type.EMAIL, data={"foo": "bar"})
236+
237+
DataConditionGroupAction.objects.create(condition_group=condition_group, action=action)
238+
209239
result = serialize(condition_group)
210240

211241
assert result == {
@@ -220,4 +250,147 @@ def test_serialize_with_conditions(self):
220250
"result": DetectorPriorityLevel.HIGH,
221251
}
222252
],
253+
"actions": [
254+
{
255+
"id": str(action.id),
256+
"type": "email",
257+
"data": '{"foo":"bar"}',
258+
}
259+
],
260+
}
261+
262+
263+
class TestActionSerializer(TestCase):
264+
def test_serialize_simple(self):
265+
action = Action.objects.create(
266+
type=Action.Type.EMAIL,
267+
data={"foo": "bar"},
268+
)
269+
270+
result = serialize(action)
271+
272+
assert result == {"id": str(action.id), "type": "email", "data": '{"foo":"bar"}'}
273+
274+
def test_serialize_with_legacy_fields(self):
275+
"""
276+
Legacy fields should not be serialized.
277+
"""
278+
with assume_test_silo_mode(SiloMode.CONTROL):
279+
integration = Integration.objects.create(
280+
provider="slack", name="example-integration", external_id="123-id", metadata={}
281+
)
282+
action = Action.objects.create(
283+
type=Action.Type.SLACK,
284+
data={"foo": "bar"},
285+
integration_id=integration.id,
286+
target_display="freddy frog",
287+
target_type=ActionTarget.USER,
288+
)
289+
290+
result = serialize(action)
291+
292+
assert result == {"id": str(action.id), "type": "slack", "data": '{"foo":"bar"}'}
293+
294+
295+
class TestWorkflowSerializer(TestCase):
296+
def test_serialize_simple(self):
297+
workflow = Workflow.objects.create(
298+
name="hojicha",
299+
organization_id=self.organization.id,
300+
config={},
301+
)
302+
303+
result = serialize(workflow)
304+
305+
assert result == {
306+
"id": str(workflow.id),
307+
"organizationId": str(self.organization.id),
308+
"dateCreated": workflow.date_added,
309+
"dateUpdated": workflow.date_updated,
310+
"triggerConditionGroup": None,
311+
"dataConditionGroups": [],
312+
"environment": None,
313+
}
314+
315+
def test_serialize_full(self):
316+
when_condition_group = DataConditionGroup.objects.create(
317+
organization_id=self.organization.id,
318+
logic_type=DataConditionGroup.Type.ANY,
319+
)
320+
trigger_condition = DataCondition.objects.create(
321+
condition_group=when_condition_group,
322+
type=Condition.FIRST_SEEN_EVENT,
323+
comparison=True,
324+
condition_result=True,
325+
)
326+
workflow = Workflow.objects.create(
327+
name="hojicha",
328+
organization_id=self.organization.id,
329+
config={},
330+
when_condition_group=when_condition_group,
331+
environment=self.environment,
332+
)
333+
334+
condition_group = DataConditionGroup.objects.create(
335+
organization_id=self.organization.id,
336+
logic_type=DataConditionGroup.Type.ALL,
337+
)
338+
action = Action.objects.create(type=Action.Type.EMAIL, data={"foo": "bar"})
339+
DataConditionGroupAction.objects.create(condition_group=condition_group, action=action)
340+
condition = DataCondition.objects.create(
341+
condition_group=condition_group,
342+
type=Condition.GREATER,
343+
comparison=100,
344+
condition_result=DetectorPriorityLevel.HIGH,
345+
)
346+
347+
WorkflowDataConditionGroup.objects.create(
348+
condition_group=condition_group,
349+
workflow=workflow,
350+
)
351+
352+
result = serialize(workflow)
353+
354+
assert result == {
355+
"id": str(workflow.id),
356+
"organizationId": str(self.organization.id),
357+
"dateCreated": workflow.date_added,
358+
"dateUpdated": workflow.date_updated,
359+
"triggerConditionGroup": {
360+
"id": str(when_condition_group.id),
361+
"organizationId": str(self.organization.id),
362+
"logicType": DataConditionGroup.Type.ANY,
363+
"conditions": [
364+
{
365+
"id": str(trigger_condition.id),
366+
"condition": "first_seen_event",
367+
"comparison": True,
368+
"result": True,
369+
}
370+
],
371+
"actions": [],
372+
},
373+
"dataConditionGroups": [
374+
{
375+
"id": str(condition_group.id),
376+
"organizationId": str(self.organization.id),
377+
"logicType": DataConditionGroup.Type.ALL,
378+
"conditions": [
379+
{
380+
"id": str(condition.id),
381+
"condition": "gt",
382+
"comparison": 100,
383+
"result": DetectorPriorityLevel.HIGH,
384+
}
385+
],
386+
"actions": [
387+
{
388+
"id": str(action.id),
389+
"type": "email",
390+
"data": '{"foo":"bar"}',
391+
}
392+
],
393+
},
394+
],
395+
"environment": self.environment.name,
223396
}

0 commit comments

Comments
 (0)