Просмотр исходного кода

feat(active-release): Add endpoint for suspect releases (#36877)

Add endpoint for suspect releases
Snigdha Sharma 2 лет назад
Родитель
Сommit
36edbec296

+ 84 - 0
src/sentry/api/endpoints/group_suspect_releases.py

@@ -0,0 +1,84 @@
+from datetime import timedelta
+
+from django.db.models import Q
+from rest_framework.request import Request
+from rest_framework.response import Response
+
+from sentry.api.base import EnvironmentMixin
+from sentry.api.bases import GroupEndpoint
+from sentry.api.serializers import serialize
+from sentry.models.deploy import Deploy
+from sentry.models.grouphistory import GroupHistory, GroupHistoryStatus
+from sentry.models.release import Release
+from sentry.types.ratelimit import RateLimit, RateLimitCategory
+
+
+class GroupSuspectReleasesEndpoint(GroupEndpoint, EnvironmentMixin):
+    private = True
+    enforce_rate_limit = True
+    rate_limits = {
+        "GET": {
+            RateLimitCategory.IP: RateLimit(5, 1),
+            RateLimitCategory.USER: RateLimit(5, 1),
+            RateLimitCategory.ORGANIZATION: RateLimit(5, 1),
+        },
+    }
+
+    def get(self, request: Request, group) -> Response:
+        """
+        Retrieve Suspect Releases for an Issue
+        ``````````````````````````````````````
+
+        Return the suspect releases for an issue. Suspect releases are defined
+        as releases which caused the first seen issue or the latest regression
+        during the active release window (1 hour).
+        :pparam string issue_id: the ID of the issue to retrieve.
+        :auth: required
+        """
+
+        organization = group.project.organization
+        suspect_releases = set()
+        regression = (
+            GroupHistory.objects.filter(group=group, status=GroupHistoryStatus.REGRESSED)
+            .order_by("-date_added")
+            .first()
+        )
+
+        latest_regression_date = None
+        if regression and regression.release:
+            suspect_releases.add(regression.release)
+        else:
+            latest_regression_date = regression.date_added if regression else None
+
+        first_seen = group.first_seen
+        deploy_filter = Q(
+            date_finished__gt=first_seen - timedelta(hours=1),
+            date_finished__lte=first_seen,
+        )
+        if latest_regression_date:
+            deploy_filter |= Q(
+                date_finished__gt=latest_regression_date - timedelta(hours=1),
+                date_finished__lte=latest_regression_date,
+            )
+
+        deploys = Deploy.objects.filter(
+            deploy_filter,
+            organization_id=organization.id,
+            release__projects__in=[group.project],
+        ).select_related("release")
+        suspect_releases.update({deploy.release for deploy in deploys})
+
+        releases = Release.objects.filter(
+            projects__in=[group.project],
+            date_released__gt=first_seen - timedelta(hours=1),
+            date_released__lte=first_seen,
+        )
+        suspect_releases.update(releases)
+
+        suspect_releases = serialize(suspect_releases, request.user)
+        data = {
+            "suspectReleases": list(
+                sorted(suspect_releases, key=lambda x: x["dateCreated"], reverse=True)
+            )
+        }
+        return Response(data)

+ 2 - 0
src/sentry/api/urls.py

@@ -145,6 +145,7 @@ from .endpoints.group_participants import GroupParticipantsEndpoint
 from .endpoints.group_reprocessing import GroupReprocessingEndpoint
 from .endpoints.group_similar_issues import GroupSimilarIssuesEndpoint
 from .endpoints.group_stats import GroupStatsEndpoint
+from .endpoints.group_suspect_releases import GroupSuspectReleasesEndpoint
 from .endpoints.group_tagkey_details import GroupTagKeyDetailsEndpoint
 from .endpoints.group_tagkey_values import GroupTagKeyValuesEndpoint
 from .endpoints.group_tags import GroupTagsEndpoint
@@ -504,6 +505,7 @@ GROUP_URLS = [
     url(r"^(?P<issue_id>[^\/]+)/events/$", GroupEventsEndpoint.as_view()),
     url(r"^(?P<issue_id>[^\/]+)/events/latest/$", GroupEventsLatestEndpoint.as_view()),
     url(r"^(?P<issue_id>[^\/]+)/events/oldest/$", GroupEventsOldestEndpoint.as_view()),
+    url(r"^(?P<issue_id>[^\/]+)/suspect-releases/$", GroupSuspectReleasesEndpoint.as_view()),
     url(r"^(?P<issue_id>[^\/]+)/(?:notes|comments)/$", GroupNotesEndpoint.as_view()),
     url(
         r"^(?P<issue_id>[^\/]+)/(?:notes|comments)/(?P<note_id>[^\/]+)/$",

+ 158 - 0
tests/sentry/api/endpoints/test_group_suspect_releases.py

@@ -0,0 +1,158 @@
+from datetime import timedelta
+
+from sentry.api.serializers import serialize
+from sentry.models import Environment
+from sentry.models.deploy import Deploy
+from sentry.models.grouphistory import GroupHistory, GroupHistoryStatus
+from sentry.testutils import APITestCase, SnubaTestCase
+
+
+class GroupSuspectReleasesTest(APITestCase, SnubaTestCase):
+    def test_no_suspect_releases(self):
+        self.login_as(user=self.user)
+
+        group = self.create_group()
+        url = f"/api/0/issues/{group.id}/suspect-releases/"
+        response = self.client.get(url, format="json")
+
+        assert response.status_code == 200, response.content
+        assert response.data["suspectReleases"] == []
+
+        release = self.create_release(
+            project=group.project,
+            version="1.0",
+            date_added=group.first_seen - timedelta(minutes=70),
+        )
+        environment = Environment.objects.create(
+            organization_id=self.organization.id, name="production"
+        )
+        Deploy.objects.create(
+            environment_id=environment.id,
+            organization_id=self.organization.id,
+            release=release,
+            date_finished=group.first_seen - timedelta(minutes=70),
+        )
+        response = self.client.get(url, format="json")
+        assert response.status_code == 200, response.content
+        assert response.data["suspectReleases"] == []
+
+    def test_with_suspect_release(self):
+        self.login_as(user=self.user)
+
+        group = self.create_group()
+        release = self.create_release(
+            project=group.project,
+            version="1.0",
+            date_added=group.first_seen - timedelta(minutes=30),
+        )
+        environment = Environment.objects.create(
+            organization_id=self.organization.id, name="production"
+        )
+        Deploy.objects.create(
+            environment_id=environment.id,
+            organization_id=self.organization.id,
+            release=release,
+            date_finished=group.first_seen - timedelta(minutes=30),
+        )
+
+        url = f"/api/0/issues/{group.id}/suspect-releases/"
+        response = self.client.get(url, format="json")
+
+        assert response.status_code == 200, response.content
+        assert response.data["suspectReleases"] == [serialize(release)]
+
+    def test_with_regression(self):
+        self.login_as(user=self.user)
+
+        group = self.create_group()
+        release = self.create_release(
+            project=group.project,
+            version="1.0",
+            date_added=group.first_seen - timedelta(minutes=30),
+        )
+        GroupHistory.objects.create(
+            group=group,
+            organization_id=self.organization.id,
+            project_id=group.project_id,
+            release=release,
+            status=GroupHistoryStatus.REGRESSED,
+        )
+
+        url = f"/api/0/issues/{group.id}/suspect-releases/"
+        response = self.client.get(url, format="json")
+
+        assert response.status_code == 200, response.content
+        assert response.data["suspectReleases"] == [serialize(release)]
+
+    def test_multiple_suspect_releases(self):
+        self.login_as(user=self.user)
+
+        group = self.create_group()
+        regression_release = self.create_release(
+            project=group.project,
+            version="1.0",
+            date_added=group.first_seen - timedelta(minutes=40),
+        )
+        GroupHistory.objects.create(
+            group=group,
+            organization_id=self.organization.id,
+            project_id=group.project_id,
+            release=regression_release,
+            status=GroupHistoryStatus.REGRESSED,
+        )
+
+        release = self.create_release(
+            project=group.project,
+            version="1.1",
+            date_added=group.first_seen - timedelta(minutes=30),
+        )
+        environment = Environment.objects.create(
+            organization_id=self.organization.id, name="production"
+        )
+        Deploy.objects.create(
+            environment_id=environment.id,
+            organization_id=self.organization.id,
+            release=release,
+            date_finished=group.first_seen - timedelta(minutes=30),
+        )
+
+        url = f"/api/0/issues/{group.id}/suspect-releases/"
+        response = self.client.get(url, format="json")
+
+        assert response.status_code == 200, response.content
+        assert response.data["suspectReleases"] == [
+            serialize(release),
+            serialize(regression_release),
+        ]
+
+    def test_duplicate_suspect_releases(self):
+        self.login_as(user=self.user)
+
+        group = self.create_group()
+        release = self.create_release(
+            project=group.project,
+            version="1.0",
+            date_added=group.first_seen - timedelta(minutes=40),
+        )
+        GroupHistory.objects.create(
+            group=group,
+            organization_id=self.organization.id,
+            project_id=group.project_id,
+            release=release,
+            status=GroupHistoryStatus.REGRESSED,
+        )
+        environment = Environment.objects.create(
+            organization_id=self.organization.id, name="production"
+        )
+        Deploy.objects.create(
+            environment_id=environment.id,
+            organization_id=self.organization.id,
+            release=release,
+            date_finished=group.first_seen - timedelta(minutes=50),
+        )
+
+        url = f"/api/0/issues/{group.id}/suspect-releases/"
+        response = self.client.get(url, format="json")
+
+        assert response.status_code == 200, response.content
+        assert response.data["suspectReleases"] == [serialize(release)]