Skip to content

Commit

Permalink
chore(hybridcloud) Backfill AlertRule user_id and team_id (#69227)
Browse files Browse the repository at this point in the history
As part of the work to finish removing `Actor` we need to backfill the
`team_id` and `user_id` columns on `AlertRule` as records before
2023-10-24 have null in these attributes.

After this backfill is complete, I'll update read paths to use
`team_id`, `user_id` and complete the work to remove `actor_id` from
this table.

Refs HC-1166
  • Loading branch information
markstory authored and MichaelSun48 committed Apr 25, 2024
1 parent 24afb1c commit e4219b3
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 1 deletion.
2 changes: 1 addition & 1 deletion migrations_lockfile.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ feedback: 0004_index_together
hybridcloud: 0016_add_control_cacheversion
nodestore: 0002_nodestore_no_dictfield
replays: 0004_index_together
sentry: 0700_drop_fileid_controlavatar
sentry: 0701_backfill_alertrule_user_team
social_auth: 0002_default_auto_field
2 changes: 2 additions & 0 deletions src/sentry/incidents/models/alert_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,15 @@ class AlertRule(Model):
"sentry.Project", related_name="alert_rule_projects", through=AlertRuleProjects
)
snuba_query = FlexibleForeignKey("sentry.SnubaQuery", null=True, unique=True)
# Deprecated use user_id or team_id instead.
owner = FlexibleForeignKey(
"sentry.Actor",
null=True,
on_delete=models.SET_NULL,
)
user_id = HybridCloudForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete="SET_NULL")
team = FlexibleForeignKey("sentry.Team", null=True, on_delete=models.SET_NULL)

excluded_projects = models.ManyToManyField(
"sentry.Project", related_name="alert_rule_exclusions", through=AlertRuleExcludedProjects
) # NOTE: This feature is not currently utilized.
Expand Down
55 changes: 55 additions & 0 deletions src/sentry/migrations/0701_backfill_alertrule_user_team.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Generated by Django 5.0.3 on 2024-04-18 15:49
import click
from django.apps.registry import Apps
from django.db import migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.models import Q

from sentry.new_migrations.migrations import CheckedMigration
from sentry.utils.query import RangeQuerySetWrapperWithProgressBar


def backfill_alertrule_owners(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None:
AlertRule = apps.get_model("sentry", "AlertRule")
Actor = apps.get_model("sentry", "Actor")

expr = Q(user_id__isnull=True) | Q(team_id__isnull=True)
# AlertRule defines a custom default manager.
rules = AlertRule.objects_with_snapshots.filter(owner_id__isnull=False).filter(expr)
for rule in RangeQuerySetWrapperWithProgressBar(rules):
actor = Actor.objects.get(id=rule.owner_id)
if actor.user_id:
rule.user_id = actor.user_id
elif actor.team_id:
rule.team_id = actor.team_id
else:
click.echo("Actor was neither a user or team")
rule.save(update_fields=["team_id", "user_id"])


class Migration(CheckedMigration):
# This flag is used to mark that a migration shouldn't be automatically run in production.
# 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 post deployment:
# - Large data migrations. Typically we want these to be run manually 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
# run this outside deployments so that we don't block them. Note that while adding an index
# is a schema change, it's completely safe to run the operation after the code has deployed.
# Once deployed, run these manually via: https://develop.sentry.dev/database-migrations/#migration-deployment

is_post_deployment = True

dependencies = [
("sentry", "0700_drop_fileid_controlavatar"),
]

operations = [
migrations.RunPython(
backfill_alertrule_owners,
reverse_code=migrations.RunPython.noop,
hints={"tables": ["sentry_alertrule", "sentry_actor"]},
)
]
54 changes: 54 additions & 0 deletions tests/sentry/migrations/test_701_backfill_alertrule_user_team.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from sentry.incidents.models.alert_rule import AlertRule
from sentry.models.actor import ACTOR_TYPES, Actor
from sentry.models.team import Team
from sentry.models.user import User
from sentry.testutils.cases import TestMigrations
from sentry.utils.actor import ActorTuple


class BackfillAlertRuleUserTeamTest(TestMigrations):
migrate_from = "0700_drop_fileid_controlavatar"
migrate_to = "0701_backfill_alertrule_user_team"

def setup_initial_state(self):
self.org = self.create_organization(owner=self.user)
self.team = self.create_team(organization=self.org, members=[self.user])

self.user_actor = Actor.objects.create(type=ACTOR_TYPES["user"], user_id=self.user.id)
self.team_actor = Actor.objects.get(type=ACTOR_TYPES["team"], team_id=self.team.id)
self.team_alert = self.create_alert_rule(
organization=self.org,
projects=[self.project],
owner=ActorTuple(id=self.team.id, type=Team),
)
self.user_alert = self.create_alert_rule(
organization=self.org,
projects=[self.project],
owner=ActorTuple(id=self.user.id, type=User),
)

other_user = self.create_user()
self.valid = self.create_alert_rule(
organization=self.org,
projects=[self.project],
owner=ActorTuple(id=other_user.id, type=User),
)

# Use QuerySet.update() to avoid validation in AlertRule
AlertRule.objects.filter(id__in=[self.team_alert.id, self.user_alert.id]).update(
team_id=None, user_id=None
)

def test(self):
self.user_alert.refresh_from_db()
self.team_alert.refresh_from_db()
self.valid.refresh_from_db()

assert self.user_alert.user_id == self.user.id
assert self.user_alert.team_id is None

assert self.team_alert.team_id == self.team.id
assert self.team_alert.user_id is None

assert self.valid.team_id is None
assert self.valid.user_id

0 comments on commit e4219b3

Please sign in to comment.