Repository: getsentry/sentry

Issue:
Handle RuleSnooze deletion
Delete a `RuleSnooze` the row if it is unmuted and there is no snooze duration, or if the snooze duration has expired. The latter will need to be run in a task. 

Depends on https://github.com/getsentry/sentry/issues/46828


Discussion:

Resolving patch:
diff --git a/src/sentry/conf/server.py b/src/sentry/conf/server.py
--- a/src/sentry/conf/server.py
+++ b/src/sentry/conf/server.py
@@ -602,6 +602,7 @@ def SOCIAL_AUTH_DEFAULT_USERNAME():
     "sentry.tasks.beacon",
     "sentry.tasks.check_auth",
     "sentry.tasks.clear_expired_snoozes",
+    "sentry.tasks.clear_expired_rulesnoozes",
     "sentry.tasks.codeowners.code_owners_auto_sync",
     "sentry.tasks.codeowners.update_code_owners_schema",
     "sentry.tasks.collect_project_platforms",
@@ -801,6 +802,11 @@ def SOCIAL_AUTH_DEFAULT_USERNAME():
         "schedule": timedelta(minutes=5),
         "options": {"expires": 300},
     },
+    "clear-expired-rulesnoozes": {
+        "task": "sentry.tasks.clear_expired_rulesnoozes",
+        "schedule": timedelta(minutes=5),
+        "options": {"expires": 300},
+    },
     "clear-expired-raw-events": {
         "task": "sentry.tasks.clear_expired_raw_events",
         "schedule": timedelta(minutes=15),
diff --git a/src/sentry/tasks/clear_expired_rulesnoozes.py b/src/sentry/tasks/clear_expired_rulesnoozes.py
new file mode 100644
--- /dev/null
+++ b/src/sentry/tasks/clear_expired_rulesnoozes.py
@@ -0,0 +1,10 @@
+from django.utils import timezone
+
+from sentry.models import RuleSnooze
+from sentry.tasks.base import instrumented_task
+
+
+@instrumented_task(name="sentry.tasks.clear_expired_rulesnoozes", time_limit=65, soft_time_limit=60)
+def clear_expired_rulesnoozes():
+    rule_snooze_ids = RuleSnooze.objects.filter(until__lte=timezone.now()).values_list("id")[:1000]
+    RuleSnooze.objects.filter(id__in=rule_snooze_ids).delete()


Test patch:
diff --git a/tests/sentry/tasks/test_clear_expired_rulesnoozes.py b/tests/sentry/tasks/test_clear_expired_rulesnoozes.py
new file mode 100644
--- /dev/null
+++ b/tests/sentry/tasks/test_clear_expired_rulesnoozes.py
@@ -0,0 +1,78 @@
+from datetime import datetime, timedelta
+
+import pytz
+
+from sentry.models import Rule, RuleSnooze
+from sentry.tasks.clear_expired_rulesnoozes import clear_expired_rulesnoozes
+from sentry.testutils import APITestCase
+
+
+class ClearExpiredRuleSnoozesTest(APITestCase):
+    def setUp(self):
+        self.issue_alert_rule = Rule.objects.create(
+            label="test rule", project=self.project, owner=self.team.actor
+        )
+        self.metric_alert_rule = self.create_alert_rule(
+            organization=self.project.organization, projects=[self.project]
+        )
+        self.until = datetime.now(pytz.UTC) - timedelta(minutes=1)
+        self.login_as(user=self.user)
+
+    def test_task_persistent_name(self):
+        assert clear_expired_rulesnoozes.name == "sentry.tasks.clear_expired_rulesnoozes"
+
+    def test_simple(self):
+        """Test that expired rulesnoozes are deleted, and ones that still have time left are left alone"""
+        issue_alert_rule_snooze = RuleSnooze.objects.create(
+            user_id=self.user.id,
+            rule=self.issue_alert_rule,
+            owner_id=self.user.id,
+            until=self.until,
+            date_added=datetime.now(pytz.UTC),
+        )
+        issue_alert_rule_snooze2 = RuleSnooze.objects.create(
+            rule=self.issue_alert_rule,
+            owner_id=self.user.id,
+            until=datetime.now(pytz.UTC) + timedelta(minutes=1),
+            date_added=datetime.now(pytz.UTC),
+        )
+        metric_alert_rule_snooze = RuleSnooze.objects.create(
+            user_id=self.user.id,
+            alert_rule=self.metric_alert_rule,
+            owner_id=self.user.id,
+            until=self.until,
+            date_added=datetime.now(pytz.UTC),
+        )
+        metric_alert_rule_snooze2 = RuleSnooze.objects.create(
+            alert_rule=self.metric_alert_rule,
+            owner_id=self.user.id,
+            until=datetime.now(pytz.UTC) + timedelta(minutes=1),
+            date_added=datetime.now(pytz.UTC),
+        )
+
+        clear_expired_rulesnoozes()
+
+        assert not RuleSnooze.objects.filter(id=issue_alert_rule_snooze.id).exists()
+        assert RuleSnooze.objects.filter(id=issue_alert_rule_snooze2.id).exists()
+        assert not RuleSnooze.objects.filter(id=metric_alert_rule_snooze.id).exists()
+        assert RuleSnooze.objects.filter(id=metric_alert_rule_snooze2.id).exists()
+
+    def test_snooze_forever(self):
+        """Test that if an issue alert rule is snoozed forever, the task doesn't remove it."""
+        issue_alert_rule_snooze = RuleSnooze.objects.create(
+            user_id=self.user.id,
+            rule=self.issue_alert_rule,
+            owner_id=self.user.id,
+            date_added=datetime.now(pytz.UTC),
+        )
+        metric_alert_rule_snooze = RuleSnooze.objects.create(
+            user_id=self.user.id,
+            alert_rule=self.metric_alert_rule,
+            owner_id=self.user.id,
+            date_added=datetime.now(pytz.UTC),
+        )
+
+        clear_expired_rulesnoozes()
+
+        assert RuleSnooze.objects.filter(id=issue_alert_rule_snooze.id).exists()
+        assert RuleSnooze.objects.filter(id=metric_alert_rule_snooze.id).exists()