diff --git a/migrations_lockfile.txt b/migrations_lockfile.txt index ce1757e1b7ca52..2e95b43f1aa1e6 100644 --- a/migrations_lockfile.txt +++ b/migrations_lockfile.txt @@ -29,7 +29,7 @@ releases: 0004_cleanup_failed_safe_deletes replays: 0007_organizationmember_replay_access -seer: 0009_genericize_night_shift_results +seer: 0010_drop_legacy_columns sentry: 1080_backfill_deprecated_dashboard_widget_display_types diff --git a/src/sentry/seer/migrations/0010_drop_legacy_columns.py b/src/sentry/seer/migrations/0010_drop_legacy_columns.py new file mode 100644 index 00000000000000..6d25006d880120 --- /dev/null +++ b/src/sentry/seer/migrations/0010_drop_legacy_columns.py @@ -0,0 +1,50 @@ +# Generated by Django 5.2.12 on 2026-05-05 02:03 + +from django.db import migrations, models + +from sentry.new_migrations.migrations import CheckedMigration +from sentry.new_migrations.monkey.fields import SafeRemoveField +from sentry.new_migrations.monkey.state import DeletionAction + + +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 = False + + dependencies = [ + ("seer", "0009_genericize_night_shift_results"), + ] + + operations = [ + SafeRemoveField( + model_name="seernightshiftrunresult", + name="action", + deletion_action=DeletionAction.DELETE, + ), + SafeRemoveField( + model_name="seernightshiftrun", + name="triage_strategy", + deletion_action=DeletionAction.DELETE, + ), + SafeRemoveField( + model_name="seernightshiftrun", + name="error_message", + deletion_action=DeletionAction.DELETE, + ), + migrations.AlterField( + model_name="seernightshiftrunresult", + name="kind", + field=models.CharField(max_length=256), + ), + ] diff --git a/src/sentry/seer/models/night_shift.py b/src/sentry/seer/models/night_shift.py index b1304990e3aa4d..dd4c8830651c56 100644 --- a/src/sentry/seer/models/night_shift.py +++ b/src/sentry/seer/models/night_shift.py @@ -43,9 +43,7 @@ class SeerNightShiftRunResult(DefaultFieldsModel): run = FlexibleForeignKey( "seer.SeerNightShiftRun", on_delete=models.CASCADE, related_name="results" ) - kind = models.CharField( - max_length=256, db_default="agentic_triage", choices=NightShiftRunResultKind.choices - ) + kind = models.CharField(max_length=256, choices=NightShiftRunResultKind.choices) group = FlexibleForeignKey( "sentry.Group", on_delete=models.CASCADE, db_constraint=False, null=True ) diff --git a/tests/sentry/seer/migrations/__init__.py b/tests/sentry/seer/migrations/__init__.py deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/tests/sentry/seer/migrations/test_0009_genericize_night_shift_results.py b/tests/sentry/seer/migrations/test_0009_genericize_night_shift_results.py deleted file mode 100644 index de4ec73ee5a6d1..00000000000000 --- a/tests/sentry/seer/migrations/test_0009_genericize_night_shift_results.py +++ /dev/null @@ -1,67 +0,0 @@ -from django.db.migrations.state import StateApps - -from sentry.testutils.cases import TestMigrations - - -class GenericizeNightShiftResultsMigrationTest(TestMigrations): - migrate_from = "0008_add_seer_run_models" - migrate_to = "0009_genericize_night_shift_results" - app = "seer" - - def setup_initial_state(self) -> None: - self.group = self.create_group() - - def setup_before_migration(self, apps: StateApps) -> None: - SeerNightShiftRun = apps.get_model("seer", "SeerNightShiftRun") - SeerNightShiftRunIssue = apps.get_model("seer", "SeerNightShiftRunIssue") - - run = SeerNightShiftRun.objects.create( - organization_id=self.organization.id, - triage_strategy="agentic_triage", - ) - autofix_row = SeerNightShiftRunIssue.objects.create( - run_id=run.id, - group_id=self.group.id, - action="autofix", - seer_run_id="seer-1", - ) - root_cause_row = SeerNightShiftRunIssue.objects.create( - run_id=run.id, - group_id=self.group.id, - action="root_cause_only", - seer_run_id="seer-2", - ) - self.run_id = run.id - self.autofix_row_id = autofix_row.id - self.root_cause_row_id = root_cause_row.id - - # A second run that recorded a failure on the legacy error_message - # column, to verify the per-row error_message backfill into extras. - self.failed_error_message = "No Seer quota available" - failed_run = SeerNightShiftRun.objects.create( - organization_id=self.organization.id, - triage_strategy="agentic_triage", - error_message=self.failed_error_message, - ) - self.failed_run_id = failed_run.id - - def test(self) -> None: - from sentry.seer.models.night_shift import SeerNightShiftRun, SeerNightShiftRunResult - - autofix_row = SeerNightShiftRunResult.objects.get(id=self.autofix_row_id) - assert autofix_row.kind == "agentic_triage" - assert autofix_row.extras == {"action": "autofix"} - assert autofix_row.seer_run_id == "seer-1" - - root_cause_row = SeerNightShiftRunResult.objects.get(id=self.root_cause_row_id) - assert root_cause_row.kind == "agentic_triage" - assert root_cause_row.extras == {"action": "root_cause_only"} - assert root_cause_row.seer_run_id == "seer-2" - - # Run with no error_message keeps an empty (or near-empty) extras. - ok_run = SeerNightShiftRun.objects.get(id=self.run_id) - assert "error_message" not in (ok_run.extras or {}) - - # Run with a recorded error has it preserved in extras. - failed_run = SeerNightShiftRun.objects.get(id=self.failed_run_id) - assert failed_run.extras["error_message"] == self.failed_error_message