Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 77 additions & 1 deletion src/sentry/workflow_engine/endpoints/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,31 @@
from sentry.api.serializers import Serializer, register, serialize
from sentry.issues.grouptype import ErrorGroupType
from sentry.models.options.project_option import ProjectOption
from sentry.utils import json
from sentry.workflow_engine.models import (
Action,
DataCondition,
DataConditionGroup,
DataSource,
DataSourceDetector,
Detector,
Workflow,
WorkflowDataConditionGroup,
)
from sentry.workflow_engine.models.data_condition_group_action import DataConditionGroupAction
from sentry.workflow_engine.types import DataSourceTypeHandler


@register(Action)
class ActionSerializer(Serializer):
def serialize(self, obj: Action, *args, **kwargs):
return {
"id": str(obj.id),
"type": obj.type,
"data": json.dumps(obj.data),
}


@register(DataSource)
class DataSourceSerializer(Serializer):
def get_attrs(
Expand Down Expand Up @@ -79,9 +94,20 @@ def get_attrs(
for condition, serialized in zip(condition_list, serialize(condition_list, user=user)):
conditions[condition.condition_group_id].append(serialized)

dcga_list = list(DataConditionGroupAction.objects.filter(condition_group__in=item_list))
actions = {dcga.action for dcga in dcga_list}

serialized_actions = {
action.id: serialized
for action, serialized in zip(actions, serialize(actions, user=user))
}
action_map = defaultdict(list)
for dcga in dcga_list:
action_map[dcga.condition_group_id].append(serialized_actions[dcga.action_id])

for item in item_list:
attrs[item]["conditions"] = conditions.get(item.id, [])

attrs[item]["actions"] = action_map.get(item.id, [])
return attrs

def serialize(
Expand All @@ -92,6 +118,7 @@ def serialize(
"organizationId": str(obj.organization_id),
"logicType": obj.logic_type,
"conditions": attrs.get("conditions"),
"actions": attrs.get("actions"),
}


Expand Down Expand Up @@ -167,3 +194,52 @@ def serialize(self, obj: Detector, attrs: Mapping[str, Any], user, **kwargs) ->
"conditionGroup": attrs.get("condition_group"),
"config": attrs.get("config"),
}


@register(Workflow)
class WorkflowSerializer(Serializer):
def get_attrs(self, item_list, user, **kwargs):
attrs: MutableMapping[Workflow, dict[str, Any]] = defaultdict(dict)
trigger_conditions = list(
DataConditionGroup.objects.filter(
id__in=[w.when_condition_group_id for w in item_list if w.when_condition_group_id]
)
)
trigger_condition_map = {
group.id: serialized
for group, serialized in zip(
trigger_conditions, serialize(trigger_conditions, user=user)
)
}

wdcg_list = list(WorkflowDataConditionGroup.objects.filter(workflow__in=item_list))
condition_groups = {wdcg.condition_group for wdcg in wdcg_list}

serialized_condition_groups = {
dcg.id: serialized
for dcg, serialized in zip(condition_groups, serialize(condition_groups, user=user))
}
dcg_map = defaultdict(list)
for wdcg in wdcg_list:
dcg_map[wdcg.workflow_id].append(serialized_condition_groups[wdcg.condition_group_id])

for item in item_list:
attrs[item]["trigger_condition_group"] = trigger_condition_map.get(
item.when_condition_group_id
) # when condition group
attrs[item]["data_condition_groups"] = dcg_map.get(
item.id, []
) # data condition groups associated with workflow via WorkflowDataConditionGroup lookup table
return attrs

def serialize(self, obj: Workflow, attrs: Mapping[str, Any], user, **kwargs) -> dict[str, Any]:
# WHAT TO DO ABOUT CONFIG?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good question 🤔 Cathy added enforcement of the schemas in https://github.com/getsentry/sentry/pull/81979/files and I was working on defining a schema for the detector config here that I need to get back to but we haven't made others yet afaik. Wanna bring it up in office hours tomorrow?

return {
"id": str(obj.id),
"organizationId": str(obj.organization_id),
"dateCreated": obj.date_added,
"dateUpdated": obj.date_updated,
"triggerConditionGroup": attrs.get("trigger_condition_group"),
"dataConditionGroups": attrs.get("data_condition_groups"),
"environment": obj.environment.name if obj.environment else None,
}
177 changes: 175 additions & 2 deletions tests/sentry/workflow_engine/endpoints/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,25 @@
from sentry.api.serializers import serialize
from sentry.incidents.grouptype import MetricAlertFire
from sentry.incidents.utils.constants import INCIDENTS_SNUBA_SUBSCRIPTION_TYPE
from sentry.integrations.models.integration import Integration
from sentry.notifications.models.notificationaction import ActionTarget
from sentry.silo.base import SiloMode
from sentry.snuba.dataset import Dataset
from sentry.snuba.models import QuerySubscriptionDataSourceHandler, SnubaQuery
from sentry.snuba.subscriptions import create_snuba_query, create_snuba_subscription
from sentry.testutils.cases import TestCase
from sentry.workflow_engine.models import DataCondition, DataConditionGroup, DataSource, Detector
from sentry.testutils.silo import assume_test_silo_mode
from sentry.workflow_engine.models import (
Action,
DataCondition,
DataConditionGroup,
DataSource,
Detector,
Workflow,
)
from sentry.workflow_engine.models.data_condition import Condition
from sentry.workflow_engine.models.data_condition_group_action import DataConditionGroupAction
from sentry.workflow_engine.models.workflow_data_condition_group import WorkflowDataConditionGroup
from sentry.workflow_engine.registry import data_source_type_registry
from sentry.workflow_engine.types import DetectorPriorityLevel

Expand Down Expand Up @@ -46,6 +59,11 @@ def test_serialize_full(self):
comparison=100,
condition_result=DetectorPriorityLevel.HIGH,
)

action = Action.objects.create(type=Action.Type.EMAIL, data={"foo": "bar"})

DataConditionGroupAction.objects.create(condition_group=condition_group, action=action)

detector = Detector.objects.create(
organization_id=self.organization.id,
name="Test Detector",
Expand Down Expand Up @@ -114,6 +132,13 @@ def test_serialize_full(self):
"result": DetectorPriorityLevel.HIGH,
}
],
"actions": [
{
"id": str(action.id),
"type": "email",
"data": '{"foo":"bar"}',
}
],
},
"config": {},
}
Expand Down Expand Up @@ -192,9 +217,10 @@ def test_serialize_simple(self):
"organizationId": str(self.organization.id),
"logicType": DataConditionGroup.Type.ANY,
"conditions": [],
"actions": [],
}

