From 3ade317010e0d38416d88f2a2fb9699569264d43 Mon Sep 17 00:00:00 2001 From: Matias Bordese Date: Tue, 14 Mar 2023 14:21:46 -0300 Subject: [PATCH] Rework webhook trigger tasks checks and payload build (#1544) --- CHANGELOG.md | 4 + .../apps/webhooks/tasks/alert_group_status.py | 67 ++++----- engine/apps/webhooks/tasks/trigger_webhook.py | 74 ++++++++-- .../tests/test_alert_group_status_change.py | 98 +++++--------- .../webhooks/tests/test_trigger_webhook.py | 127 +++++++++++------- 5 files changed, 210 insertions(+), 160 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d1887cc..565e77ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add filtering by escalation chain to alert groups page ([1535](https://github.com/grafana/oncall/pull/1535)) +### Fixed + +- Improve tasks checking/triggering webhooks in new backend + ## v1.1.37 (2023-03-14) ### Fixed diff --git a/engine/apps/webhooks/tasks/alert_group_status.py b/engine/apps/webhooks/tasks/alert_group_status.py index 52b0f0bc..67059ddc 100644 --- a/engine/apps/webhooks/tasks/alert_group_status.py +++ b/engine/apps/webhooks/tasks/alert_group_status.py @@ -4,9 +4,7 @@ from celery.utils.log import get_task_logger from django.conf import settings from apps.alerts.models import AlertGroup, AlertGroupLogRecord -from apps.user_management.models import User from apps.webhooks.models import Webhook -from apps.webhooks.utils import serialize_event from common.custom_celery_tasks import shared_dedicated_queue_retry_task from .trigger_webhook import send_webhook_event @@ -17,6 +15,14 @@ logger.setLevel(logging.DEBUG) MAX_RETRIES = 10 +ACTION_TO_TRIGGER_TYPE = { + AlertGroupLogRecord.TYPE_ACK: Webhook.TRIGGER_ACKNOWLEDGE, + AlertGroupLogRecord.TYPE_RESOLVED: Webhook.TRIGGER_RESOLVE, + AlertGroupLogRecord.TYPE_SILENCE: Webhook.TRIGGER_SILENCE, + AlertGroupLogRecord.TYPE_UN_SILENCE: Webhook.TRIGGER_UNSILENCE, + AlertGroupLogRecord.TYPE_UN_RESOLVED: Webhook.TRIGGER_UNRESOLVE, +} + @shared_dedicated_queue_retry_task( bind=True, autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else MAX_RETRIES @@ -28,15 +34,16 @@ def alert_group_created(self, alert_group_id): return trigger_type = Webhook.TRIGGER_NEW - event = { - "type": "Firing", - "time": alert_group.started_at, - } - data = serialize_event(event, alert_group, None) organization_id = alert_group.channel.organization_id team_id = alert_group.channel.team_id + webhooks = Webhook.objects.filter(trigger_type=trigger_type, organization_id=organization_id, team_id=team_id) + + # check if there are any webhooks before going on + if not webhooks: + return + send_webhook_event.apply_async( - (trigger_type, data), kwargs={"organization_id": organization_id, "team_id": team_id} + (trigger_type, alert_group_id), kwargs={"organization_id": organization_id, "team_id": team_id} ) @@ -46,46 +53,22 @@ def alert_group_created(self, alert_group_id): def alert_group_status_change(self, action_type, alert_group_id, user_id): try: alert_group = AlertGroup.unarchived_objects.get(pk=alert_group_id) - user = User.objects.filter(pk=user_id).first() except (AlertGroup.DoesNotExist): return - # TODO: update mapping, maybe use a dict instead - if action_type == AlertGroupLogRecord.TYPE_ACK: - trigger_type = Webhook.TRIGGER_ACKNOWLEDGE - event = { - "type": "Acknowledge", - "time": alert_group.acknowledged_at, - } - elif action_type == AlertGroupLogRecord.TYPE_RESOLVED: - trigger_type = Webhook.TRIGGER_RESOLVE - event = { - "type": "Resolve", - "time": alert_group.resolved_at, - } - elif action_type == AlertGroupLogRecord.TYPE_SILENCE: - trigger_type = Webhook.TRIGGER_SILENCE - event = { - "type": "Silence", - "time": alert_group.silenced_at, - "until": alert_group.silenced_until, - } - elif action_type == AlertGroupLogRecord.TYPE_UN_SILENCE: - trigger_type = Webhook.TRIGGER_UNSILENCE - event = { - "type": "Unsilence", - } - elif action_type == AlertGroupLogRecord.TYPE_UN_RESOLVED: - trigger_type = Webhook.TRIGGER_UNRESOLVE - event = { - "type": "Unresolve", - } - else: + trigger_type = ACTION_TO_TRIGGER_TYPE.get(action_type) + if trigger_type is None: return - data = serialize_event(event, alert_group, user) organization_id = alert_group.channel.organization_id team_id = alert_group.channel.team_id + webhooks = Webhook.objects.filter(trigger_type=trigger_type, organization_id=organization_id, team_id=team_id) + + # check if there are any webhooks before going on + if not webhooks: + return + send_webhook_event.apply_async( - (trigger_type, data), kwargs={"organization_id": organization_id, "team_id": team_id} + (trigger_type, alert_group_id), + kwargs={"organization_id": organization_id, "team_id": team_id, "user_id": user_id}, ) diff --git a/engine/apps/webhooks/tasks/trigger_webhook.py b/engine/apps/webhooks/tasks/trigger_webhook.py index 19ec0961..6dffb655 100644 --- a/engine/apps/webhooks/tasks/trigger_webhook.py +++ b/engine/apps/webhooks/tasks/trigger_webhook.py @@ -7,8 +7,16 @@ from django.apps import apps from django.conf import settings from django.utils import timezone -from apps.webhooks.models import WebhookLog -from apps.webhooks.utils import InvalidWebhookData, InvalidWebhookHeaders, InvalidWebhookTrigger, InvalidWebhookUrl +from apps.alerts.models import AlertGroup +from apps.user_management.models import User +from apps.webhooks.models import Webhook, WebhookLog +from apps.webhooks.utils import ( + InvalidWebhookData, + InvalidWebhookHeaders, + InvalidWebhookTrigger, + InvalidWebhookUrl, + serialize_event, +) from common.custom_celery_tasks import shared_dedicated_queue_retry_task logger = get_task_logger(__name__) @@ -18,20 +26,72 @@ logger.setLevel(logging.DEBUG) @shared_dedicated_queue_retry_task( autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else None ) -def send_webhook_event(trigger_type, data, team_id=None, organization_id=None): +def send_webhook_event(trigger_type, alert_group_id, team_id=None, organization_id=None, user_id=None): Webhooks = apps.get_model("webhooks", "Webhook") webhooks_qs = Webhooks.objects.filter(trigger_type=trigger_type, organization_id=organization_id, team_id=team_id) for webhook in webhooks_qs: - execute_webhook.apply_async((webhook.pk, data)) + execute_webhook.apply_async((webhook.pk, alert_group_id, user_id)) + + +def _isoformat_date(date_value): + return date_value.isoformat() if date_value else None + + +def _build_payload(trigger_type, alert_group_id, user_id): + user = None + try: + alert_group = AlertGroup.unarchived_objects.get(pk=alert_group_id) + if user_id is not None: + user = User.objects.filter(pk=user_id).first() + except AlertGroup.DoesNotExist: + return + + if trigger_type == Webhook.TRIGGER_NEW: + event = { + "type": "Firing", + "time": _isoformat_date(alert_group.started_at), + } + elif trigger_type == Webhook.TRIGGER_ACKNOWLEDGE: + event = { + "type": "Acknowledge", + "time": _isoformat_date(alert_group.acknowledged_at), + } + elif trigger_type == Webhook.TRIGGER_RESOLVE: + event = { + "type": "Resolve", + "time": _isoformat_date(alert_group.resolved_at), + } + elif trigger_type == Webhook.TRIGGER_SILENCE: + event = { + "type": "Silence", + "time": _isoformat_date(alert_group.silenced_at), + "until": _isoformat_date(alert_group.silenced_until), + } + elif trigger_type == Webhook.TRIGGER_UNSILENCE: + event = { + "type": "Unsilence", + } + elif trigger_type == Webhook.TRIGGER_UNRESOLVE: + event = { + "type": "Unresolve", + } + data = serialize_event(event, alert_group, user) + return data @shared_dedicated_queue_retry_task( autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else None ) -def execute_webhook(webhook_pk, data): +def execute_webhook(webhook_pk, alert_group_id, user_id): Webhooks = apps.get_model("webhooks", "Webhook") + try: + webhook = Webhooks.objects.get(pk=webhook_pk) + except Webhooks.DoesNotExist: + logger.warn(f"Webhook {webhook_pk} does not exist") + return + data = _build_payload(webhook.trigger_type, alert_group_id, user_id) status = { "last_run_at": timezone.now(), "input_data": data, @@ -45,7 +105,6 @@ def execute_webhook(webhook_pk, data): exception = None try: - webhook = Webhooks.objects.get(pk=webhook_pk) triggered, status["trigger"] = webhook.check_trigger(data) if triggered: status["url"] = webhook.build_url(data) @@ -66,9 +125,6 @@ def execute_webhook(webhook_pk, data): else: # do not add a log entry if the webhook is not triggered return - except Webhooks.DoesNotExist: - logger.warn(f"Webhook {webhook_pk} does not exist") - return except InvalidWebhookUrl as e: status["url"] = e.message except InvalidWebhookTrigger as e: diff --git a/engine/apps/webhooks/tests/test_alert_group_status_change.py b/engine/apps/webhooks/tests/test_alert_group_status_change.py index 8a55c599..497fd7d1 100644 --- a/engine/apps/webhooks/tests/test_alert_group_status_change.py +++ b/engine/apps/webhooks/tests/test_alert_group_status_change.py @@ -4,67 +4,53 @@ import pytest from django.utils import timezone from apps.alerts.models import AlertGroup, AlertGroupLogRecord -from apps.public_api.serializers import IncidentSerializer from apps.webhooks.models import Webhook from apps.webhooks.tasks import alert_group_created, alert_group_status_change @pytest.mark.django_db -def test_alert_group_created(make_organization, make_alert_receive_channel, make_alert_group): +def test_alert_group_created(make_organization, make_alert_receive_channel, make_alert_group, make_custom_webhook): organization = make_organization() alert_receive_channel = make_alert_receive_channel(organization) alert_group = make_alert_group(alert_receive_channel) + # make sure there is a webhook setup + make_custom_webhook(organization=organization, trigger_type=Webhook.TRIGGER_NEW) with patch("apps.webhooks.tasks.trigger_webhook.send_webhook_event.apply_async") as mock_send_event: alert_group_created(alert_group.pk) assert mock_send_event.called - expected_data = { - "event": { - "type": "Firing", - "time": alert_group.started_at, - }, - "user": None, - "alert_group": IncidentSerializer(alert_group).data, - "alert_group_id": alert_group.public_primary_key, - "alert_payload": "", - } - assert mock_send_event.call_args == call( - (Webhook.TRIGGER_NEW, expected_data), kwargs={"organization_id": organization.pk, "team_id": None} + (Webhook.TRIGGER_NEW, alert_group.pk), kwargs={"organization_id": organization.pk, "team_id": None} ) @pytest.mark.django_db -def test_alert_group_created_for_team(make_organization, make_team, make_alert_receive_channel, make_alert_group): +def test_alert_group_created_for_team( + make_organization, make_team, make_alert_receive_channel, make_alert_group, make_custom_webhook +): organization = make_organization() team = make_team(organization) alert_receive_channel = make_alert_receive_channel(organization, team=team) alert_group = make_alert_group(alert_receive_channel) + # make sure there is a webhook setup + make_custom_webhook(organization=organization, team=team, trigger_type=Webhook.TRIGGER_NEW) with patch("apps.webhooks.tasks.trigger_webhook.send_webhook_event.apply_async") as mock_send_event: alert_group_created(alert_group.pk) assert mock_send_event.called - expected_data = { - "event": { - "type": "Firing", - "time": alert_group.started_at, - }, - "user": None, - "alert_group": IncidentSerializer(alert_group).data, - "alert_group_id": alert_group.public_primary_key, - "alert_payload": "", - } - assert mock_send_event.call_args == call( - (Webhook.TRIGGER_NEW, expected_data), kwargs={"organization_id": organization.pk, "team_id": team.pk} + (Webhook.TRIGGER_NEW, alert_group.pk), kwargs={"organization_id": organization.pk, "team_id": team.pk} ) @pytest.mark.django_db -def test_alert_group_created_does_not_exist(): +def test_alert_group_created_does_not_exist(make_organization, make_custom_webhook): assert AlertGroup.all_objects.filter(pk=53).first() is None + organization = make_organization() + # make sure there is a webhook setup + make_custom_webhook(organization=organization, trigger_type=Webhook.TRIGGER_NEW) with patch("apps.webhooks.tasks.trigger_webhook.send_webhook_event.apply_async") as mock_send_event: alert_group_created(53) @@ -74,13 +60,13 @@ def test_alert_group_created_does_not_exist(): @pytest.mark.django_db @pytest.mark.parametrize( - "action_type,event_type,webhook_type,time_field", + "action_type,webhook_type", [ - (AlertGroupLogRecord.TYPE_ACK, "Acknowledge", Webhook.TRIGGER_ACKNOWLEDGE, "acknowledged_at"), - (AlertGroupLogRecord.TYPE_RESOLVED, "Resolve", Webhook.TRIGGER_RESOLVE, "resolved_at"), - (AlertGroupLogRecord.TYPE_SILENCE, "Silence", Webhook.TRIGGER_SILENCE, "silenced_at"), - (AlertGroupLogRecord.TYPE_UN_SILENCE, "Unsilence", Webhook.TRIGGER_UNSILENCE, None), - (AlertGroupLogRecord.TYPE_UN_RESOLVED, "Unresolve", Webhook.TRIGGER_UNRESOLVE, None), + (AlertGroupLogRecord.TYPE_ACK, Webhook.TRIGGER_ACKNOWLEDGE), + (AlertGroupLogRecord.TYPE_RESOLVED, Webhook.TRIGGER_RESOLVE), + (AlertGroupLogRecord.TYPE_SILENCE, Webhook.TRIGGER_SILENCE), + (AlertGroupLogRecord.TYPE_UN_SILENCE, Webhook.TRIGGER_UNSILENCE), + (AlertGroupLogRecord.TYPE_UN_RESOLVED, Webhook.TRIGGER_UNRESOLVE), ], ) def test_alert_group_status_change( @@ -88,40 +74,31 @@ def test_alert_group_status_change( make_user_for_organization, make_alert_receive_channel, make_alert_group, + make_custom_webhook, action_type, - event_type, webhook_type, - time_field, ): organization = make_organization() user = make_user_for_organization(organization) alert_receive_channel = make_alert_receive_channel(organization) alert_group = make_alert_group(alert_receive_channel) + # make sure there is a webhook setup + make_custom_webhook(organization=organization, trigger_type=webhook_type) with patch("apps.webhooks.tasks.trigger_webhook.send_webhook_event.apply_async") as mock_send_event: alert_group_status_change(action_type, alert_group.pk, user.pk) - expected_data = { - "event": { - "type": event_type, - }, - "user": user.username, - "alert_group": IncidentSerializer(alert_group).data, - "alert_group_id": alert_group.public_primary_key, - "alert_payload": "", - } - if time_field is not None: - expected_data["event"]["time"] = getattr(alert_group, time_field) - if action_type == AlertGroupLogRecord.TYPE_SILENCE: - expected_data["event"]["until"] = alert_group.silenced_until assert mock_send_event.call_args == call( - (webhook_type, expected_data), kwargs={"organization_id": organization.pk, "team_id": None} + (webhook_type, alert_group.pk), kwargs={"organization_id": organization.pk, "team_id": None, "user_id": user.pk} ) @pytest.mark.django_db -def test_alert_group_status_change_does_not_exist(): +def test_alert_group_status_change_does_not_exist(make_organization, make_custom_webhook): assert AlertGroup.all_objects.filter(pk=53).first() is None + organization = make_organization() + # make sure there is a webhook setup + make_custom_webhook(organization=organization, trigger_type=Webhook.TRIGGER_ACKNOWLEDGE) with patch("apps.webhooks.tasks.trigger_webhook.send_webhook_event.apply_async") as mock_send_event: alert_group_status_change(AlertGroupLogRecord.TYPE_ACK, 53, None) @@ -130,25 +107,20 @@ def test_alert_group_status_change_does_not_exist(): @pytest.mark.django_db -def test_alert_group_status_change_for_team(make_organization, make_team, make_alert_receive_channel, make_alert_group): +def test_alert_group_status_change_for_team( + make_organization, make_team, make_alert_receive_channel, make_alert_group, make_custom_webhook +): organization = make_organization() team = make_team(organization) alert_receive_channel = make_alert_receive_channel(organization, team=team) alert_group = make_alert_group(alert_receive_channel, resolved=True, resolved_at=timezone.now()) + # make sure there is a webhook setup + make_custom_webhook(organization=organization, team=team, trigger_type=Webhook.TRIGGER_RESOLVE) with patch("apps.webhooks.tasks.trigger_webhook.send_webhook_event.apply_async") as mock_send_event: alert_group_status_change(AlertGroupLogRecord.TYPE_RESOLVED, alert_group.pk, None) - expected_data = { - "event": { - "type": "Resolve", - "time": alert_group.resolved_at, - }, - "user": None, - "alert_group": IncidentSerializer(alert_group).data, - "alert_group_id": alert_group.public_primary_key, - "alert_payload": "", - } assert mock_send_event.call_args == call( - (Webhook.TRIGGER_RESOLVE, expected_data), kwargs={"organization_id": organization.pk, "team_id": team.pk} + (Webhook.TRIGGER_RESOLVE, alert_group.pk), + kwargs={"organization_id": organization.pk, "team_id": team.pk, "user_id": None}, ) diff --git a/engine/apps/webhooks/tests/test_trigger_webhook.py b/engine/apps/webhooks/tests/test_trigger_webhook.py index 352efb2c..ba41d11d 100644 --- a/engine/apps/webhooks/tests/test_trigger_webhook.py +++ b/engine/apps/webhooks/tests/test_trigger_webhook.py @@ -2,7 +2,9 @@ import json from unittest.mock import call, patch import pytest +from django.utils import timezone +from apps.public_api.serializers import IncidentSerializer from apps.webhooks.models import Webhook from apps.webhooks.tasks import execute_webhook, send_webhook_event @@ -16,10 +18,14 @@ class MockResponse: @pytest.mark.django_db -def test_send_webhook_event_filters(make_organization, make_team, make_custom_webhook): +def test_send_webhook_event_filters( + make_organization, make_team, make_alert_receive_channel, make_alert_group, make_custom_webhook +): organization = make_organization() other_organization = make_organization() other_team = make_team(organization) + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) webhooks = {} for trigger_type, _ in Webhook.TRIGGER_TYPES: @@ -33,74 +39,93 @@ def test_send_webhook_event_filters(make_organization, make_team, make_custom_we ) other_org_webhook = make_custom_webhook(organization=other_organization, trigger_type=Webhook.TRIGGER_NEW) - sample_data = {"field": "value"} for trigger_type, _ in Webhook.TRIGGER_TYPES: with patch("apps.webhooks.tasks.trigger_webhook.execute_webhook.apply_async") as mock_execute: - send_webhook_event(trigger_type, sample_data, organization_id=organization.pk) - assert mock_execute.call_args == call((webhooks[trigger_type].pk, sample_data)) + send_webhook_event(trigger_type, alert_group.pk, organization_id=organization.pk) + assert mock_execute.call_args == call((webhooks[trigger_type].pk, alert_group.pk, None)) # other team + alert_receive_channel = make_alert_receive_channel(organization, team=other_team) + alert_group = make_alert_group(alert_receive_channel) with patch("apps.webhooks.tasks.trigger_webhook.execute_webhook.apply_async") as mock_execute: send_webhook_event( - Webhook.TRIGGER_ACKNOWLEDGE, sample_data, organization_id=organization.pk, team_id=other_team.pk + Webhook.TRIGGER_ACKNOWLEDGE, alert_group.pk, organization_id=organization.pk, team_id=other_team.pk ) - assert mock_execute.call_args == call((other_team_webhook.pk, sample_data)) + assert mock_execute.call_args == call((other_team_webhook.pk, alert_group.pk, None)) # other org + alert_receive_channel = make_alert_receive_channel(other_organization) + alert_group = make_alert_group(alert_receive_channel) with patch("apps.webhooks.tasks.trigger_webhook.execute_webhook.apply_async") as mock_execute: - send_webhook_event(Webhook.TRIGGER_NEW, sample_data, organization_id=other_organization.pk) - assert mock_execute.call_args == call((other_org_webhook.pk, sample_data)) + send_webhook_event(Webhook.TRIGGER_NEW, alert_group.pk, organization_id=other_organization.pk) + assert mock_execute.call_args == call((other_org_webhook.pk, alert_group.pk, None)) @pytest.mark.django_db -def test_execute_webhook_ok(make_organization, make_custom_webhook): - # set trigger, build_url, build_requests_args, check status/log +def test_execute_webhook_ok( + make_organization, make_user_for_organization, make_alert_receive_channel, make_alert_group, make_custom_webhook +): organization = make_organization() + user = make_user_for_organization(organization) + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group( + alert_receive_channel, acknowledged_at=timezone.now(), acknowledged=True, acknowledged_by=user.pk + ) webhook = make_custom_webhook( organization=organization, - url="https://something/{{ alert_id }}/", + url="https://something/{{ alert_group_id }}/", http_method="POST", trigger_type=Webhook.TRIGGER_ACKNOWLEDGE, - trigger_template="{{ integration_id == 'the-integration' }}", - headers='{"some-header": "{{ alert_id }}"}', - data='{"value": "{{ value }}"}', + trigger_template="{{{{ alert_group.integration_id == '{}' }}}}".format( + alert_receive_channel.public_primary_key + ), + headers='{"some-header": "{{ alert_group_id }}"}', + data='{"value": "{{ alert_group_id }}"}', forward_all=False, ) - data = { - "integration_id": "the-integration", - "alert_id": "ID123", - "value": "42", - } mock_response = MockResponse() with patch("apps.webhooks.utils.socket.gethostbyname") as mock_gethostbyname: mock_gethostbyname.return_value = "8.8.8.8" with patch("apps.webhooks.models.webhook.requests") as mock_requests: mock_requests.post.return_value = mock_response - execute_webhook(webhook.pk, data) + execute_webhook(webhook.pk, alert_group.pk, user.pk) assert mock_requests.post.called expected_call = call( - "https://something/ID123/", + "https://something/{}/".format(alert_group.public_primary_key), timeout=10, - headers={"some-header": "ID123"}, - json={"value": "42"}, + headers={"some-header": alert_group.public_primary_key}, + json={"value": alert_group.public_primary_key}, ) assert mock_requests.post.call_args == expected_call # check logs log = webhook.logs.all()[0] assert log.response_status == "200" assert log.response == json.dumps(mock_response.json()) - assert log.input_data == data - assert log.data == json.dumps({"value": "42"}) - assert log.headers == json.dumps({"some-header": "ID123"}) - assert log.url == "https://something/ID123/" + expected_data = { + "event": { + "type": "Acknowledge", + "time": alert_group.acknowledged_at.isoformat(), + }, + "user": user.username, + "alert_group": IncidentSerializer(alert_group).data, + "alert_group_id": alert_group.public_primary_key, + "alert_payload": "", + } + assert log.input_data == expected_data + assert log.data == json.dumps({"value": alert_group.public_primary_key}) + assert log.headers == json.dumps({"some-header": alert_group.public_primary_key}) + assert log.url == "https://something/{}/".format(alert_group.public_primary_key) @pytest.mark.django_db -def test_execute_webhook_trigger_false(make_organization, make_custom_webhook): - # set trigger, build_url, build_requests_args, check status/log +def test_execute_webhook_trigger_false( + make_organization, make_alert_receive_channel, make_alert_group, make_custom_webhook +): organization = make_organization() + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, acknowledged_at=timezone.now(), acknowledged=True) webhook = make_custom_webhook( organization=organization, url="https://something/{{ alert_id }}/", @@ -108,14 +133,9 @@ def test_execute_webhook_trigger_false(make_organization, make_custom_webhook): trigger_type=Webhook.TRIGGER_ACKNOWLEDGE, trigger_template="{{ integration_id == 'the-integration' }}", ) - data = { - "integration_id": "other-integration", - "alert_id": "ID123", - "value": "42", - } with patch("apps.webhooks.models.webhook.requests") as mock_requests: - execute_webhook(webhook.pk, data) + execute_webhook(webhook.pk, alert_group.pk, None) assert not mock_requests.post.called # check no logs @@ -128,9 +148,9 @@ def test_execute_webhook_trigger_false(make_organization, make_custom_webhook): [ ( "url", - "https://myserver/{{ alert_payload.id }}/triggered", + "https://myserver/{{ }}/triggered", "url", - "URL - Template Warning: 'alert_payload' is undefined", + "URL - Template Error: Expected an expression, got 'end of print statement'", ), ( "trigger_template", @@ -143,35 +163,50 @@ def test_execute_webhook_trigger_false(make_organization, make_custom_webhook): ], ) def test_execute_webhook_errors( - make_organization, make_custom_webhook, field_name, value, log_field_name, expected_error + make_organization, + make_alert_receive_channel, + make_alert_group, + make_custom_webhook, + field_name, + value, + log_field_name, + expected_error, ): organization = make_organization() + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, resolved_at=timezone.now(), resolved=True) + extra_kwargs = {field_name: value} if "url" not in extra_kwargs: extra_kwargs["url"] = "https://something.cool/" webhook = make_custom_webhook( organization=organization, http_method="POST", - trigger_type=Webhook.TRIGGER_ACKNOWLEDGE, + trigger_type=Webhook.TRIGGER_RESOLVE, forward_all=False, **extra_kwargs, ) - data = { - "integration_id": "other-integration", - "alert_id": "ID123", - "value": "42", - } with patch("apps.webhooks.utils.socket.gethostbyname") as mock_gethostbyname: # make it a valid URL when resolving name mock_gethostbyname.return_value = "8.8.8.8" with patch("apps.webhooks.models.webhook.requests") as mock_requests: - execute_webhook(webhook.pk, data) + execute_webhook(webhook.pk, alert_group.pk, None) assert not mock_requests.post.called log = webhook.logs.all()[0] assert log.response_status is None assert log.response is None - assert log.input_data == data + expected_data = { + "event": { + "type": "Resolve", + "time": alert_group.resolved_at.isoformat(), + }, + "user": None, + "alert_group": IncidentSerializer(alert_group).data, + "alert_group_id": alert_group.public_primary_key, + "alert_payload": "", + } + assert log.input_data == expected_data error = getattr(log, log_field_name) assert error == expected_error