Browse Source

feat(rulesnooze): Add delete endpoint (#47285)

A follow up to https://github.com/getsentry/sentry/pull/47050 and
https://github.com/getsentry/sentry/pull/47123 - this adds a delete
endpoint for `RuleSnooze` so that in the front end someone can click
"unmute" and receive notifications for the muted rule again.
Colleen O'Rourke 1 year ago
parent
commit
a47d84b1e7

+ 71 - 33
src/sentry/api/endpoints/rule_snooze.py

@@ -1,6 +1,7 @@
 import datetime
 
 from rest_framework import serializers, status
+from rest_framework.exceptions import AuthenticationFailed
 from rest_framework.request import Request
 from rest_framework.response import Response
 
@@ -15,8 +16,6 @@ from sentry.models import Organization, Rule, RuleSnooze, Team
 
 class RuleSnoozeValidator(CamelSnakeSerializer):
     target = serializers.CharField(required=True, allow_null=False)
-    rule = serializers.BooleanField(required=False, allow_null=True)
-    alert_rule = serializers.BooleanField(required=False, allow_null=True)
     until = serializers.DateTimeField(required=False, allow_null=True)
 
 
@@ -50,14 +49,22 @@ def can_edit_alert_rule(rule, organization, user_id, user):
 
 
 @region_silo_endpoint
-class RuleSnoozeEndpoint(ProjectEndpoint):
+class BaseRuleSnoozeEndpoint(ProjectEndpoint):
     permission_classes = (ProjectAlertRulePermission,)
 
+    def get_rule(self, rule_id):
+        try:
+            rule = self.rule_model.objects.get(id=rule_id)
+        except self.rule_model.DoesNotExist:
+            raise serializers.ValidationError("Rule does not exist")
+
+        return rule
+
     def post(self, request: Request, project, rule_id) -> Response:
-        if not features.has("organizations:mute-alerts", project.organization, actor=None):
-            return Response(
-                {"detail": "This feature is not available for this organization."},
-                status=status.HTTP_401_UNAUTHORIZED,
+        if not features.has("organizations:mute-alerts", project.organization, actor=request.user):
+            raise AuthenticationFailed(
+                detail="This feature is not available for this organization.",
+                code=status.HTTP_401_UNAUTHORIZED,
             )
 
         serializer = RuleSnoozeValidator(data=request.data)
@@ -65,43 +72,24 @@ class RuleSnoozeEndpoint(ProjectEndpoint):
             return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
 
         data = serializer.validated_data
-
-        if data.get("rule") and data.get("alert_rule"):
-            raise serializers.ValidationError("Pass either rule or alert rule, not both.")
-
-        issue_alert_rule = None
-        metric_alert_rule = None
+        rule = self.get_rule(rule_id)
 
         user_id = request.user.id if data.get("target") == "me" else None
-
-        if data.get("rule"):
-            try:
-                issue_alert_rule = Rule.objects.get(id=rule_id)
-            except Rule.DoesNotExist:
-                raise serializers.ValidationError("Rule does not exist")
-
-        if data.get("alert_rule"):
-            try:
-                metric_alert_rule = AlertRule.objects.get(id=rule_id)
-            except AlertRule.DoesNotExist:
-                raise serializers.ValidationError("Rule does not exist")
-
-        rule = issue_alert_rule or metric_alert_rule
         if not can_edit_alert_rule(rule, project.organization, user_id, request.user):
-            return Response(
-                {"detail": "Requesting user cannot mute this rule."},
-                status=status.HTTP_401_UNAUTHORIZED,
+            raise AuthenticationFailed(
+                detail="Requesting user cannot mute this rule.", code=status.HTTP_401_UNAUTHORIZED
             )
 
+        kwargs = {self.rule_field: rule}
+
         rule_snooze, created = RuleSnooze.objects.get_or_create(
             user_id=user_id,
-            rule=issue_alert_rule,
-            alert_rule=metric_alert_rule,
             defaults={
                 "owner_id": request.user.id,
                 "until": data.get("until"),
                 "date_added": datetime.datetime.now(),
             },
+            **kwargs,
         )
         # don't allow editing of a rulesnooze object for a given rule and user (or no user)
         if not created:
@@ -112,7 +100,7 @@ class RuleSnoozeEndpoint(ProjectEndpoint):
 
         if not user_id:
             # create an audit log entry if the rule is snoozed for everyone
-            audit_log_event = "RULE_SNOOZE" if issue_alert_rule else "ALERT_RULE_SNOOZE"
+            audit_log_event = "RULE_SNOOZE" if self.rule_model == Rule else "ALERT_RULE_SNOOZE"
 
             self.create_audit_entry(
                 request=request,
@@ -126,3 +114,53 @@ class RuleSnoozeEndpoint(ProjectEndpoint):
             serialize(rule_snooze, request.user, RuleSnoozeSerializer()),
             status=status.HTTP_201_CREATED,
         )
+
+    def delete(self, request: Request, project, rule_id) -> Response:
+        if not features.has("organizations:mute-alerts", project.organization, actor=request.user):
+            raise AuthenticationFailed(
+                detail="This feature is not available for this organization.",
+                code=status.HTTP_401_UNAUTHORIZED,
+            )
+
+        serializer = RuleSnoozeValidator(data=request.data)
+        if not serializer.is_valid():
+            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+        data = serializer.validated_data
+        rule = self.get_rule(rule_id)
+        user_id = request.user.id if data.get("target") == "me" else None
+
+        if not can_edit_alert_rule(rule, project.organization, user_id, request.user):
+            raise AuthenticationFailed(
+                detail="Requesting user cannot mute this rule.", code=status.HTTP_401_UNAUTHORIZED
+            )
+
+        kwargs = {self.rule_field: rule}
+
+        try:
+            rulesnooze = RuleSnooze.objects.get(
+                user_id=user_id,
+                owner_id=request.user.id,
+                until=data.get("until"),
+                **kwargs,
+            )
+        except RuleSnooze.DoesNotExist:
+            return Response(
+                {"detail": "This rulesnooze object doesn't exist."},
+                status=status.HTTP_404_NOT_FOUND,
+            )
+
+        rulesnooze.delete()
+        return Response(status=status.HTTP_204_NO_CONTENT)
+
+
+@region_silo_endpoint
+class RuleSnoozeEndpoint(BaseRuleSnoozeEndpoint):
+    rule_model = Rule
+    rule_field = "rule"
+
+
+@region_silo_endpoint
+class MetricRuleSnoozeEndpoint(BaseRuleSnoozeEndpoint):
+    rule_model = AlertRule
+    rule_field = "alert_rule"

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

@@ -475,7 +475,7 @@ from .endpoints.relay import (
     RelayRegisterResponseEndpoint,
 )
 from .endpoints.release_deploys import ReleaseDeploysEndpoint
-from .endpoints.rule_snooze import RuleSnoozeEndpoint
+from .endpoints.rule_snooze import MetricRuleSnoozeEndpoint, RuleSnoozeEndpoint
 from .endpoints.setup_wizard import SetupWizard
 from .endpoints.shared_group_details import SharedGroupDetailsEndpoint
 from .endpoints.source_map_debug import SourceMapDebugEndpoint
@@ -2070,6 +2070,11 @@ PROJECT_URLS = [
         RuleSnoozeEndpoint.as_view(),
         name="sentry-api-0-rule-snooze",
     ),
+    url(
+        r"^(?P<organization_slug>[^\/]+)/(?P<project_slug>[^\/]+)/alert-rules/(?P<rule_id>[^\/]+)/snooze/$",
+        MetricRuleSnoozeEndpoint.as_view(),
+        name="sentry-api-0-metric-rule-snooze",
+    ),
     url(
         r"^(?P<organization_slug>[^\/]+)/(?P<project_slug>[^\/]+)/rules/preview$",
         ProjectRulePreviewEndpoint.as_view(),

+ 359 - 167
tests/sentry/api/endpoints/test_rule_snooze.py

@@ -6,14 +6,12 @@ from sentry import audit_log
 from sentry.models import AuditLogEntry, Rule, RuleSnooze
 from sentry.models.actor import ActorTuple
 from sentry.testutils import APITestCase
+from sentry.testutils.helpers import with_feature
 from sentry.testutils.silo import region_silo_test
 
 
 @region_silo_test
-class RuleSnoozeTest(APITestCase):
-    endpoint = "sentry-api-0-rule-snooze"
-    method = "post"
-
+class BaseRuleSnoozeTest(APITestCase):
     def setUp(self):
         self.issue_alert_rule = Rule.objects.create(
             label="test rule", project=self.project, owner=self.team.actor
@@ -24,13 +22,22 @@ class RuleSnoozeTest(APITestCase):
         self.until = datetime.now(pytz.UTC) + timedelta(days=10)
         self.login_as(user=self.user)
 
+
+@region_silo_test
+class PostRuleSnoozeTest(BaseRuleSnoozeTest):
+    endpoint = "sentry-api-0-rule-snooze"
+    method = "post"
+
+    @with_feature("organizations:mute-alerts")
     def test_mute_issue_alert_user_forever(self):
         """Test that a user can mute an issue alert rule for themselves forever"""
-        data = {"target": "me", "rule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.issue_alert_rule.id, **data
-            )
+        data = {"target": "me"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.issue_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(rule=self.issue_alert_rule.id).exists()
         assert response.status_code == 201
         assert len(response.data) == 6
@@ -40,13 +47,16 @@ class RuleSnoozeTest(APITestCase):
         assert response.data["alertRuleId"] is None
         assert response.data["until"] == "forever"
 
+    @with_feature("organizations:mute-alerts")
     def test_mute_issue_alert_user_until(self):
         """Test that a user can mute an issue alert rule for themselves a period of time"""
-        data = {"target": "me", "rule": True, "until": self.until}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.issue_alert_rule.id, **data
-            )
+        data = {"target": "me", "until": self.until}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.issue_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(rule=self.issue_alert_rule.id).exists()
         assert response.status_code == 201
         assert len(response.data) == 6
@@ -56,13 +66,16 @@ class RuleSnoozeTest(APITestCase):
         assert response.data["alertRuleId"] is None
         assert response.data["until"] == self.until
 
+    @with_feature("organizations:mute-alerts")
     def test_mute_issue_alert_everyone_forever(self):
         """Test that an issue alert rule can be muted for everyone forever"""
-        data = {"target": "everyone", "rule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.issue_alert_rule.id, **data
-            )
+        data = {"target": "everyone"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.issue_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(rule=self.issue_alert_rule.id).exists()
         assert response.status_code == 201
         assert len(response.data) == 6
@@ -78,13 +91,16 @@ class RuleSnoozeTest(APITestCase):
             target_object=self.issue_alert_rule.id,
         )
 
+    @with_feature("organizations:mute-alerts")
     def test_mute_issue_alert_everyone_until(self):
         """Test that an issue alert rule can be muted for everyone for a period of time"""
-        data = {"target": "everyone", "rule": True, "until": self.until}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.issue_alert_rule.id, **data
-            )
+        data = {"target": "everyone", "until": self.until}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.issue_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(rule=self.issue_alert_rule.id).exists()
         assert response.status_code == 201
         assert len(response.data) == 6
@@ -100,120 +116,234 @@ class RuleSnoozeTest(APITestCase):
             target_object=self.issue_alert_rule.id,
         )
 
+    @with_feature("organizations:mute-alerts")
     def test_mute_issue_alert_user_then_everyone(self):
         """Test that a user can mute an issue alert for themselves and then the same alert can be muted for everyone"""
-        data = {"target": "me", "rule": True, "until": self.until}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.issue_alert_rule.id, **data
-            )
+        data = {"target": "me", "until": self.until}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.issue_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(
             rule=self.issue_alert_rule.id, user_id=self.user.id, until=self.until
         ).exists()
         assert response.status_code == 201
 
         everyone_until = datetime.now(pytz.UTC) + timedelta(days=1)
-        data = {"target": "everyone", "rule": True, "until": everyone_until}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.issue_alert_rule.id, **data
-            )
+        data = {"target": "everyone", "until": everyone_until}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.issue_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(
             rule=self.issue_alert_rule.id, user_id=None, until=everyone_until
         ).exists()
         assert response.status_code == 201
 
+    @with_feature("organizations:mute-alerts")
     def test_mute_issue_alert_everyone_then_user(self):
         """Test that an issue alert can be muted for everyone and then a user can mute the same alert for themselves"""
         everyone_until = datetime.now(pytz.UTC) + timedelta(days=1)
-        data = {"target": "everyone", "rule": True, "until": everyone_until}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.issue_alert_rule.id, **data
-            )
+        data = {"target": "everyone", "until": everyone_until}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.issue_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(
             rule=self.issue_alert_rule.id, user_id=None, until=everyone_until
         ).exists()
         assert response.status_code == 201
 
