From d7e2f7053dcb5afd8bc7e25561279f5684e3d2ff Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Thu, 3 Aug 2023 11:12:52 +0200 Subject: [PATCH] API for grafana alerting (including test fix) (#2737) # What this PR does This PR is related to #2645. That PR was reverted in #2730. This reverts the revert + adds a fix for the test that was failing ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Innokentii Konstantinov --- .../api/serializers/alert_receive_channel.py | 2 +- .../api/tests/test_alert_receive_channel.py | 35 +++++++++++++++++++ .../apps/api/views/alert_receive_channel.py | 32 +++++++++++++++-- engine/apps/api/views/features.py | 4 +++ engine/settings/base.py | 1 + 5 files changed, 71 insertions(+), 3 deletions(-) diff --git a/engine/apps/api/serializers/alert_receive_channel.py b/engine/apps/api/serializers/alert_receive_channel.py index 6d15085e..abeb687b 100644 --- a/engine/apps/api/serializers/alert_receive_channel.py +++ b/engine/apps/api/serializers/alert_receive_channel.py @@ -227,7 +227,7 @@ class FilterAlertReceiveChannelSerializer(serializers.ModelSerializer): class Meta: model = AlertReceiveChannel - fields = ["value", "display_name"] + fields = ["value", "display_name", "integration_url"] def get_value(self, obj): return obj.public_primary_key diff --git a/engine/apps/api/tests/test_alert_receive_channel.py b/engine/apps/api/tests/test_alert_receive_channel.py index ce546a6f..e13b1204 100644 --- a/engine/apps/api/tests/test_alert_receive_channel.py +++ b/engine/apps/api/tests/test_alert_receive_channel.py @@ -33,6 +33,41 @@ def test_get_alert_receive_channel(alert_receive_channel_internal_api_setup, mak assert response.status_code == status.HTTP_200_OK +@pytest.mark.django_db +@pytest.mark.parametrize( + "query_param,should_be_unpaginated", + [ + ("True", True), + ("true", True), + ("TRUE", True), + ("", False), + ("False", False), + ("false", False), + ("FALSE", False), + ], +) +def test_list_alert_receive_channel_skip_pagination_for_grafana_alerting( + alert_receive_channel_internal_api_setup, + make_user_auth_headers, + query_param, + should_be_unpaginated, +): + user, token, _ = alert_receive_channel_internal_api_setup + client = APIClient() + + url = reverse("api-internal:alert_receive_channel-list") + response = client.get(f"{url}?skip_pagination={query_param}", format="json", **make_user_auth_headers(user, token)) + results = response.json() + assert response.status_code == status.HTTP_200_OK + + if should_be_unpaginated: + assert type(results) == list + assert len(results) > 0 + else: + assert type(results["results"]) == list + assert len(results["results"]) > 0 + + @pytest.mark.django_db def test_heartbeat_data_absence_alert_receive_channel(alert_receive_channel_internal_api_setup, make_user_auth_headers): """ diff --git a/engine/apps/api/views/alert_receive_channel.py b/engine/apps/api/views/alert_receive_channel.py index 2be1cb24..c468b18b 100644 --- a/engine/apps/api/views/alert_receive_channel.py +++ b/engine/apps/api/views/alert_receive_channel.py @@ -38,7 +38,7 @@ class AlertReceiveChannelFilter(ByTeamModelFieldFilterMixin, filters.FilterSet): maintenance_mode = filters.MultipleChoiceFilter( choices=AlertReceiveChannel.MAINTENANCE_MODE_CHOICES, method="filter_maintenance_mode" ) - integration = filters.ChoiceFilter(choices=AlertReceiveChannel.INTEGRATION_CHOICES) + integration = filters.MultipleChoiceFilter(choices=AlertReceiveChannel.INTEGRATION_CHOICES) team = TeamModelMultipleChoiceFilter() class Meta: @@ -80,7 +80,7 @@ class AlertReceiveChannelView( update_serializer_class = AlertReceiveChannelUpdateSerializer filter_backends = [SearchFilter, DjangoFilterBackend] - search_fields = ("verbal_name", "integration") + search_fields = ("verbal_name",) filterset_class = AlertReceiveChannelFilter pagination_class = FifteenPageSizePaginator @@ -102,6 +102,7 @@ class AlertReceiveChannelView( "filters": [RBACPermission.Permissions.INTEGRATIONS_READ], "start_maintenance": [RBACPermission.Permissions.INTEGRATIONS_WRITE], "stop_maintenance": [RBACPermission.Permissions.INTEGRATIONS_WRITE], + "validate_name": [RBACPermission.Permissions.INTEGRATIONS_WRITE], "migrate": [RBACPermission.Permissions.INTEGRATIONS_WRITE], } @@ -144,6 +145,15 @@ class AlertReceiveChannelView( return queryset + def paginate_queryset(self, queryset): + """ + If `skip_pagination` is provided and is equal to "true" (or "True"), it will return + a non paginated list of results. This is useful for Grafana Alerting + """ + if self.request.query_params.get("skip_pagination", "false").lower() == "true": + return None + return super().paginate_queryset(queryset) + @action(detail=True, methods=["post"], throttle_classes=[DemoAlertThrottler]) def send_demo_alert(self, request, pk): instance = self.get_object() @@ -333,3 +343,21 @@ class AlertReceiveChannelView( instance.save() return Response(status=status.HTTP_200_OK) + + @action(detail=False, methods=["get"]) + def validate_name(self, request): + """ + Checks if verbal_name is available. + It is needed for OnCall <-> Alerting integration. + """ + verbal_name = self.request.query_params.get("verbal_name") + if verbal_name is None: + raise BadRequest("verbal_name is required") + organization = self.request.auth.organization + name_used = AlertReceiveChannel.objects.filter(organization=organization, verbal_name=verbal_name).exists() + if name_used: + r = Response(status=status.HTTP_409_CONFLICT) + else: + r = Response(status=status.HTTP_200_OK) + + return r diff --git a/engine/apps/api/views/features.py b/engine/apps/api/views/features.py index 93fcba78..a3e92018 100644 --- a/engine/apps/api/views/features.py +++ b/engine/apps/api/views/features.py @@ -10,6 +10,7 @@ FEATURE_TELEGRAM = "telegram" FEATURE_LIVE_SETTINGS = "live_settings" FEATURE_GRAFANA_CLOUD_NOTIFICATIONS = "grafana_cloud_notifications" FEATURE_GRAFANA_CLOUD_CONNECTION = "grafana_cloud_connection" +FEATURE_GRAFANA_ALERTING_V2 = "grafana_alerting_v2" class FeaturesAPIView(APIView): @@ -40,4 +41,7 @@ class FeaturesAPIView(APIView): if live_settings.GRAFANA_CLOUD_NOTIFICATIONS_ENABLED: enabled_features.append(FEATURE_GRAFANA_CLOUD_NOTIFICATIONS) + if settings.FEATURE_GRAFANA_ALERTING_V2_ENABLED: + enabled_features.append(FEATURE_GRAFANA_ALERTING_V2) + return enabled_features diff --git a/engine/settings/base.py b/engine/settings/base.py index bffb2992..29f08f38 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -65,6 +65,7 @@ FEATURE_MULTIREGION_ENABLED = getenv_boolean("FEATURE_MULTIREGION_ENABLED", defa FEATURE_INBOUND_EMAIL_ENABLED = getenv_boolean("FEATURE_INBOUND_EMAIL_ENABLED", default=False) FEATURE_PROMETHEUS_EXPORTER_ENABLED = getenv_boolean("FEATURE_PROMETHEUS_EXPORTER_ENABLED", default=False) FEATURE_SHIFT_SWAPS_ENABLED = getenv_boolean("FEATURE_SHIFT_SWAPS_ENABLED", default=False) +FEATURE_GRAFANA_ALERTING_V2_ENABLED = getenv_boolean("FEATURE_GRAFANA_ALERTING_V2_ENABLED", default=False) GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED = getenv_boolean("GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED", default=True) GRAFANA_CLOUD_NOTIFICATIONS_ENABLED = getenv_boolean("GRAFANA_CLOUD_NOTIFICATIONS_ENABLED", default=True)