Browse Source

chore(aci): allow blank FKs on Workflow for dry run full_clean() (#84973)

Cathy Teng 3 weeks ago
parent
commit
c9733a8112

+ 1 - 1
migrations_lockfile.txt

@@ -23,4 +23,4 @@ tempest: 0001_create_tempest_credentials_model
 
 
 uptime: 0025_uptime_migrate_constraint
 uptime: 0025_uptime_migrate_constraint
 
 
-workflow_engine: 0029_ds_query_id_to_pending
+workflow_engine: 0030_allow_blank_workflow_owner_fks

+ 4 - 2
src/sentry/models/owner_base.py

@@ -14,8 +14,10 @@ class OwnerModel(Model):
     A base model that adds ownership fields to existing models.
     A base model that adds ownership fields to existing models.
     """
     """
 
 
-    owner_user_id = HybridCloudForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete="SET_NULL")
-    owner_team = FlexibleForeignKey("sentry.Team", null=True, on_delete=models.SET_NULL)
+    owner_user_id = HybridCloudForeignKey(
+        settings.AUTH_USER_MODEL, null=True, blank=True, on_delete="SET_NULL"
+    )
+    owner_team = FlexibleForeignKey("sentry.Team", null=True, blank=True, on_delete=models.SET_NULL)
 
 
     class Meta:
     class Meta:
         abstract = True
         abstract = True

+ 93 - 0
src/sentry/workflow_engine/migrations/0030_allow_blank_workflow_owner_fks.py

@@ -0,0 +1,93 @@
+# Generated by Django 5.1.5 on 2025-02-11 19:46
+
+import django.db.models.deletion
+from django.db import migrations
+
+import sentry.db.models.fields.foreignkey
+import sentry.db.models.fields.hybrid_cloud_foreign_key
+from sentry.new_migrations.migrations import CheckedMigration
+
+
+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 = [
+        ("sentry", "0828_add_platform_to_grouphash_metadata"),
+        ("workflow_engine", "0029_ds_query_id_to_pending"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="detector",
+            name="owner_team",
+            field=sentry.db.models.fields.foreignkey.FlexibleForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.SET_NULL,
+                to="sentry.team",
+            ),
+        ),
+        migrations.AlterField(
+            model_name="detector",
+            name="owner_user_id",
+            field=sentry.db.models.fields.hybrid_cloud_foreign_key.HybridCloudForeignKey(
+                "sentry.User", blank=True, db_index=True, null=True, on_delete="SET_NULL"
+            ),
+        ),
+        migrations.AlterField(
+            model_name="workflow",
+            name="created_by_id",
+            field=sentry.db.models.fields.hybrid_cloud_foreign_key.HybridCloudForeignKey(
+                "sentry.User", blank=True, db_index=True, null=True, on_delete="SET_NULL"
+            ),
+        ),
+        migrations.AlterField(
+            model_name="workflow",
+            name="environment",
+            field=sentry.db.models.fields.foreignkey.FlexibleForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.CASCADE,
+                to="sentry.environment",
+            ),
+        ),
+        migrations.AlterField(
+            model_name="workflow",
+            name="owner_team",
+            field=sentry.db.models.fields.foreignkey.FlexibleForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.SET_NULL,
+                to="sentry.team",
+            ),
+        ),
+        migrations.AlterField(
+            model_name="workflow",
+            name="owner_user_id",
+            field=sentry.db.models.fields.hybrid_cloud_foreign_key.HybridCloudForeignKey(
+                "sentry.User", blank=True, db_index=True, null=True, on_delete="SET_NULL"
+            ),
+        ),
+        migrations.AlterField(
+            model_name="workflow",
+            name="when_condition_group",
+            field=sentry.db.models.fields.foreignkey.FlexibleForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.CASCADE,
+                to="workflow_engine.dataconditiongroup",
+            ),
+        ),
+    ]

+ 7 - 3
src/sentry/workflow_engine/models/workflow.py

@@ -30,11 +30,15 @@ class Workflow(DefaultFieldsModel, OwnerModel, JSONConfigBase):
     enabled = models.BooleanField(db_default=True)
     enabled = models.BooleanField(db_default=True)
 
 
     # Required as the 'when' condition for the workflow, this evalutes states emitted from the detectors
     # Required as the 'when' condition for the workflow, this evalutes states emitted from the detectors
-    when_condition_group = FlexibleForeignKey("workflow_engine.DataConditionGroup", null=True)
+    when_condition_group = FlexibleForeignKey(
+        "workflow_engine.DataConditionGroup", null=True, blank=True
+    )
 
 
-    environment = FlexibleForeignKey("sentry.Environment", null=True)
+    environment = FlexibleForeignKey("sentry.Environment", null=True, blank=True)
 
 
-    created_by_id = HybridCloudForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete="SET_NULL")
+    created_by_id = HybridCloudForeignKey(
+        settings.AUTH_USER_MODEL, null=True, blank=True, on_delete="SET_NULL"
+    )
 
 
     DEFAULT_FREQUENCY = 30
     DEFAULT_FREQUENCY = 30
 
 

+ 44 - 0
tests/sentry/workflow_engine/models/test_workflow.py

@@ -1,3 +1,7 @@
+import pytest
+from django.core.exceptions import ValidationError
+
+from sentry.workflow_engine.models import Workflow
 from sentry.workflow_engine.models.data_condition import Condition
 from sentry.workflow_engine.models.data_condition import Condition
 from sentry.workflow_engine.types import WorkflowJob
 from sentry.workflow_engine.types import WorkflowJob
 from tests.sentry.workflow_engine.test_base import BaseWorkflowTest
 from tests.sentry.workflow_engine.test_base import BaseWorkflowTest
@@ -42,3 +46,43 @@ class WorkflowTest(BaseWorkflowTest):
 
 
         assert evaluation is True
         assert evaluation is True
         assert remaining_conditions == [slow_condition]
         assert remaining_conditions == [slow_condition]
+
+    def test_full_clean__success(self):
+        self.create_workflow(
+            organization_id=self.organization.id,
+            name="test",
+            environment_id=None,
+            when_condition_group=self.create_data_condition_group(),
+            created_by_id=None,
+            owner_user_id=None,
+            owner_team=None,
+            config={"frequency": 5},
+        )
+
+        workflow2 = Workflow(
+            organization_id=self.organization.id,
+            name="test2",
+            environment_id=None,
+            when_condition_group=self.create_data_condition_group(),
+            created_by_id=None,
+            owner_user_id=None,
+            owner_team=None,
+            config={"frequency": 5},
+        )
+        workflow2.full_clean()
+
+    def test_full_clean__fail(self):
+        workflow2 = Workflow(
+            organization_id=self.organization.id,
+            name="test2",
+            environment_id=None,
+            when_condition_group=self.create_data_condition_group(),
+            created_by_id=self.user.id,
+            owner_user_id=None,
+            owner_team=None,
+            config={"frequency": 5},
+        )
+        self.organization.delete()
+
+        with pytest.raises(ValidationError):
+            workflow2.full_clean()