From 060a5820af38045e094d136b27cd7bd5dc29fe0d Mon Sep 17 00:00:00 2001 From: Eric Hasegawa Date: Sun, 10 Sep 2023 15:26:19 -0700 Subject: [PATCH] feat(releases): Implement threshold details GET --- .../endpoints/release_threshold_details.py | 48 ++++++++++++ .../serializers/models/release_threshold.py | 1 + src/sentry/api/urls.py | 6 ++ .../test_release_threshold_details.py | 73 +++++++++++++++++++ 4 files changed, 128 insertions(+) create mode 100644 src/sentry/api/endpoints/release_threshold_details.py create mode 100644 tests/sentry/api/endpoints/test_release_threshold_details.py diff --git a/src/sentry/api/endpoints/release_threshold_details.py b/src/sentry/api/endpoints/release_threshold_details.py new file mode 100644 index 0000000000000..7b1cf38fb85b5 --- /dev/null +++ b/src/sentry/api/endpoints/release_threshold_details.py @@ -0,0 +1,48 @@ +from django.http import HttpResponse +from rest_framework import serializers +from rest_framework.request import Request +from rest_framework.response import Response + +from sentry.api.api_owners import ApiOwner +from sentry.api.api_publish_status import ApiPublishStatus +from sentry.api.base import region_silo_endpoint +from sentry.api.bases.project import ProjectEndpoint +from sentry.api.serializers import serialize +from sentry.api.serializers.rest_framework.project import ProjectField +from sentry.models import Project +from sentry.models.release_threshold.releasethreshold import ReleaseThreshold + + +class ReleaseThresholdDetailsGETSerializer(serializers.Serializer): + threshold_id = serializers.CharField() + project = ProjectField() + + +@region_silo_endpoint +class ReleaseThresholdDetailsEndpoint(ProjectEndpoint): + owner: ApiOwner = ApiOwner.ENTERPRISE + publish_status = { + "GET": ApiPublishStatus.PRIVATE, + } + + def get(self, request: Request, project: Project, threshold_id: str) -> HttpResponse: + request.data["project"] = project.slug + request.data["threshold_id"] = threshold_id + serializer = ReleaseThresholdDetailsGETSerializer( + data=request.data, + context={ + "organization": project.organization, + "access": request.access, + }, + ) + if not serializer.is_valid(): + return Response(serializer.errors, status=400) + result = serializer.validated_data + try: + release_threshold = ReleaseThreshold.objects.get( + id=result.get("threshold_id"), + project=project, + ) + return Response(serialize(release_threshold, request.user), status=200) + except ReleaseThreshold.DoesNotExist: + return Response(status=404) diff --git a/src/sentry/api/serializers/models/release_threshold.py b/src/sentry/api/serializers/models/release_threshold.py index 138bf81b6ba5e..87db1fc7fa6e9 100644 --- a/src/sentry/api/serializers/models/release_threshold.py +++ b/src/sentry/api/serializers/models/release_threshold.py @@ -10,6 +10,7 @@ class ReleaseThresholdSerializer(Serializer): def serialize(self, obj, attrs, user): return { + "id": str(obj.id), "threshold_type": THRESHOLD_TYPE_INT_TO_STR[obj.threshold_type], "trigger_type": TRIGGER_TYPE_INT_TO_STR[obj.trigger_type], "value": obj.value, diff --git a/src/sentry/api/urls.py b/src/sentry/api/urls.py index 39f6d220284a3..cb6cf447b0cc8 100644 --- a/src/sentry/api/urls.py +++ b/src/sentry/api/urls.py @@ -22,6 +22,7 @@ OrganizationProjectsExperimentEndpoint, ) from sentry.api.endpoints.release_threshold import ReleaseThresholdEndpoint +from sentry.api.endpoints.release_threshold_details import ReleaseThresholdDetailsEndpoint from sentry.api.utils import method_dispatch from sentry.data_export.endpoints.data_export import DataExportEndpoint from sentry.data_export.endpoints.data_export_details import DataExportDetailsEndpoint @@ -2106,6 +2107,11 @@ ReleaseThresholdEndpoint.as_view(), name="sentry-api-0-project-release-thresholds", ), + re_path( + r"^(?P[^\/]+)/(?P[^\/]+)/releases/thresholds/(?P[^/]+)/$", + ReleaseThresholdDetailsEndpoint.as_view(), + name="sentry-api-0-project-release-thresholds-details", + ), re_path( r"^(?P[^\/]+)/(?P[^\/]+)/commits/$", ProjectCommitsEndpoint.as_view(), diff --git a/tests/sentry/api/endpoints/test_release_threshold_details.py b/tests/sentry/api/endpoints/test_release_threshold_details.py new file mode 100644 index 0000000000000..8a453960567a0 --- /dev/null +++ b/tests/sentry/api/endpoints/test_release_threshold_details.py @@ -0,0 +1,73 @@ +from django.urls import reverse + +from sentry.models.environment import Environment +from sentry.models.release_threshold.releasethreshold import ReleaseThreshold +from sentry.testutils.cases import APITestCase + + +class ReleaseThresholdDetailsTest(APITestCase): + def setUp(self): + super().setUp() + self.user = self.create_user(is_staff=True, is_superuser=True) + + self.canary_environment = Environment.objects.create( + organization_id=self.organization.id, name="canary" + ) + self.production_environment = Environment.objects.create( + organization_id=self.organization.id, name="production" + ) + self.login_as(user=self.user) + + self.basic_threshold = ReleaseThreshold.objects.create( + threshold_type=0, + trigger_type=0, + value=100, + window_in_seconds=1800, + project=self.project, + environment=self.canary_environment, + ) + + def test_invalid_threshold_id(self): + url = reverse( + "sentry-api-0-project-release-thresholds-details", + kwargs={ + "organization_slug": self.organization.slug, + "project_slug": self.project.slug, + "threshold_id": 123, + }, + ) + response = self.client.get(url) + + assert response.status_code == 404 + + def test_invalid_project(self): + url = reverse( + "sentry-api-0-project-release-thresholds-details", + kwargs={ + "organization_slug": self.organization.slug, + "project_slug": "kingdom_of_the_crystal_skull", + "threshold_id": self.basic_threshold.id, + }, + ) + response = self.client.get(url) + + assert response.status_code == 404 + + def test_valid(self): + url = reverse( + "sentry-api-0-project-release-thresholds-details", + kwargs={ + "organization_slug": self.organization.slug, + "project_slug": self.project.slug, + "threshold_id": self.basic_threshold.id, + }, + ) + response = self.client.get(url) + + assert response.status_code == 200 + assert response.data["id"] == str(self.basic_threshold.id) + assert response.data["threshold_type"] == "total_error_count" + assert response.data["trigger_type"] == "percent_over" + assert response.data["value"] == 100 + assert response.data["window_in_seconds"] == 1800 + assert response.data["environment"]["name"] == "canary"