-        data = {"target": "me", "rule": True, "until": self.until}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.issue_alert_rule.id, **data
-            )
+        data = {"target": "me", "until": self.until}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.issue_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(
             rule=self.issue_alert_rule.id, user_id=self.user.id, until=self.until
         ).exists()
         assert response.status_code == 201
 
+    @with_feature("organizations:mute-alerts")
     def test_edit_issue_alert_mute(self):
         """Test that we throw an error if an issue alert rule has already been muted by a user"""
-        data = {"target": "me", "rule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.issue_alert_rule.id, **data
-            )
+        data = {"target": "me"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.issue_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(rule=self.issue_alert_rule.id).exists()
         assert response.status_code == 201
 
-        data = {"target": "me", "rule": True, "until": self.until}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.issue_alert_rule.id, **data
-            )
+        data = {"target": "me", "until": self.until}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.issue_alert_rule.id,
+            **data,
+        )
         assert len(RuleSnooze.objects.all()) == 1
         assert response.status_code == 410
         assert "RuleSnooze already exists for this rule and scope." in response.data["detail"]
 
+    @with_feature("organizations:mute-alerts")
     def test_user_cant_mute_issue_alert_for_everyone(self):
         """Test that if a user doesn't belong to the team that can edit an issue alert rule, we throw an error when they try to mute it for everyone."""
         other_team = self.create_team()
         other_issue_alert_rule = Rule.objects.create(
             label="test rule", project=self.project, owner=other_team.actor
         )
