Browse Source

feat(app-platform): Delete External Issue (#12438)

Adds an endpoint to delete External Issues created through Sentry Apps.
It only deletes ones made through Sentry Apps for the time being, but
may end up being the general place for this in the future.
Matte Noble 6 years ago
parent
commit
bba24ec44f

+ 30 - 0
src/sentry/api/endpoints/group_external_issue_details.py

@@ -0,0 +1,30 @@
+from __future__ import absolute_import
+
+from rest_framework.response import Response
+
+from sentry import features
+from sentry.api.bases.group import GroupEndpoint
+from sentry.mediators import external_issues
+from sentry.models import PlatformExternalIssue
+
+
+class GroupExternalIssueDetailsEndpoint(GroupEndpoint):
+    def delete(self, request, external_issue_id, group):
+        if not features.has('organizations:sentry-apps',
+                            group.organization,
+                            actor=request.user):
+            return Response(status=404)
+
+        try:
+            external_issue = PlatformExternalIssue.objects.get(
+                id=external_issue_id,
+                group_id=group.id,
+            )
+        except PlatformExternalIssue.DoesNotExist:
+            return Response(status=404)
+
+        external_issues.Destroyer.run(
+            external_issue=external_issue
+        )
+
+        return Response(status=204)

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

@@ -39,6 +39,7 @@ from .endpoints.group_notes import GroupNotesEndpoint
 from .endpoints.group_notes_details import GroupNotesDetailsEndpoint
 from .endpoints.group_participants import GroupParticipantsEndpoint
 from .endpoints.group_external_issues import GroupExternalIssuesEndpoint
+from .endpoints.group_external_issue_details import GroupExternalIssueDetailsEndpoint
 from .endpoints.group_similar_issues import GroupSimilarIssuesEndpoint
 from .endpoints.group_stats import GroupStatsEndpoint
 from .endpoints.group_tags import GroupTagsEndpoint
@@ -1214,6 +1215,11 @@ urlpatterns = patterns(
         GroupExternalIssuesEndpoint.as_view(),
         name='sentry-api-0-group-external-issues'
     ),
+    url(
+        r'^(?:issues|groups)/(?P<issue_id>\d+)/external-issues/(?P<external_issue_id>\d+)/$',
+        GroupExternalIssueDetailsEndpoint.as_view(),
+        name='sentry-api-0-group-external-issue-details'
+    ),
     url(
         r'^(?:issues|groups)/(?P<issue_id>\d+)/integrations/$',
         GroupIntegrationsEndpoint.as_view(),

+ 1 - 0
src/sentry/mediators/external_issues/__init__.py

@@ -1,3 +1,4 @@
 from __future__ import absolute_import
 
 from .issue_link_creator import IssueLinkCreator  # NOQA
+from .destroyer import Destroyer  # NOQA

+ 20 - 0
src/sentry/mediators/external_issues/destroyer.py

@@ -0,0 +1,20 @@
+from __future__ import absolute_import
+
+from sentry.mediators import Mediator, Param
+
+
+class Destroyer(Mediator):
+    external_issue = Param('sentry.models.PlatformExternalIssue')
+
+    def call(self):
+        self._delete_external_issue()
+        self._notify_sentry_app()
+        return True
+
+    def _delete_external_issue(self):
+        self.external_issue.delete()
+
+    def _notify_sentry_app(self):
+        """
+        Placeholder until implemented. Planned but not prioritized yet.
+        """

+ 11 - 1
src/sentry/testutils/factories.py

@@ -25,7 +25,7 @@ from sentry.models import (
     Activity, Environment, Event, EventError, EventMapping, Group, Organization, OrganizationMember,
     OrganizationMemberTeam, Project, ProjectBookmark, Team, User, UserEmail, Release, Commit, ReleaseCommit,
     CommitAuthor, Repository, CommitFileChange, ProjectDebugFile, File, UserPermission, EventAttachment,
-    UserReport,
+    UserReport, PlatformExternalIssue,
 )
 from sentry.utils.canonical import CanonicalKeyDict
 
@@ -821,3 +821,13 @@ class Factories(object):
         session = engine.SessionStore()
         session.save()
         return session
+
+    @staticmethod
+    def create_platform_external_issue(group=None, service_type=None,
+                                       display_name=None, web_url=None):
+        return PlatformExternalIssue.objects.create(
+            group_id=group.id,
+            service_type=service_type,
+            display_name=display_name,
+            web_url=web_url,
+        )

+ 3 - 0
src/sentry/testutils/fixtures.py

@@ -203,6 +203,9 @@ class Fixtures(object):
     def create_userreport(self, *args, **kwargs):
         return Factories.create_userreport(*args, **kwargs)
 
+    def create_platform_external_issue(self, *args, **kwargs):
+        return Factories.create_platform_external_issue(*args, **kwargs)
+
     @pytest.fixture(autouse=True)
     def _init_insta_snapshot(self, insta_snapshot):
         self.insta_snapshot = insta_snapshot

+ 69 - 0
tests/sentry/api/endpoints/test_group_external_issue_details.py

@@ -0,0 +1,69 @@
+from __future__ import absolute_import, print_function
+
+from sentry.models import PlatformExternalIssue
+from sentry.testutils import APITestCase
+from sentry.testutils.helpers import with_feature
+
+
+class GroupExternalIssueDetailsEndpointTest(APITestCase):
+    def setUp(self):
+        self.login_as(user=self.user)
+
+        self.group = self.create_group()
+        self.external_issue = self.create_platform_external_issue(
+            group=self.group,
+            service_type='sentry-app',
+            display_name='App#issue-1',
+            web_url='https://example.com/app/issues/1',
+        )
+
+        self.url = u'/api/0/issues/{}/external-issues/{}/'.format(
+            self.group.id,
+            self.external_issue.id,
+        )
+
+    @with_feature('organizations:sentry-apps')
+    def test_deletes_external_issue(self):
+        response = self.client.delete(self.url, format='json')
+
+        assert response.status_code == 204, response.content
+        assert not PlatformExternalIssue.objects.filter(
+            id=self.external_issue.id,
+        ).exists()
+
+    @with_feature('organizations:sentry-apps')
+    def test_handles_non_existing_external_issue(self):
+        url = u'/api/0/issues/{}/external-issues/{}/'.format(
+            self.group.id,
+            99999,
+        )
+
+        response = self.client.delete(url, format='json')
+
+        assert response.status_code == 404, response.content
+
+    @with_feature('organizations:sentry-apps')
+    def test_forbids_deleting_an_inaccessible_issue(self):
+        group = self.create_group(
+            project=self.create_project(
+                organization=self.create_organization(
+                    owner=self.create_user()  # Not the logged in User
+                )
+            )
+        )
+
+        external_issue = self.create_platform_external_issue(
+            group=group,
+            service_type='sentry-app',
+            display_name='App#issue-1',
+            web_url='https://example.com/app/issues/1',
+        )
+
+        url = u'/api/0/issues/{}/external-issues/{}/'.format(
+            group.id,
+            external_issue.id,
+        )
+
+        response = self.client.delete(url, format='json')
+
+        assert response.status_code == 403, response.content