diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cdf60a4..a97214e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Helm chart: add the option to use a helm hook for the migration job ([1386](https://github.com/grafana/oncall/pull/1386)) - Add endpoints to start and stop maintenance in alert receive channel private api ([1755](https://github.com/grafana/oncall/pull/1755)) - Send demo alert with dynamic payload and get demo payload example on private api ([1700](https://github.com/grafana/oncall/pull/1700)) +- Allow use of dynamic payloads in alert receive channels preview template in private api ([1756](https://github.com/grafana/oncall/pull/1756)) ## v1.2.11 (2023-04-14) diff --git a/engine/apps/api/tests/test_alert_receive_channel.py b/engine/apps/api/tests/test_alert_receive_channel.py index 53996c56..9a487803 100644 --- a/engine/apps/api/tests/test_alert_receive_channel.py +++ b/engine/apps/api/tests/test_alert_receive_channel.py @@ -505,6 +505,44 @@ def test_alert_receive_channel_preview_template_require_notification_channel( assert response.status_code == status.HTTP_200_OK +@pytest.mark.django_db +@pytest.mark.parametrize("template_name", ["title", "message", "image_url"]) +@pytest.mark.parametrize("notification_channel", ["slack", "web", "telegram"]) +def test_alert_receive_channel_preview_template_dynamic_payload( + make_organization_and_user_with_plugin_token, + make_user_auth_headers, + make_alert_receive_channel, + template_name, + notification_channel, + make_alert_group, + make_alert, +): + organization, user, token = make_organization_and_user_with_plugin_token() + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + + make_alert(alert_group=alert_group, raw_request_data=alert_receive_channel.config.example_payload) + + client = APIClient() + url = reverse( + "api-internal:alert_receive_channel-preview-template", kwargs={"pk": alert_receive_channel.public_primary_key} + ) + + data = { + "template_body": "{{ payload.foo }}", + "template_name": f"{notification_channel}_{template_name}", + "payload": {"foo": "bar"}, + } + + response = client.post(url, data=data, format="json", **make_user_auth_headers(user, token)) + + assert response.status_code == status.HTTP_200_OK + if notification_channel == "web" and template_name == "message": + assert response.data["preview"] == "
bar
" + else: + assert response.data["preview"] == "bar" + + @pytest.mark.django_db @pytest.mark.parametrize( "role,expected_status", diff --git a/engine/apps/api/views/alert_group.py b/engine/apps/api/views/alert_group.py index a0a290be..96420041 100644 --- a/engine/apps/api/views/alert_group.py +++ b/engine/apps/api/views/alert_group.py @@ -657,5 +657,5 @@ class AlertGroupView( ) # This method is required for PreviewTemplateMixin - def get_alert_to_template(self): + def get_alert_to_template(self, payload=None): return self.get_object().alerts.first() diff --git a/engine/apps/api/views/alert_receive_channel.py b/engine/apps/api/views/alert_receive_channel.py index 5331dae3..ced06e18 100644 --- a/engine/apps/api/views/alert_receive_channel.py +++ b/engine/apps/api/views/alert_receive_channel.py @@ -8,7 +8,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet -from apps.alerts.models import AlertReceiveChannel +from apps.alerts.models import Alert, AlertGroup, AlertReceiveChannel from apps.alerts.models.maintainable_object import MaintainableObject from apps.api.permissions import RBACPermission from apps.api.serializers.alert_receive_channel import ( @@ -22,6 +22,7 @@ from common.api_helpers.exceptions import BadRequest from common.api_helpers.filters import ByTeamModelFieldFilterMixin, TeamModelMultipleChoiceFilter from common.api_helpers.mixins import ( FilterSerializerMixin, + PreviewTemplateException, PreviewTemplateMixin, PublicPrimaryKeyMixin, TeamFilteringMixin, @@ -228,9 +229,16 @@ class AlertReceiveChannelView( return Response(response) # This method is required for PreviewTemplateMixin - def get_alert_to_template(self): + def get_alert_to_template(self, payload=None): try: - return self.get_object().alert_groups.last().alerts.first() + if payload is None: + return self.get_object().alert_groups.last().alerts.first() + else: + if type(payload) != dict: + raise PreviewTemplateException("Payload must be a valid json object") + # Build Alert and AlertGroup objects to pass to templater without saving them to db + alert_group_to_template = AlertGroup(channel=self.get_object()) + return Alert(raw_request_data=payload, group=alert_group_to_template) except AttributeError: return None diff --git a/engine/common/api_helpers/mixins.py b/engine/common/api_helpers/mixins.py index 271a9555..9889e642 100644 --- a/engine/common/api_helpers/mixins.py +++ b/engine/common/api_helpers/mixins.py @@ -283,11 +283,16 @@ BEHAVIOUR_TEMPLATE_NAMES = [RESOLVE_CONDITION, ACKNOWLEDGE_CONDITION, GROUPING_I ALL_TEMPLATE_NAMES = APPEARANCE_TEMPLATE_NAMES + BEHAVIOUR_TEMPLATE_NAMES +class PreviewTemplateException(Exception): + pass + + class PreviewTemplateMixin: @action(methods=["post"], detail=True) def preview_template(self, request, pk): template_body = request.data.get("template_body", None) template_name = request.data.get("template_name", None) + payload = request.data.get("payload", None) if template_body is None or template_name is None: response = {"preview": None} @@ -295,18 +300,21 @@ class PreviewTemplateMixin: notification_channel, attr_name = self.parse_name_and_notification_channel(template_name) if attr_name is None: - raise BadRequest(detail={"template_name": "Attr name is required"}) + raise BadRequest(detail={"template_name": "Template name is missing"}) if attr_name not in ALL_TEMPLATE_NAMES: - raise BadRequest(detail={"template_name": "Unknown attr name"}) + raise BadRequest(detail={"template_name": "Unknown template name"}) if attr_name in APPEARANCE_TEMPLATE_NAMES: if notification_channel is None: raise BadRequest(detail={"notification_channel": "notification_channel is required"}) if notification_channel not in NOTIFICATION_CHANNEL_OPTIONS: raise BadRequest(detail={"notification_channel": "Unknown notification_channel"}) - alert_to_template = self.get_alert_to_template() - if alert_to_template is None: - raise BadRequest(detail="Alert to preview does not exist") + try: + alert_to_template = self.get_alert_to_template(payload=payload) + if alert_to_template is None: + raise BadRequest(detail="Alert to preview does not exist") + except PreviewTemplateException as e: + raise BadRequest(detail=str(e)) if attr_name in APPEARANCE_TEMPLATE_NAMES: @@ -337,7 +345,7 @@ class PreviewTemplateMixin: response = {"preview": templated_attr} return Response(response, status=status.HTTP_200_OK) - def get_alert_to_template(self): + def get_alert_to_template(self, payload=None): raise NotImplementedError @staticmethod