-        data = {"target": "everyone", "rule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, other_issue_alert_rule.id, **data
-            )
+        data = {"target": "everyone"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            other_issue_alert_rule.id,
+            **data,
+        )
         assert not RuleSnooze.objects.filter(rule=other_issue_alert_rule.id).exists()
         assert response.status_code == 401
         assert "Requesting user cannot mute this rule" in response.data["detail"]
 
+    @with_feature("organizations:mute-alerts")
     def test_user_can_mute_issue_alert_for_self(self):
         """Test that if a user doesn't belong to the team that can edit an issue alert rule, they can still mute it for just themselves."""
         other_team = self.create_team()
         other_issue_alert_rule = Rule.objects.create(
             label="test rule", project=self.project, owner=other_team.actor
         )
-        data = {"target": "me", "rule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, other_issue_alert_rule.id, **data
-            )
+        data = {"target": "me"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            other_issue_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(rule=other_issue_alert_rule.id).exists()
         assert response.status_code == 201
 
+    @with_feature("organizations:mute-alerts")
     def test_user_can_mute_unassigned_issue_alert(self):
         """Test that if an issue alert rule's owner is unassigned, the user can mute it."""
         other_issue_alert_rule = Rule.objects.create(
             label="test rule", project=self.project, owner=None
         )
-        data = {"target": "me", "rule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, other_issue_alert_rule.id, **data
-            )
+        data = {"target": "me"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            other_issue_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(rule=other_issue_alert_rule.id).exists()
         assert response.status_code == 201
 
+    @with_feature("organizations:mute-alerts")
+    def test_no_issue_alert(self):
+        """Test that we throw an error when an issue alert rule doesn't exist"""
+        data = {"target": "me"}
+        response = self.get_response(self.organization.slug, self.project.slug, 777, **data)
+        assert not RuleSnooze.objects.filter(alert_rule=self.issue_alert_rule.id).exists()
+        assert response.status_code == 400
+        assert "Rule does not exist" in response.data
+
+    @with_feature("organizations:mute-alerts")
+    def test_invalid_data_issue_alert(self):
+        """Test that we throw an error when passed invalid data"""
+        data = {"target": "me", "until": 123}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.metric_alert_rule.id,
+            **data,
+        )
+        assert not RuleSnooze.objects.filter(alert_rule=self.metric_alert_rule.id).exists()
+        assert response.status_code == 400
+        assert "Datetime has wrong format." in response.data["until"][0]
+
+
+@region_silo_test
+class DeleteRuleSnoozeTest(BaseRuleSnoozeTest):
+    endpoint = "sentry-api-0-rule-snooze"
+    method = "delete"
+
+    @with_feature("organizations:mute-alerts")
+    def test_delete_issue_alert_rule_mute_myself(self):
+        """Test that a user can unsnooze a rule they've snoozed for just themselves"""
+        RuleSnooze.objects.create(
+            user_id=self.user.id, rule=self.issue_alert_rule, owner_id=self.user.id
+        )
+        data = {"target": "me"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.issue_alert_rule.id,
+            **data,
+        )
+        assert not RuleSnooze.objects.filter(
+            rule=self.issue_alert_rule.id, user_id=self.user.id
+        ).exists()
+        assert response.status_code == 204
+
+    @with_feature("organizations:mute-alerts")
+    def test_delete_issue_alert_rule_mute_everyone(self):
+        """Test that a user can unsnooze a rule they've snoozed for everyone"""
+        RuleSnooze.objects.create(rule=self.issue_alert_rule, owner_id=self.user.id)
+        data = {"target": "everyone"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.issue_alert_rule.id,
+            **data,
+        )
+        assert not RuleSnooze.objects.filter(
+            rule=self.issue_alert_rule.id, user_id=self.user.id
+        ).exists()
+        assert response.status_code == 204
+
+    @with_feature("organizations:mute-alerts")
+    def test_cant_delete_issue_alert_rule_mute_everyone(self):
+        """Test that a user can't unsnooze a rule that's snoozed for everyone if they do not belong to the team the owns the rule"""
+        other_team = self.create_team()
+        other_issue_alert_rule = Rule.objects.create(
+            label="test rule", project=self.project, owner=other_team.actor
+        )
+        RuleSnooze.objects.create(rule=other_issue_alert_rule)
+        data = {"target": "everyone"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            other_issue_alert_rule.id,
+            **data,
+        )
+        assert RuleSnooze.objects.filter(rule=other_issue_alert_rule.id).exists()
+        assert response.status_code == 401
+
+
+@region_silo_test
+class PostMetricRuleSnoozeTest(BaseRuleSnoozeTest):
+    endpoint = "sentry-api-0-metric-rule-snooze"
+    method = "post"
+
+    @with_feature("organizations:mute-alerts")
     def test_mute_metric_alert_user_forever(self):
         """Test that a user can mute a metric alert rule for themselves forever"""
-        data = {"target": "me", "alert_rule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.metric_alert_rule.id, **data
-            )
+        data = {"target": "me"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.metric_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(alert_rule=self.metric_alert_rule.id).exists()
         assert response.status_code == 201
         assert len(response.data) == 6
@@ -223,13 +353,16 @@ class RuleSnoozeTest(APITestCase):
         assert response.data["alertRuleId"] == self.metric_alert_rule.id
         assert response.data["until"] == "forever"
 
+    @with_feature("organizations:mute-alerts")
     def test_mute_metric_alert_user_until(self):
         """Test that a user can mute a metric alert rule for themselves a period of time"""
-        data = {"target": "me", "alert_rule": True, "until": self.until}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.metric_alert_rule.id, **data
-            )
+        data = {"target": "me", "until": self.until}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.metric_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(alert_rule=self.metric_alert_rule.id).exists()
         assert response.status_code == 201
         assert len(response.data) == 6
@@ -239,13 +372,16 @@ class RuleSnoozeTest(APITestCase):
         assert response.data["alertRuleId"] == self.metric_alert_rule.id
         assert response.data["until"] == self.until
 
+    @with_feature("organizations:mute-alerts")
     def test_mute_metric_alert_everyone_forever(self):
         """Test that a metric alert rule can be muted for everyone forever"""
-        data = {"target": "everyone", "alert_rule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.metric_alert_rule.id, **data
-            )
+        data = {"target": "everyone"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.metric_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(alert_rule=self.metric_alert_rule.id).exists()
         assert response.status_code == 201
         assert len(response.data) == 6
@@ -261,13 +397,16 @@ class RuleSnoozeTest(APITestCase):
             target_object=self.metric_alert_rule.id,
         )
 
+    @with_feature("organizations:mute-alerts")
     def test_mute_metric_alert_everyone_until(self):
         """Test that a metric alert rule can be muted for everyone for a period of time"""
-        data = {"target": "everyone", "alert_rule": True, "until": self.until}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.metric_alert_rule.id, **data
-            )
+        data = {"target": "everyone", "until": self.until}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.metric_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(alert_rule=self.metric_alert_rule.id).exists()
         assert response.status_code == 201
         assert len(response.data) == 6
