Browse Source

Add DataConditions for workflow_engine (#77448)

# Description
[📜 Technical spec](
https://www.notion.so/sentry/Alerts-Create-Issues-9114a498098143178839d584c293fe75?pvs=4#2910558fcd2941b9af88d86fa37fa390)

### PR Changes
- Adds DataConditions
- Connects `DataCondition` with a 1 to many relationship for
`DataConditionGroups` the Group model allows us to define `ANY` and
`ALL`, and group the conditions for evaluation.
Josh Callender 5 months ago
parent
commit
60674f0550

+ 134 - 0
fixtures/backup/model_dependencies/detailed.json

@@ -6218,6 +6218,77 @@
       ]
     ]
   },
+  "workflow_engine.action": {
+    "dangling": false,
+    "foreign_keys": {},
+    "model": "workflow_engine.action",
+    "relocation_dependencies": [],
+    "relocation_scope": "Excluded",
+    "silos": [
+      "Region"
+    ],
+    "table_name": "workflow_engine_action",
+    "uniques": []
+  },
+  "workflow_engine.datacondition": {
+    "dangling": false,
+    "foreign_keys": {
+      "condition_group": {
+        "kind": "DefaultForeignKey",
+        "model": "workflow_engine.dataconditiongroup",
+        "nullable": false
+      }
+    },
+    "model": "workflow_engine.datacondition",
+    "relocation_dependencies": [],
+    "relocation_scope": "Organization",
+    "silos": [
+      "Region"
+    ],
+    "table_name": "workflow_engine_datacondition",
+    "uniques": []
+  },
+  "workflow_engine.dataconditiongroup": {
+    "dangling": false,
+    "foreign_keys": {
+      "organization": {
+        "kind": "DefaultForeignKey",
+        "model": "sentry.organization",
+        "nullable": false
+      }
+    },
+    "model": "workflow_engine.dataconditiongroup",
+    "relocation_dependencies": [],
+    "relocation_scope": "Organization",
+    "silos": [
+      "Region"
+    ],
+    "table_name": "workflow_engine_dataconditiongroup",
+    "uniques": []
+  },
+  "workflow_engine.dataconditiongroupaction": {
+    "dangling": false,
+    "foreign_keys": {
+      "action": {
+        "kind": "FlexibleForeignKey",
+        "model": "workflow_engine.action",
+        "nullable": false
+      },
+      "condition_group": {
+        "kind": "FlexibleForeignKey",
+        "model": "workflow_engine.dataconditiongroup",
+        "nullable": false
+      }
+    },
+    "model": "workflow_engine.dataconditiongroupaction",
+    "relocation_dependencies": [],
+    "relocation_scope": "Excluded",
+    "silos": [
+      "Region"
+    ],
+    "table_name": "workflow_engine_dataconditiongroupaction",
+    "uniques": []
+  },
   "workflow_engine.datasource": {
     "dangling": false,
     "foreign_keys": {
@@ -6281,6 +6352,11 @@
         "kind": "HybridCloudForeignKey",
         "model": "sentry.user",
         "nullable": true
+      },
+      "workflow_condition_group": {
+        "kind": "FlexibleForeignKey",
+        "model": "workflow_engine.dataconditiongroup",
+        "nullable": true
       }
     },
     "model": "workflow_engine.detector",
@@ -6294,9 +6370,35 @@
       [
         "name",
         "organization"
+      ],
+      [
+        "workflow_condition_group"
       ]
     ]
   },
+  "workflow_engine.detectorworkflow": {
+    "dangling": false,
+    "foreign_keys": {
+      "detector": {
+        "kind": "FlexibleForeignKey",
+        "model": "workflow_engine.detector",
+        "nullable": false
+      },
+      "workflow": {
+        "kind": "FlexibleForeignKey",
+        "model": "workflow_engine.workflow",
+        "nullable": false
+      }
+    },
+    "model": "workflow_engine.detectorworkflow",
+    "relocation_dependencies": [],
+    "relocation_scope": "Organization",
+    "silos": [
+      "Region"
+    ],
+    "table_name": "workflow_engine_detectorworkflow",
+    "uniques": []
+  },
   "workflow_engine.workflow": {
     "dangling": false,
     "foreign_keys": {
@@ -6304,6 +6406,11 @@
         "kind": "FlexibleForeignKey",
         "model": "sentry.organization",
         "nullable": false
+      },
+      "when_condition_group": {
+        "kind": "FlexibleForeignKey",
+        "model": "workflow_engine.dataconditiongroup",
+        "nullable": true
       }
     },
     "model": "workflow_engine.workflow",
