Browse Source

ref(rules): Delete issue alert rules (#52403)

Currently we only ever "soft delete" rules and we have 12m that are
"pending deletion". The only way to truly delete a rule is to delete the
parent project or organization, but if you delete a single rule, today
we don't actually get rid of it. This PR adds in actually deleting
rules, and I'll follow this up with a migration to delete all the rules
"pending deletion".
Colleen O'Rourke 1 year ago
parent
commit
1b267fe478

+ 11 - 1
src/sentry/api/endpoints/project_rule_details.py

@@ -13,7 +13,15 @@ from sentry.api.serializers.models.rule import RuleSerializer
 from sentry.api.serializers.rest_framework.rule import RuleSerializer as DrfRuleSerializer
 from sentry.integrations.slack.utils import RedisRuleStatus
 from sentry.mediators import project_rules
-from sentry.models import RuleActivity, RuleActivityType, RuleStatus, SentryAppComponent, Team, User
+from sentry.models import (
+    RegionScheduledDeletion,
+    RuleActivity,
+    RuleActivityType,
+    RuleStatus,
+    SentryAppComponent,
+    Team,
+    User,
+)
 from sentry.models.integrations.sentry_app_installation import (
     SentryAppInstallation,
     prepare_ui_component,
@@ -199,11 +207,13 @@ class ProjectRuleDetailsEndpoint(RuleEndpoint):
         RuleActivity.objects.create(
             rule=rule, user_id=request.user.id, type=RuleActivityType.DELETED.value
         )
+        scheduled = RegionScheduledDeletion.schedule(rule, days=0, actor=request.user)
         self.create_audit_entry(
             request=request,
             organization=project.organization,
             target_object=rule.id,
             event=audit_log.get_event_id("RULE_REMOVE"),
             data=rule.get_audit_log_data(),
+            transaction_id=scheduled.id,
         )
         return Response(status=202)

+ 1 - 0
src/sentry/deletions/__init__.py

@@ -164,6 +164,7 @@ def load_defaults():
     default_manager.register(models.Team, defaults.TeamDeletionTask)
     default_manager.register(models.UserReport, BulkModelDeletionTask)
     default_manager.register(models.ArtifactBundle, ArtifactBundleDeletionTask)
+    default_manager.register(models.Rule, defaults.RuleDeletionTask)
 
 
 load_defaults()

+ 1 - 0
src/sentry/deletions/defaults/__init__.py

@@ -13,6 +13,7 @@ from .project import *  # noqa: F401,F403
 from .release import *  # noqa: F401,F403
 from .repository import *  # noqa: F401,F403
 from .repositoryprojectpathconfig import *  # noqa: F401,F403
+from .rule import *  # noqa: F401,F403
 from .sentry_app import *  # noqa: F401,F403
 from .sentry_app_installation import *  # noqa: F401,F403
 from .sentry_app_installation_token import *  # noqa: F401,F403

+ 20 - 0
src/sentry/deletions/defaults/rule.py

@@ -0,0 +1,20 @@
+from ..base import ModelDeletionTask, ModelRelation
+
+
+class RuleDeletionTask(ModelDeletionTask):
+    def get_child_relations(self, instance):
+        from sentry.models import GroupRuleStatus, RuleActivity
+        from sentry.models.rulefirehistory import RuleFireHistory
+
+        return [
+            ModelRelation(GroupRuleStatus, {"rule_id": instance.id}),
+            ModelRelation(RuleFireHistory, {"rule_id": instance.id}),
+            ModelRelation(RuleActivity, {"rule_id": instance.id}),
+        ]
+
+    def mark_deletion_in_progress(self, instance_list):
+        from sentry.models import RuleStatus
+
+        for instance in instance_list:
+            if instance.status != RuleStatus.PENDING_DELETION:
+                instance.update(status=RuleStatus.PENDING_DELETION)

+ 5 - 5
tests/sentry/api/endpoints/test_project_rule_details.py

@@ -758,11 +758,11 @@ class DeleteProjectRuleTest(ProjectRuleDetailsBaseTestCase):
     method = "DELETE"
 
     def test_simple(self):
+        rule = self.create_project_rule(self.project)
         self.get_success_response(
-            self.organization.slug, self.project.slug, self.rule.id, status_code=202
+            self.organization.slug, rule.project.slug, rule.id, status_code=202
         )
-        self.rule.refresh_from_db()
-        assert self.rule.status == RuleStatus.PENDING_DELETION
-        assert RuleActivity.objects.filter(
-            rule=self.rule, type=RuleActivityType.DELETED.value
+        rule.refresh_from_db()
+        assert not Rule.objects.filter(
+            id=self.rule.id, project=self.project, status=RuleStatus.PENDING_DELETION
         ).exists()

+ 40 - 0
tests/sentry/deletions/test_rule.py

@@ -0,0 +1,40 @@
+from sentry.models import (
+    GroupRuleStatus,
+    RegionScheduledDeletion,
+    Rule,
+    RuleActivity,
+    RuleActivityType,
+    RuleStatus,
+)
+from sentry.models.rulefirehistory import RuleFireHistory
+from sentry.tasks.deletion.scheduled import run_scheduled_deletions
+from sentry.testutils import TestCase
+from sentry.testutils.silo import region_silo_test
+
+
+@region_silo_test()
+class DeleteRuleTest(TestCase):
+    def test_simple(self):
+        project = self.create_project()
+        rule = self.create_project_rule(project)
+        rule_fire_history = RuleFireHistory.objects.create(
+            project=rule.project,
+            rule=rule,
+            group=self.group,
+        )
+        group_rule_status = GroupRuleStatus.objects.create(
+            rule=rule, group=self.group, project=rule.project
+        )
+        rule_activity = RuleActivity.objects.create(rule=rule, type=RuleActivityType.CREATED.value)
+
+        RegionScheduledDeletion.schedule(rule, days=0)
+
+        with self.tasks():
+            run_scheduled_deletions()
+
+        assert not Rule.objects.filter(
+            id=rule.id, project=project, status=RuleStatus.PENDING_DELETION
+        ).exists()
+        assert not GroupRuleStatus.objects.filter(id=group_rule_status.id).exists()
+        assert not RuleFireHistory.objects.filter(id=rule_fire_history.id).exists()
+        assert not RuleActivity.objects.filter(id=rule_activity.id).exists()