@@ -283,71 +422,87 @@ class RuleSnoozeTest(APITestCase):
             target_object=self.metric_alert_rule.id,
         )
 
+    @with_feature("organizations:mute-alerts")
     def test_mute_metric_alert_user_then_everyone(self):
         """Test that a user can mute a metric alert for themselves and then the same alert can be muted for everyone"""
-        data = {"target": "me", "alert_rule": True, "until": self.until}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.metric_alert_rule.id, **data
-            )
+        data = {"target": "me", "until": self.until}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.metric_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(
             alert_rule=self.metric_alert_rule.id, user_id=self.user.id, until=self.until
         ).exists()
         assert response.status_code == 201
 
         everyone_until = datetime.now(pytz.UTC) + timedelta(days=1)
-        data = {"target": "everyone", "alert_rule": True, "until": everyone_until}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.metric_alert_rule.id, **data
-            )
+        data = {"target": "everyone", "until": everyone_until}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.metric_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(
             alert_rule=self.metric_alert_rule.id, user_id=None, until=everyone_until
         ).exists()
         assert response.status_code == 201
 
+    @with_feature("organizations:mute-alerts")
     def test_mute_metric_alert_everyone_then_user(self):
         """Test that a metric alert can be muted for everyone and then a user can mute the same alert for themselves"""
         everyone_until = datetime.now(pytz.UTC) + timedelta(days=1)