def test_serialize_with_conditions(self):
def test_serialize_full(self):
condition_group = DataConditionGroup.objects.create(
organization_id=self.organization.id,
logic_type=DataConditionGroup.Type.ANY,
Expand All @@ -206,6 +232,10 @@ def test_serialize_with_conditions(self):
condition_result=DetectorPriorityLevel.HIGH,
)

action = Action.objects.create(type=Action.Type.EMAIL, data={"foo": "bar"})

DataConditionGroupAction.objects.create(condition_group=condition_group, action=action)

result = serialize(condition_group)

assert result == {
Expand All @@ -220,4 +250,147 @@ def test_serialize_with_conditions(self):
"result": DetectorPriorityLevel.HIGH,
}
],
"actions": [
{
"id": str(action.id),
"type": "email",
"data": '{"foo":"bar"}',
}
],
}


class TestActionSerializer(TestCase):
def test_serialize_simple(self):
action = Action.objects.create(
type=Action.Type.EMAIL,
data={"foo": "bar"},
)

result = serialize(action)

assert result == {"id": str(action.id), "type": "email", "data": '{"foo":"bar"}'}

def test_serialize_with_legacy_fields(self):
"""
Legacy fields should not be serialized.
"""
with assume_test_silo_mode(SiloMode.CONTROL):
integration = Integration.objects.create(
provider="slack", name="example-integration", external_id="123-id", metadata={}
)
action = Action.objects.create(
type=Action.Type.SLACK,
data={"foo": "bar"},
integration_id=integration.id,
target_display="freddy frog",
target_type=ActionTarget.USER,
)

result = serialize(action)

assert result == {"id": str(action.id), "type": "slack", "data": '{"foo":"bar"}'}


class TestWorkflowSerializer(TestCase):
def test_serialize_simple(self):
workflow = Workflow.objects.create(
name="hojicha",
organization_id=self.organization.id,
config={},
)

result = serialize(workflow)

assert result == {
"id": str(workflow.id),
"organizationId": str(self.organization.id),
"dateCreated": workflow.date_added,
"dateUpdated": workflow.date_updated,
"triggerConditionGroup": None,
"dataConditionGroups": [],
"environment": None,
}

def test_serialize_full(self):
when_condition_group = DataConditionGroup.objects.create(
organization_id=self.organization.id,
logic_type=DataConditionGroup.Type.ANY,
)
trigger_condition = DataCondition.objects.create(
condition_group=when_condition_group,
type=Condition.FIRST_SEEN_EVENT,
comparison=True,
condition_result=True,
)
workflow = Workflow.objects.create(
name="hojicha",
organization_id=self.organization.id,
config={},
when_condition_group=when_condition_group,
environment=self.environment,
)

condition_group = DataConditionGroup.objects.create(
organization_id=self.organization.id,
logic_type=DataConditionGroup.Type.ALL,
)
action = Action.objects.create(type=Action.Type.EMAIL, data={"foo": "bar"})
DataConditionGroupAction.objects.create(condition_group=condition_group, action=action)
condition = DataCondition.objects.create(
condition_group=condition_group,
type=Condition.GREATER,
comparison=100,
condition_result=DetectorPriorityLevel.HIGH,
)

WorkflowDataConditionGroup.objects.create(
condition_group=condition_group,
workflow=workflow,
)

result = serialize(workflow)

assert result == {
"id": str(workflow.id),
"organizationId": str(self.organization.id),
"dateCreated": workflow.date_added,
"dateUpdated": workflow.date_updated,
"triggerConditionGroup": {
"id": str(when_condition_group.id),
"organizationId": str(self.organization.id),
"logicType": DataConditionGroup.Type.ANY,
"conditions": [
{
"id": str(trigger_condition.id),
"condition": "first_seen_event",
"comparison": True,
"result": True,
}
],
"actions": [],
},
"dataConditionGroups": [
{
"id": str(condition_group.id),
"organizationId": str(self.organization.id),
"logicType": DataConditionGroup.Type.ALL,
"conditions": [
{
"id": str(condition.id),
"condition": "gt",
"comparison": 100,
"result": DetectorPriorityLevel.HIGH,
}
],
"actions": [
{
"id": str(action.id),
"type": "email",
"data": '{"foo":"bar"}',
}
],
},
],
"environment": self.environment.name,
}
Loading