Repository: getsentry/sentry

Issue:
Pre-save Rule to prevent name data from being saved in conditions and actions
Despite the work done in https://github.com/getsentry/sentry/pull/54739 and https://github.com/getsentry/sentry/pull/54746 we still have an edge case where we're saving name data for `sentry.rules.filters.tagged_event.TaggedEventFilter` and `sentry.rules.filters.level.LevelFilter` possibly when duplicating older alerts. Adding a `pre_save` should prevent this so we can move forward with the data migration.

https://redash.getsentry.net/queries/4772/source


Discussion:

Resolving patch:
diff --git a/src/sentry/api/endpoints/project_rules.py b/src/sentry/api/endpoints/project_rules.py
--- a/src/sentry/api/endpoints/project_rules.py
+++ b/src/sentry/api/endpoints/project_rules.py
@@ -1,4 +1,6 @@
 from django.conf import settings
+from django.db.models.signals import pre_save
+from django.dispatch import receiver
 from rest_framework import status
 from rest_framework.request import Request
 from rest_framework.response import Response
@@ -20,6 +22,18 @@
 from sentry.web.decorators import transaction_start
 
 
+def clean_rule_data(data):
+    for datum in data:
+        if datum.get("name"):
+            del datum["name"]
+
+
+@receiver(pre_save, sender=Rule)
+def pre_save_rule(instance, sender, *args, **kwargs):
+    clean_rule_data(instance.data.get("conditions", []))
+    clean_rule_data(instance.data.get("actions", []))
+
+
 @region_silo_endpoint
 class ProjectRulesEndpoint(ProjectEndpoint):
     owner = ApiOwner.ISSUES


Test patch:
diff --git a/tests/sentry/api/endpoints/test_project_rules.py b/tests/sentry/api/endpoints/test_project_rules.py
--- a/tests/sentry/api/endpoints/test_project_rules.py
+++ b/tests/sentry/api/endpoints/test_project_rules.py
@@ -159,6 +159,40 @@ def test_with_name(self):
 
         self.run_test(actions=actions, conditions=conditions)
 
+    def test_pre_save(self):
+        """Test that a rule with name data in the conditions and actions is saved without it"""
+        conditions = [
+            {
+                "id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition",
+                "name": "A new issue is created",
+            }
+        ]
+        actions = [
+            {
+                "id": "sentry.rules.actions.notify_event.NotifyEventAction",
+                "name": "Send a notification to IssueOwners and if none can be found then send a notification to ActiveMembers",
+            }
+        ]
+        response = self.get_success_response(
+            self.organization.slug,
+            self.project.slug,
+            name="hello world",
+            owner=f"user:{self.user.id}",
+            environment=None,
+            actionMatch="any",
+            frequency=5,
+            actions=actions,
+            conditions=conditions,
+            status_code=status.HTTP_200_OK,
+        )
+        rule = Rule.objects.get(id=response.data.get("id"))
+        assert rule.data["actions"][0] == {
+            "id": "sentry.rules.actions.notify_event.NotifyEventAction"
+        }
+        assert rule.data["conditions"][0] == {
+            "id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"
+        }
+
     def test_with_environment(self):
         Environment.get_or_create(self.project, "production")
         conditions = [{"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}]
diff --git a/tests/sentry/integrations/slack/test_tasks.py b/tests/sentry/integrations/slack/test_tasks.py
--- a/tests/sentry/integrations/slack/test_tasks.py
+++ b/tests/sentry/integrations/slack/test_tasks.py
@@ -109,7 +109,6 @@ def test_task_new_rule(self, mock_set_value):
                 "channel": "#my-channel",
                 "channel_id": "chan-id",
                 "id": "sentry.integrations.slack.notify_action.SlackNotifyServiceAction",
-                "name": "Send a notification to the funinthesun Slack workspace to #secrets and show tags [] in notification",
                 "tags": "",
                 "workspace": self.integration.id,
             }
@@ -158,7 +157,6 @@ def test_task_existing_rule(self, mock_set_value):
                 "channel": "#my-channel",
                 "channel_id": "chan-id",
                 "id": "sentry.integrations.slack.notify_action.SlackNotifyServiceAction",
-                "name": "Send a notification to the funinthesun Slack workspace to #secrets and show tags [] in notification",
                 "tags": "",
                 "workspace": self.integration.id,
             }
diff --git a/tests/sentry/mediators/project_rules/test_creator.py b/tests/sentry/mediators/project_rules/test_creator.py
--- a/tests/sentry/mediators/project_rules/test_creator.py
+++ b/tests/sentry/mediators/project_rules/test_creator.py
@@ -48,7 +48,6 @@ def test_creates_rule(self):
             "actions": [
                 {
                     "id": "sentry.rules.actions.notify_event.NotifyEventAction",
-                    "name": "Send a notification (for all legacy integrations)",
                 }
             ],
             "conditions": [
diff --git a/tests/sentry/mediators/project_rules/test_updater.py b/tests/sentry/mediators/project_rules/test_updater.py
--- a/tests/sentry/mediators/project_rules/test_updater.py
+++ b/tests/sentry/mediators/project_rules/test_updater.py
@@ -63,7 +63,6 @@ def test_update_actions(self):
         assert self.rule.data["actions"] == [
             {
                 "id": "sentry.rules.actions.notify_event.NotifyEventAction",
-                "name": "Send a notification (for all legacy integrations)",
             }
         ]