@@ -6337,5 +6444,32 @@
     ],
     "table_name": "workflow_engine_workflowaction",
     "uniques": []
+  },
+  "workflow_engine.workflowdataconditiongroup": {
+    "dangling": false,
+    "foreign_keys": {
+      "condition_group": {
+        "kind": "FlexibleForeignKey",
+        "model": "workflow_engine.dataconditiongroup",
+        "nullable": false
+      },
+      "workflow": {
+        "kind": "FlexibleForeignKey",
+        "model": "workflow_engine.workflow",
+        "nullable": false
+      }
+    },
+    "model": "workflow_engine.workflowdataconditiongroup",
+    "relocation_dependencies": [],
+    "relocation_scope": "Organization",
+    "silos": [
+      "Region"
+    ],
+    "table_name": "workflow_engine_workflowdataconditiongroup",
+    "uniques": [
+      [
+        "condition_group"
+      ]
+    ]
   }
 }

+ 23 - 2
fixtures/backup/model_dependencies/flat.json

@@ -856,6 +856,17 @@
     "uptime.uptimesubscription"
   ],
   "uptime.uptimesubscription": [],
+  "workflow_engine.action": [],
+  "workflow_engine.datacondition": [
+    "workflow_engine.dataconditiongroup"
+  ],
+  "workflow_engine.dataconditiongroup": [
+    "sentry.organization"
+  ],
+  "workflow_engine.dataconditiongroupaction": [
+    "workflow_engine.action",
+    "workflow_engine.dataconditiongroup"
+  ],
   "workflow_engine.datasource": [
     "sentry.organization"
   ],
@@ -866,12 +877,22 @@
   "workflow_engine.detector": [
     "sentry.organization",
     "sentry.team",
-    "sentry.user"
+    "sentry.user",
+    "workflow_engine.dataconditiongroup"
+  ],
+  "workflow_engine.detectorworkflow": [
+    "workflow_engine.detector",
+    "workflow_engine.workflow"
   ],
   "workflow_engine.workflow": [
-    "sentry.organization"
+    "sentry.organization",
+    "workflow_engine.dataconditiongroup"
   ],
   "workflow_engine.workflowaction": [
     "workflow_engine.workflow"
+  ],
+  "workflow_engine.workflowdataconditiongroup": [
+    "workflow_engine.dataconditiongroup",
+    "workflow_engine.workflow"
   ]
 }

+ 6 - 0
fixtures/backup/model_dependencies/sorted.json

@@ -48,11 +48,17 @@
   "sentry.userroleuser",
   "social_auth.usersocialauth",
   "uptime.uptimesubscription",
+  "workflow_engine.action",
+  "workflow_engine.dataconditiongroup",
+  "workflow_engine.dataconditiongroupaction",
   "workflow_engine.datasource",
   "workflow_engine.detector",
   "workflow_engine.workflow",
   "workflow_engine.workflowaction",
+  "workflow_engine.workflowdataconditiongroup",
+  "workflow_engine.detectorworkflow",
   "workflow_engine.datasourcedetector",
+  "workflow_engine.datacondition",
   "sentry.savedsearch",
   "sentry.relocation",
   "sentry.recentsearch",

+ 6 - 0
fixtures/backup/model_dependencies/truncate.json

@@ -48,11 +48,17 @@
   "sentry_userrole_users",
   "social_auth_usersocialauth",
   "uptime_uptimesubscription",
+  "workflow_engine_action",
+  "workflow_engine_dataconditiongroup",
+  "workflow_engine_dataconditiongroupaction",
   "workflow_engine_datasource",
   "workflow_engine_detector",
   "workflow_engine_workflow",
   "workflow_engine_workflowaction",