-        data = {"target": "everyone", "alert_rule": True, "until": everyone_until}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.metric_alert_rule.id, **data
-            )
+        data = {"target": "everyone", "until": everyone_until}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.metric_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(
             alert_rule=self.metric_alert_rule.id, user_id=None, until=everyone_until
         ).exists()
         assert response.status_code == 201
 
-        data = {"target": "me", "alert_rule": True, "until": self.until}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.metric_alert_rule.id, **data
-            )
+        data = {"target": "me", "until": self.until}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.metric_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(
             alert_rule=self.metric_alert_rule.id, user_id=self.user.id, until=self.until
         ).exists()
         assert response.status_code == 201
 
+    @with_feature("organizations:mute-alerts")
     def test_edit_metric_alert_mute(self):
         """Test that we throw an error if a metric alert rule has already been muted by a user"""
-        data = {"target": "me", "alert_rule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.metric_alert_rule.id, **data
-            )
+        data = {"target": "me"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.metric_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(alert_rule=self.metric_alert_rule.id).exists()
         assert response.status_code == 201
 
-        data = {"target": "me", "alert_rule": True, "until": self.until}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.metric_alert_rule.id, **data
-            )
+        data = {"target": "me", "until": self.until}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.metric_alert_rule.id,
+            **data,
+        )
         assert len(RuleSnooze.objects.all()) == 1
         assert response.status_code == 410
         assert "RuleSnooze already exists for this rule and scope." in response.data["detail"]
 
