diff --git a/migrations_lockfile.txt b/migrations_lockfile.txt index d78afee5c9ef22..fdff4a2b285fc7 100644 --- a/migrations_lockfile.txt +++ b/migrations_lockfile.txt @@ -39,4 +39,4 @@ tempest: 0003_use_encrypted_char_field uptime: 0049_cleanup_failed_safe_deletes -workflow_engine: 0102_cleanup_failed_safe_deletes +workflow_engine: 0103_action_data_fallthrough_type diff --git a/src/sentry/workflow_engine/migrations/0103_action_data_fallthrough_type.py b/src/sentry/workflow_engine/migrations/0103_action_data_fallthrough_type.py new file mode 100644 index 00000000000000..547432317530d5 --- /dev/null +++ b/src/sentry/workflow_engine/migrations/0103_action_data_fallthrough_type.py @@ -0,0 +1,47 @@ +# Generated by Django 5.2.8 on 2025-11-20 20:02 + +from django.db import migrations +from django.db.backends.base.schema import BaseDatabaseSchemaEditor +from django.db.migrations.state import StateApps + +from sentry.new_migrations.migrations import CheckedMigration +from sentry.utils.query import RangeQuerySetWrapper + + +def migrate_fallthrough_type(apps: StateApps, schema_editor: BaseDatabaseSchemaEditor) -> None: + Action = apps.get_model("workflow_engine", "Action") + for action in RangeQuerySetWrapper(Action.objects.all()): + if action.data.get("fallthroughType"): + new_data = action.data.copy() + del new_data["fallthroughType"] + new_data["fallthrough_type"] = action.data["fallthroughType"] + action.data = new_data + action.save() + + +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 = [ + ("workflow_engine", "0102_cleanup_failed_safe_deletes"), + ] + + operations = [ + migrations.RunPython( + code=migrate_fallthrough_type, + reverse_code=migrations.RunPython.noop, + hints={"tables": ["workflow_engine_workflowfirehistory"]}, + ), + ] diff --git a/tests/sentry/workflow_engine/migrations/test_0103_action_data_fallthrough_type.py b/tests/sentry/workflow_engine/migrations/test_0103_action_data_fallthrough_type.py new file mode 100644 index 00000000000000..61350135531083 --- /dev/null +++ b/tests/sentry/workflow_engine/migrations/test_0103_action_data_fallthrough_type.py @@ -0,0 +1,40 @@ +from sentry.notifications.models.notificationaction import ActionTarget +from sentry.notifications.types import FallthroughChoiceType +from sentry.testutils.cases import TestMigrations +from sentry.workflow_engine.models import Action + + +class TestActionDataFallthroughType(TestMigrations): + migrate_from = "0102_cleanup_failed_safe_deletes" + migrate_to = "0103_action_data_fallthrough_type" + app = "workflow_engine" + + def setup_initial_state(self) -> None: + self.org = self.create_organization(name="test-org") + self.project = self.create_project(organization=self.org) + + self.action = Action.objects.create( + type="email", + data={"fallthroughType": FallthroughChoiceType.ACTIVE_MEMBERS}, + config={ + "target_type": ActionTarget.ISSUE_OWNERS, + "target_identifier": None, + }, + ) + self.action_no_fallthrough = Action.objects.create( + type="email", + data={}, + config={ + "target_type": ActionTarget.USER, + "target_identifier": str(self.user.id), + }, + ) + + def test_migration(self) -> None: + fallthrough_action = Action.objects.filter( + data={"fallthrough_type": FallthroughChoiceType.ACTIVE_MEMBERS} + ) + assert len(fallthrough_action) == 1 + + no_fallthrough_action = Action.objects.filter(data={}) + assert len(no_fallthrough_action) == 1