+  "workflow_engine_workflowdataconditiongroup",
+  "workflow_engine_detectorworkflow",
   "workflow_engine_datasourcedetector",
+  "workflow_engine_datacondition",
   "sentry_savedsearch",
   "sentry_relocation",
   "sentry_recentsearch",

+ 1 - 1
migrations_lockfile.txt

@@ -13,4 +13,4 @@ replays: 0004_index_together
 sentry: 0768_fix_old_group_first_seen_dates
 social_auth: 0002_default_auto_field
 uptime: 0013_uptime_subscription_new_unique
-workflow_engine: 0005_data_source_detector
+workflow_engine: 0006_data_conditions

+ 14 - 0
src/sentry/backup/comparators.py

@@ -865,13 +865,27 @@ def get_default_comparators() -> dict[str, list[JSONScrubbingComparator]]:
             ],
             "sentry.userrole": [DateUpdatedComparator("date_updated")],
             "sentry.userroleuser": [DateUpdatedComparator("date_updated")],
+            "workflow_engine.action": [DateUpdatedComparator("date_updated", "date_added")],
+            "workflow_engine.datacondition": [DateUpdatedComparator("date_updated", "date_added")],
+            "workflow_engine.dataconditiongroup": [
+                DateUpdatedComparator("date_updated", "date_added")
+            ],
+            "workflow_engine.dataconditiongroupaction": [
+                DateUpdatedComparator("date_updated", "date_added")
+            ],
             "workflow_engine.datasource": [DateUpdatedComparator("date_updated", "date_added")],
             "workflow_engine.datasourcedetector": [
                 DateUpdatedComparator("date_updated", "date_added")
             ],
             "workflow_engine.detector": [DateUpdatedComparator("date_updated", "date_added")],
+            "workflow_engine.detectorworkflow": [
+                DateUpdatedComparator("date_updated", "date_added")
+            ],
             "workflow_engine.workflow": [DateUpdatedComparator("date_updated", "date_added")],
             "workflow_engine.workflowaction": [DateUpdatedComparator("date_updated", "date_added")],
+            "workflow_engine.workflowdataconditiongroup": [
+                DateUpdatedComparator("date_updated", "date_added")
+            ],
         },
     )
 

+ 70 - 3
src/sentry/testutils/factories.py

@@ -171,11 +171,17 @@ from sentry.users.services.user import RpcUser
 from sentry.utils import loremipsum
 from sentry.utils.performance_issues.performance_problem import PerformanceProblem
 from sentry.workflow_engine.models import (
+    Action,
+    DataCondition,
+    DataConditionGroup,
+    DataConditionGroupAction,
     DataSource,
     DataSourceDetector,
     Detector,
+    DetectorWorkflow,
     Workflow,
     WorkflowAction,
+    WorkflowDataConditionGroup,
 )
 from social_auth.models import UserSocialAuth
 
@@ -2065,13 +2071,41 @@ class Factories:
 
     @staticmethod
     @assume_test_silo_mode(SiloMode.REGION)