+    @with_feature("organizations:mute-alerts")
     def test_user_cant_snooze_metric_alert_for_everyone(self):
         """Test that if a user doesn't belong to the team that can edit a metric alert rule, we throw an error when they try to mute it for everyone"""
         other_team = self.create_team()
@@ -356,15 +511,18 @@ class RuleSnoozeTest(APITestCase):
             projects=[self.project],
             owner=ActorTuple.from_actor_identifier(f"team:{other_team.id}"),
         )
-        data = {"target": "everyone", "alertRule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, other_metric_alert_rule.id, **data
-            )
+        data = {"target": "everyone"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            other_metric_alert_rule.id,
+            **data,
+        )
         assert not RuleSnooze.objects.filter(alert_rule=other_metric_alert_rule).exists()
         assert response.status_code == 401
         assert "Requesting user cannot mute this rule" in response.data["detail"]
 
+    @with_feature("organizations:mute-alerts")
     def test_user_can_snooze_metric_alert_for_self(self):
         """Test that if a user doesn't belong to the team that can edit a metric alert rule, they are able to mute it for just themselves."""
         other_team = self.create_team()
@@ -373,63 +531,97 @@ class RuleSnoozeTest(APITestCase):
             projects=[self.project],
             owner=ActorTuple.from_actor_identifier(f"team:{other_team.id}"),
         )
