Просмотр исходного кода

feat(integration): Backfill UUIDs for rule actions (#65123)

Backfill all rule actions to have a uuid. After backfill, the dashboard
should show 0: https://redash.getsentry.net/queries/6032.


Requires: https://github.com/getsentry/sentry/pull/64922
Yash Kamothi 1 год назад
Родитель
Сommit
50be7cefe2

+ 1 - 1
migrations_lockfile.txt

@@ -9,5 +9,5 @@ feedback: 0004_index_together
 hybridcloud: 0011_add_hybridcloudapitoken_index
 nodestore: 0002_nodestore_no_dictfield
 replays: 0004_index_together
-sentry: 0644_backfill_priority_for_groups
+sentry: 0645_backfill_add_uuid_to_all_rule_actions
 social_auth: 0002_default_auto_field

+ 82 - 0
src/sentry/migrations/0645_backfill_add_uuid_to_all_rule_actions.py

@@ -0,0 +1,82 @@
+# Generated by Django 5.0.2 on 2024-02-13 20:45
+from typing import Any
+from uuid import UUID, uuid4
+
+from django.db import migrations
+
+from sentry.new_migrations.migrations import CheckedMigration
+from sentry.utils.query import RangeQuerySetWrapperWithProgressBarApprox
+
+_ACTION_UUID_KEY = "uuid"
+
+
+def _ensure_action_uuid(action: dict[Any, Any]) -> None:
+    """
+    Ensure that each action is uniquely identifiable.
+    The function will check that a UUID key and value exists in the action.
+    If the key does not exist, or it's not a valid UUID, it will create a new one and assign it to the action.
+
+    Does not add an uuid to the action if it is empty.
+    """
+    if not action:
+        return
+
+    if _ACTION_UUID_KEY in action:
+        existing_uuid = action[_ACTION_UUID_KEY]
+        try:
+            UUID(existing_uuid)
+        except (ValueError, TypeError):
+            pass
+        else:
+            return
+
+    action[_ACTION_UUID_KEY] = str(uuid4())
+
+
+def add_uuid_to_all_rule_actions(apps, schema_editor) -> None:
+    """
+    Backfill all rule actions with an uuid.
+    If the rule's data does not have any actions, simply move on and do not update.
+    """
+    Rule = apps.get_model("sentry", "Rule")
+
+    for rule in RangeQuerySetWrapperWithProgressBarApprox(Rule.objects.all()):
+        actions = rule.data.get("actions", [])
+        if not actions:
+            continue
+
+        for action in actions:
+            _ensure_action_uuid(action)
+
+        rule.data["actions"] = actions
+        rule.save(update_fields=["data"])
+
+
+class Migration(CheckedMigration):
+    # This flag is used to mark that a migration shouldn't be automatically run in production. For
+    # the most part, this should only be used for operations where it's safe to run the migration
+    # after your code has deployed. So this should not be used for most operations that alter the
+    # schema of a table.
+    # Here are some things that make sense to mark as dangerous:
+    # - Large data migrations. Typically we want these to be run manually by ops so that they can
+    #   be monitored and not block the deploy for a long period of time while they run.
+    # - Adding indexes to large tables. Since this can take a long time, we'd generally prefer to
+    #   have ops run this and not block the deploy. Note that while adding an index is a schema
+    #   change, it's completely safe to run the operation after the code has deployed.
+    is_dangerous = True
+
+    dependencies = [
+        ("sentry", "0644_backfill_priority_for_groups"),
+    ]
+
+    operations = [
+        migrations.RunPython(
+            add_uuid_to_all_rule_actions,
+            migrations.RunPython.noop,
+            hints={
+                "tables": [
+                    "sentry_rule",
+                ]
+            },
+        )
+    ]

+ 22 - 0
tests/sentry/migrations/test_0645_backfill_add_uuid_to_all_rule_actions.py

@@ -0,0 +1,22 @@
+from uuid import UUID
+
+from sentry.testutils.cases import TestMigrations
+
+
+class BackfillAddUuidToAllRuleActions(TestMigrations):
+    migrate_from = "0644_backfill_priority_for_groups"
+    migrate_to = "0645_backfill_add_uuid_to_all_rule_actions"
+
+    def setup_before_migration(self, apps):
+        # Create your db state here
+        self.notify_event_action = [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}]
+        self.rule = self.create_project_rule(action_match=self.notify_event_action)
+
+    def test(self):
+        # Test state after migration
+        self.rule.refresh_from_db()
+        actions = self.rule.data["actions"]
+        assert len(actions) == len(self.notify_event_action)
+        action = actions[0]
+        assert "uuid" in action
+        assert UUID(action["uuid"])