diff --git a/src/dispatch/signal/flows.py b/src/dispatch/signal/flows.py index 06e7359bda44..47383856b1bf 100644 --- a/src/dispatch/signal/flows.py +++ b/src/dispatch/signal/flows.py @@ -94,8 +94,8 @@ def signal_instance_create_flow( oncall_service = signal_instance.oncall_service elif signal_instance.signal.oncall_service: oncall_service = signal_instance.signal.oncall_service - elif signal_instance.case_type.oncall_service: - oncall_service = signal_instance.case_type.oncall_service + elif case_type.oncall_service: + oncall_service = case_type.oncall_service else: oncall_service = None @@ -103,8 +103,8 @@ def signal_instance_create_flow( conversation_target = signal_instance.conversation_target elif signal_instance.signal.conversation_target: conversation_target = signal_instance.signal.conversation_target - elif signal_instance.case_type.conversation_target: - conversation_target = signal_instance.case_type.conversation_target + elif case_type.conversation_target: + conversation_target = case_type.conversation_target else: conversation_target = None @@ -176,7 +176,10 @@ def create_signal_instance( raise DispatchException("Signal definition is not enabled.") signal_instance_in = SignalInstanceCreate( - raw=signal_instance_data, signal=signal, project=signal.project + **signal_instance_data, + raw=signal_instance_data, + signal=signal, + project=signal.project, ) signal_instance = signal_service.create_instance( diff --git a/src/dispatch/signal/models.py b/src/dispatch/signal/models.py index 3db8af636659..eca688756a2d 100644 --- a/src/dispatch/signal/models.py +++ b/src/dispatch/signal/models.py @@ -38,7 +38,7 @@ TimeStampMixin, ) from dispatch.project.models import ProjectRead -from dispatch.service.models import Service +from dispatch.service.models import Service, ServiceRead from dispatch.tag.models import TagRead from dispatch.workflow.models import WorkflowRead @@ -391,7 +391,7 @@ class SignalInstanceCreate(SignalInstanceBase): case_priority: Optional[CasePriorityRead] case_type: Optional[CaseTypeRead] conversation_target: Optional[str] - oncall_service: Optional[Service] + oncall_service: Optional[ServiceRead] class SignalInstanceRead(SignalInstanceBase): diff --git a/src/dispatch/signal/service.py b/src/dispatch/signal/service.py index ba158d9e7b0e..f60e9bd0ebaa 100644 --- a/src/dispatch/signal/service.py +++ b/src/dispatch/signal/service.py @@ -571,6 +571,11 @@ def create_instance( signal = get(db_session=db_session, signal_id=signal_instance_in.signal.id) + # remove non-serializable entities from the raw JSON: + signal_instance_in_raw = signal_instance_in.raw.copy() + if signal_instance_in.oncall_service: + signal_instance_in_raw.pop('oncall_service') + # we round trip the raw data to json-ify date strings signal_instance = SignalInstance( **signal_instance_in.dict( @@ -580,12 +585,13 @@ def create_instance( "case_type", "entities", "external_id", + "oncall_service", "project", "raw", "signal", } ), - raw=json.loads(json.dumps(signal_instance_in.raw)), + raw=json.loads(json.dumps(signal_instance_in_raw)), project=project, signal=signal, ) @@ -619,6 +625,14 @@ def create_instance( ) signal_instance.case_type = case_type + if signal_instance_in.oncall_service: + oncall_service = service_service.get_by_name( + db_session=db_session, + project_id=project.id, + name=signal_instance_in.oncall_service.name, + ) + signal_instance.oncall_service = oncall_service + db_session.add(signal_instance) db_session.commit() return signal_instance diff --git a/tests/signal/test_signal_flow.py b/tests/signal/test_signal_flow.py index f4612bbde555..11dc5c26cb04 100644 --- a/tests/signal/test_signal_flow.py +++ b/tests/signal/test_signal_flow.py @@ -1,3 +1,6 @@ +from unittest import mock +from unittest.mock import MagicMock + import pytest from dispatch.exceptions import DispatchException @@ -59,3 +62,180 @@ def test_create_signal_instance_not_enabled(session, signal, case_severity, case signal_instance_data=instance_data, current_user=user, ) + +def test_create_signal_instance_custom_conversation_target(session, signal, case_severity, case_priority, user, case_type): + from dispatch.signal.flows import create_signal_instance + + case_priority.default = True + case_priority.project_id = signal.project_id + + case_severity.default = True + case_severity.project_id = signal.project_id + + instance_data = {"variant": signal.variant, "conversation_target": "instance-conversation-target"} + signal.conversation_target = "signal-conversation-target" + + signal_instance = create_signal_instance( + db_session=session, + project=signal.project, + signal_instance_data=instance_data, + current_user=user, + ) + assert signal_instance.conversation_target == 'instance-conversation-target' + + +def test_create_signal_instance_custom_oncall_service(session, signal, case_severity, case_priority, user, services): + from dispatch.signal.flows import create_signal_instance + + case_priority.default = True + case_priority.project_id = signal.project_id + + case_severity.default = True + case_severity.project_id = signal.project_id + + service_0, service_1 = services + service_0.project_id = signal.project_id + service_1.project_id = signal.project_id + + signal.oncall_service = service_0 + instance_data = {"variant": signal.variant, "oncall_service": service_1} + + signal_instance = create_signal_instance( + db_session=session, + project=signal.project, + signal_instance_data=instance_data, + current_user=user, + ) + assert signal_instance.oncall_service.id == service_1.id + +def test_signal_instance_create_flow_custom_attributes(session, signal, case_severity, case_priority, user, services, signal_instance, oncall_plugin, case_type, case): + from dispatch.signal.flows import signal_instance_create_flow + from dispatch.service import flows as service_flows + from dispatch.case import service as case_service + + case_priority.default = True + case_priority.project_id = signal.project_id + + case_severity.default = True + case_severity.project_id = signal.project_id + + service_0, service_1 = services + service_0.project_id = signal.project_id + service_1.project_id = signal.project_id + + signal_instance.oncall_service = service_0 + signal_instance.signal.oncall_service = service_1 + signal_instance.conversation_target = "instance-conversation-target" + signal_instance.signal.conversation_target = "signal-conversation-target" + + with mock.patch.object(service_flows, "resolve_oncall") as mock_resolve_oncall, \ + mock.patch.object(case_service, "create") as mock_case_create, \ + mock.patch("dispatch.case.flows.case_new_create_flow") as mock_case_new_create_flow: + mock_resolve_oncall.side_effect = lambda service, db_session: "example@test.com" if service.id == service_0.id else None + mock_case_create.return_value = case + + post_flow_instance = signal_instance_create_flow( + signal_instance_id=signal_instance.id, + db_session=session, + current_user=user + ) + case_in_arg = mock_case_create.call_args[1]['case_in'] + assert case_in_arg.assignee.individual.email == "example@test.com" + mock_case_new_create_flow.assert_called_once_with( + db_session=session, + organization_slug=None, + service_id=None, + conversation_target="instance-conversation-target", + case_id=post_flow_instance.case.id, + create_all_resources=False + ) + +def test_signal_instance_create_flow_use_signal_attributes(session, signal, case_severity, case_priority, user, services, signal_instance, + case_type, case): + """ + If the signal instance does not specify a conversation target or on-call service, use the signal's configurations + before the case type's configurations. + """ + from dispatch.signal.flows import signal_instance_create_flow + from dispatch.service import flows as service_flows + from dispatch.case import service as case_service + + case_priority.default = True + case_priority.project_id = signal.project_id + + case_severity.default = True + case_severity.project_id = signal.project_id + + service_0, service_1 = services + service_0.project_id = signal.project_id + service_1.project_id = signal.project_id + + signal_instance.signal.oncall_service = service_0 + signal_instance.signal.conversation_target = "signal-conversation-target" + case_type.oncall_service = service_1 + case_type.conversation_target = "case-type-conversation-target" + signal_instance.signal.case_type = case_type + + with mock.patch.object(service_flows, "resolve_oncall") as mock_resolve_oncall, \ + mock.patch.object(case_service, "create") as mock_case_create, \ + mock.patch("dispatch.case.flows.case_new_create_flow") as mock_case_new_create_flow: + + mock_resolve_oncall.side_effect = lambda service, db_session: "example@test.com" if service.id == service_0.id else None + mock_case_create.return_value = case + + post_flow_instance = signal_instance_create_flow( + signal_instance_id=signal_instance.id, + db_session=session, + current_user=user + ) + case_in_arg = mock_case_create.call_args[1]['case_in'] + assert case_in_arg.assignee.individual.email == "example@test.com" + mock_case_new_create_flow.assert_called_once_with( + db_session=session, + organization_slug=None, + service_id=None, + conversation_target="signal-conversation-target", + case_id=post_flow_instance.case.id, + create_all_resources=False + ) + + +def test_signal_instance_create_flow_use_case_type_attributes(session, signal, case_severity, case_priority, user, service, case, signal_instance, case_type): + """ + If the signal instance and the signal both do not specify conversation targets or on-call services, use the case type's configurations. + """ + from dispatch.signal.flows import signal_instance_create_flow + from dispatch.service import flows as service_flows + from dispatch.case import service as case_service + + case_priority.default = True + case_priority.project_id = signal.project_id + + case_severity.default = True + case_severity.project_id = signal.project_id + + case_type.oncall_service = service + case_type.conversation_target = "case-type-conversation-target" + signal_instance.signal.case_type = case_type + + with mock.patch.object(service_flows, "resolve_oncall") as mock_resolve_oncall, \ + mock.patch.object(case_service, "create") as mock_case_create, \ + mock.patch("dispatch.case.flows.case_new_create_flow") as mock_case_new_create_flow: + mock_resolve_oncall.side_effect = lambda service, db_session: "example@test.com" + mock_case_create.return_value = case + + post_flow_instance = signal_instance_create_flow( + signal_instance_id=signal_instance.id, + db_session=session, + current_user=user + ) + case_in_arg = mock_case_create.call_args[1]['case_in'] + assert case_in_arg.assignee.individual.email == "example@test.com" + mock_case_new_create_flow.assert_called_once_with( + db_session=session, + organization_slug=None, + service_id=None, + conversation_target="case-type-conversation-target", + case_id=post_flow_instance.case.id, + create_all_resources=False + )