-        data = {"target": "me", "alertRule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, other_metric_alert_rule.id, **data
-            )
+        data = {"target": "me"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            other_metric_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(alert_rule=other_metric_alert_rule).exists()
         assert response.status_code == 201
 
+    @with_feature("organizations:mute-alerts")
     def test_user_can_mute_unassigned_metric_alert(self):
         """Test that if a metric alert rule's owner is unassigned, the user can mute it."""
         other_metric_alert_rule = self.create_alert_rule(
             organization=self.project.organization, projects=[self.project], owner=None
         )
-        data = {"target": "me", "alertRule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, other_metric_alert_rule.id, **data
-            )
+        data = {"target": "me"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            other_metric_alert_rule.id,
+            **data,
+        )
         assert RuleSnooze.objects.filter(alert_rule=other_metric_alert_rule.id).exists()
         assert response.status_code == 201
 
-    def test_no_issue_alert(self):
-        """Test that we throw an error when an issue alert rule doesn't exist"""
-        data = {"target": "me", "rule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(self.organization.slug, self.project.slug, 777, **data)
-        assert not RuleSnooze.objects.filter(alert_rule=self.issue_alert_rule.id).exists()
-        assert response.status_code == 400
-        assert "Rule does not exist" in response.data
-
+    @with_feature("organizations:mute-alerts")
     def test_no_metric_alert(self):
         """Test that we throw an error when a metric alert rule doesn't exist"""
-        data = {"target": "me", "alert_rule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(self.organization.slug, self.project.slug, 777, **data)
+        data = {"target": "me"}
+        response = self.get_response(self.organization.slug, self.project.slug, 777, **data)
         assert not RuleSnooze.objects.filter(alert_rule=self.metric_alert_rule.id).exists()
         assert response.status_code == 400
         assert "Rule does not exist" in response.data
 
-    def test_invalid_data_issue_alert(self):
-        """Test that we throw an error when passed invalid data"""
-        data = {"target": "me", "rule": self.issue_alert_rule.id}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.metric_alert_rule.id, **data
-            )
-        assert not RuleSnooze.objects.filter(alert_rule=self.metric_alert_rule.id).exists()
-        assert response.status_code == 400
-        assert "Must be a valid boolean" in response.data["rule"][0]
-
-    def test_rule_and_alert_rule(self):
-        """Test that we throw an error if both rule and alert rule are passed"""
-        data = {"target": "me", "rule": True, "alert_rule": True}
-        with self.feature({"organizations:mute-alerts": True}):
-            response = self.get_response(
-                self.organization.slug, self.project.slug, self.metric_alert_rule.id, **data
-            )
-        assert not RuleSnooze.objects.filter(alert_rule=self.metric_alert_rule.id).exists()
-        assert response.status_code == 400
-        assert "Pass either rule or alert rule, not both." in response.data
+
+@region_silo_test
+class DeleteMetricRuleSnoozeTest(BaseRuleSnoozeTest):
+    endpoint = "sentry-api-0-metric-rule-snooze"
+    method = "delete"
+
+    @with_feature("organizations:mute-alerts")
+    def test_delete_metric_alert_rule_mute_myself(self):
+        """Test that a user can unsnooze a metric alert rule they've snoozed for just themselves"""
+        RuleSnooze.objects.create(
+            user_id=self.user.id, alert_rule=self.metric_alert_rule, owner_id=self.user.id
+        )
+        data = {"target": "me"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.metric_alert_rule.id,
+            **data,
+        )
+        assert not RuleSnooze.objects.filter(
+            alert_rule=self.metric_alert_rule.id, user_id=self.user.id
+        ).exists()
+        assert response.status_code == 204
+
+    @with_feature("organizations:mute-alerts")
+    def test_delete_metric_alert_rule_mute_everyone(self):
+        """Test that a user can unsnooze a metric rule they've snoozed for everyone"""
+        RuleSnooze.objects.create(alert_rule=self.metric_alert_rule, owner_id=self.user.id)
+        data = {"target": "everyone"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            self.metric_alert_rule.id,
+            **data,
+        )
+        assert not RuleSnooze.objects.filter(
+            alert_rule=self.metric_alert_rule.id, user_id=self.user.id
+        ).exists()
+        assert response.status_code == 204
+
+    @with_feature("organizations:mute-alerts")
+    def test_cant_delete_metric_alert_rule_mute_everyone(self):
+        """Test that a user can't unsnooze a metric rule that's snoozed for everyone if they do not belong to the team the owns the rule"""
+        other_team = self.create_team()
+        other_metric_alert_rule = self.create_alert_rule(
+            organization=self.project.organization,
+            projects=[self.project],
+            owner=ActorTuple.from_actor_identifier(f"team:{other_team.id}"),
+        )
+        RuleSnooze.objects.create(alert_rule=other_metric_alert_rule)
+        data = {"target": "everyone"}
+        response = self.get_response(
+            self.organization.slug,
+            self.project.slug,
+            other_metric_alert_rule.id,
+            **data,
+        )
+        assert RuleSnooze.objects.filter(alert_rule=other_metric_alert_rule.id).exists()
+        assert response.status_code == 401