-    def create_workflowaction(
-        workflow: Workflow | None = None,
+    def create_workflow_action(
         **kwargs,
     ) -> WorkflowAction:
+        return WorkflowAction.objects.create(**kwargs)
+
+    @staticmethod
+    @assume_test_silo_mode(SiloMode.REGION)
+    def create_data_condition_group(
+        **kwargs,
+    ) -> DataConditionGroup:
+        return DataConditionGroup.objects.create(**kwargs)
+
+    @staticmethod
+    @assume_test_silo_mode(SiloMode.REGION)
+    def create_workflow_data_condition_group(
+        workflow: Workflow | None = None,
+        condition_group: DataConditionGroup | None = None,
+        **kwargs,
+    ) -> WorkflowDataConditionGroup:
         if workflow is None:
             workflow = Factories.create_workflow()
-        return WorkflowAction.objects.create(workflow=workflow, **kwargs)
+
+        if not condition_group:
+            condition_group = Factories.create_data_condition_group()
+
+        return WorkflowDataConditionGroup.objects.create(
+            workflow=workflow, condition_group=condition_group
+        )
+
+    @staticmethod
+    @assume_test_silo_mode(SiloMode.REGION)
+    def create_data_condition(
+        **kwargs,
+    ) -> DataCondition:
+        return DataCondition.objects.create(**kwargs)
 
     @staticmethod
     @assume_test_silo_mode(SiloMode.REGION)
@@ -2118,3 +2152,36 @@ class Factories:
         if detector is None:
             detector = Factories.create_detector()
         return DataSourceDetector.objects.create(data_source=data_source, detector=detector)
+
+    @staticmethod
+    @assume_test_silo_mode(SiloMode.REGION)
+    def create_action(**kwargs) -> Action:
+        return Action.objects.create(**kwargs)
+
+    @staticmethod
+    @assume_test_silo_mode(SiloMode.REGION)
+    def create_detector_workflow(
+        detector: Detector | None = None,
+        workflow: Workflow | None = None,
+        **kwargs,
+    ) -> DetectorWorkflow:
+        if detector is None:
+            detector = Factories.create_detector()
+        if workflow is None:
+            workflow = Factories.create_workflow()
+        return DetectorWorkflow.objects.create(detector=detector, workflow=workflow, **kwargs)
+
+    @staticmethod
+    @assume_test_silo_mode(SiloMode.REGION)
+    def create_data_condition_group_action(
+        action: Action | None = None,
+        condition_group: DataConditionGroup | None = None,
+        **kwargs,
+    ) -> DataConditionGroupAction:
+        if action is None:
+            action = Factories.create_action()
+        if condition_group is None:
+            condition_group = Factories.create_data_condition_group()
+        return DataConditionGroupAction.objects.create(
+            action=action, condition_group=condition_group, **kwargs
+        )

+ 26 - 6
src/sentry/testutils/fixtures.py

@@ -43,6 +43,7 @@ from sentry.uptime.models import (
 from sentry.users.models.identity import Identity, IdentityProvider
 from sentry.users.models.user import User
 from sentry.users.services.user import RpcUser
+from sentry.workflow_engine.models import DataSource, Detector, Workflow
 
 
 class Fixtures:
@@ -633,21 +634,40 @@ class Fixtures:
     def create_dashboard_widget_query(self, *args, **kwargs):
         return Factories.create_dashboard_widget_query(*args, **kwargs)
 
-    def create_workflow(self, *args, **kwargs):
-        return Factories.create_workflow(*args, **kwargs)
+    def create_workflow_action(self, *args, **kwargs) -> Workflow:
+        return Factories.create_workflow_action(*args, **kwargs)
 
-    def create_workflowaction(self, *args, **kwargs):
-        return Factories.create_workflowaction(*args, **kwargs)
+    def create_workflow(self, *args, **kwargs) -> Workflow:
+        return Factories.create_workflow(*args, **kwargs)
 
-    def create_data_source(self, *args, **kwargs):
+    def create_data_source(self, *args, **kwargs) -> DataSource:
         return Factories.create_data_source(*args, **kwargs)
 
-    def create_detector(self, *args, **kwargs):
+    def create_data_condition(self, *args, **kwargs):
+        return Factories.create_data_condition(*args, **kwargs)
+
+    def create_detector(self, *args, **kwargs) -> Detector:
         return Factories.create_detector(*args, **kwargs)
 
     def create_data_source_detector(self, *args, **kwargs):
         return Factories.create_data_source_detector(*args, **kwargs)
 
+    def create_data_condition_group(self, *args, **kwargs):
+        return Factories.create_data_condition_group(*args, **kwargs)
+
+    def create_data_condition_group_action(self, *args, **kwargs):
+        return Factories.create_data_condition_group_action(*args, **kwargs)
+
+    def create_detector_workflow(self, *args, **kwargs):
+        return Factories.create_detector_workflow(*args, **kwargs)
+
+    def create_workflow_data_condition_group(self, *args, **kwargs):
+        return Factories.create_workflow_data_condition_group(*args, **kwargs)
+
+    # workflow_engine action
+    def create_action(self, *args, **kwargs):
+        return Factories.create_action(*args, **kwargs)
+
     def create_uptime_subscription(
         self,
         type: str = "test",

+ 52 - 5
src/sentry/testutils/helpers/backups.py

@@ -111,6 +111,7 @@ from sentry.users.models.user_option import UserOption
 from sentry.users.models.userip import UserIP
 from sentry.users.models.userrole import UserRole, UserRoleUser
 from sentry.utils import json
+from sentry.workflow_engine.models import Action, DataConditionGroup
 
 __all__ = [
     "export_to_file",
@@ -608,13 +609,59 @@ class ExhaustiveFixtures(Fixtures):
             access_end=timezone.now() + timedelta(days=1),
         )
 
+        # Setup a test 'Issue Rule' and 'Automation'
         workflow = self.create_workflow(organization=org)
-        self.create_workflowaction(workflow=workflow)
-        self.create_workflow(organization=org)
-        self.create_data_source_detector(
-            self.create_data_source(organization=org),
-            self.create_detector(organization=org),
+        detector = self.create_detector(organization=org)
+        self.create_detector_workflow(detector=detector, workflow=workflow)
+
+        # TODO @saponifi3d: Delete this once the migration to remove the model is complete
+        self.create_workflow_action(workflow=workflow)
+
+        notification_condition_group = self.create_data_condition_group(
+            logic_type=DataConditionGroup.Type.ANY,
+            organization=org,
+        )
+
+        send_notification_action = self.create_action(type=Action.Type.Notification, data="")
+        self.create_data_condition_group_action(
+            action=send_notification_action,
+            condition_group=notification_condition_group,
+        )
+
+        # TODO @saponifi3d: Update warning to be DetectorState.Critical
+        self.create_data_condition(
+            condition="eq",
+            comparison="critical",
+            type="WorkflowCondition",
+            condition_result="True",
+            condition_group=notification_condition_group,
+        )
+
+        self.create_workflow_data_condition_group(
+            workflow=workflow, condition_group=notification_condition_group
+        )
+
+        data_source = self.create_data_source(organization=org)
+
+        self.create_data_source_detector(data_source, detector)
+        detector_conditions = self.create_data_condition_group(
+            logic_type=DataConditionGroup.Type.ALL,
+            organization=org,
+        )
+
+        # TODO @saponifi3d: Create or define trigger workflow action type
+        trigger_workflows_action = self.create_action(type=Action.Type.TriggerWorkflow, data="")
+        self.create_data_condition_group_action(
+            action=trigger_workflows_action, condition_group=detector_conditions
+        )
+        self.create_data_condition(
+            condition="eq",
+            comparison="critical",
+            type="DetectorCondition",
+            condition_result="True",
+            condition_group=detector_conditions,
         )
+        detector.workflow_condition_group = detector_conditions
 
         return org
 

+ 209 - 0
src/sentry/workflow_engine/migrations/0006_data_conditions.py

@@ -0,0 +1,209 @@
+# Generated by Django 5.1.1 on 2024-09-26 00:11
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+import sentry.db.models.fields.bounded
+import sentry.db.models.fields.foreignkey
+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", "0767_add_selected_aggregate_to_dashboards_widget_query"),
+        ("workflow_engine", "0005_data_source_detector"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="Action",
+            fields=[
+                (
+                    "id",
+                    sentry.db.models.fields.bounded.BoundedBigAutoField(
+                        primary_key=True, serialize=False
+                    ),
+                ),
+                ("date_updated", models.DateTimeField(auto_now=True)),
+                ("date_added", models.DateTimeField(auto_now_add=True)),
+                ("required", models.BooleanField(default=False)),
+                ("type", models.TextField()),
+                ("data", models.JSONField(default=dict)),
+            ],
+            options={
+                "abstract": False,
+            },
+        ),
+        migrations.CreateModel(
+            name="DataConditionGroup",
+            fields=[
+                (
+                    "id",
+                    sentry.db.models.fields.bounded.BoundedBigAutoField(
+                        primary_key=True, serialize=False
+                    ),
+                ),
+                ("date_updated", models.DateTimeField(auto_now=True)),
+                ("date_added", models.DateTimeField(auto_now_add=True)),
+                ("logic_type", models.CharField(default="any", max_length=200)),
+                (
+                    "organization",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="sentry.organization"
+                    ),
+                ),
+            ],
+            options={
+                "abstract": False,
+            },
+        ),
+        migrations.CreateModel(
+            name="DataCondition",
+            fields=[
+                (
+                    "id",
+                    sentry.db.models.fields.bounded.BoundedBigAutoField(
+                        primary_key=True, serialize=False
+                    ),
+                ),
+                ("date_updated", models.DateTimeField(auto_now=True)),
+                ("date_added", models.DateTimeField(auto_now_add=True)),
+                ("condition", models.CharField(max_length=200)),
+                ("comparison", models.JSONField()),
+                ("condition_result", models.JSONField()),
+                ("type", models.CharField(max_length=200)),
+                (
+                    "condition_group",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="workflow_engine.dataconditiongroup",
+                    ),
+                ),
+            ],
+            options={
+                "abstract": False,
+            },
+        ),
+        migrations.AddField(
+            model_name="detector",
+            name="workflow_condition_group",
+            field=sentry.db.models.fields.foreignkey.FlexibleForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.SET_NULL,
+                to="workflow_engine.dataconditiongroup",
+                unique=True,
+            ),
+        ),
+        migrations.AddField(
+            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",
+            ),
+        ),
+        migrations.CreateModel(
+            name="DataConditionGroupAction",
+            fields=[
+                (
+                    "id",
+                    sentry.db.models.fields.bounded.BoundedBigAutoField(
+                        primary_key=True, serialize=False
+                    ),
+                ),
+                ("date_updated", models.DateTimeField(auto_now=True)),
+                ("date_added", models.DateTimeField(auto_now_add=True)),
+                (
+                    "action",
+                    sentry.db.models.fields.foreignkey.FlexibleForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="workflow_engine.action"
+                    ),
+                ),
+                (
+                    "condition_group",
+                    sentry.db.models.fields.foreignkey.FlexibleForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="workflow_engine.dataconditiongroup",
+                    ),
+                ),
+            ],
+            options={
+                "abstract": False,
+            },
+        ),
+        migrations.CreateModel(
+            name="DetectorWorkflow",
+            fields=[
+                (
+                    "id",
+                    sentry.db.models.fields.bounded.BoundedBigAutoField(
+                        primary_key=True, serialize=False
+                    ),
+                ),
+                ("date_updated", models.DateTimeField(auto_now=True)),
+                ("date_added", models.DateTimeField(auto_now_add=True)),
+                (
+                    "detector",
+                    sentry.db.models.fields.foreignkey.FlexibleForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="workflow_engine.detector"
+                    ),
+                ),
+                (
+                    "workflow",
+                    sentry.db.models.fields.foreignkey.FlexibleForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="workflow_engine.workflow"
+                    ),
+                ),
+            ],
+            options={
+                "abstract": False,
+            },
+        ),
+        migrations.CreateModel(
+            name="WorkflowDataConditionGroup",
+            fields=[
+                (
+                    "id",
+                    sentry.db.models.fields.bounded.BoundedBigAutoField(
+                        primary_key=True, serialize=False
+                    ),
+                ),
+                ("date_updated", models.DateTimeField(auto_now=True)),
+                ("date_added", models.DateTimeField(auto_now_add=True)),
+                (
+                    "condition_group",
+                    sentry.db.models.fields.foreignkey.FlexibleForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="workflow_engine.dataconditiongroup",
+                        unique=True,
+                    ),
+                ),
+                (
+                    "workflow",
+                    sentry.db.models.fields.foreignkey.FlexibleForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="workflow_engine.workflow"
+                    ),
+                ),
+            ],
+            options={
+                "abstract": False,
+            },
+        ),
+    ]

Some files were not shown because too many files changed in this diff