diff --git a/migrations_lockfile.txt b/migrations_lockfile.txt index 741de05aa2ea89..4d4f1057a24e47 100644 --- a/migrations_lockfile.txt +++ b/migrations_lockfile.txt @@ -31,7 +31,7 @@ releases: 0004_cleanup_failed_safe_deletes replays: 0006_add_bulk_delete_job -sentry: 1007_cleanup_failed_safe_deletes +sentry: 1008_add_organizationcontributors_table social_auth: 0003_social_auth_json_field diff --git a/src/sentry/migrations/1008_add_organizationcontributors_table.py b/src/sentry/migrations/1008_add_organizationcontributors_table.py new file mode 100644 index 00000000000000..a48c3813744ed5 --- /dev/null +++ b/src/sentry/migrations/1008_add_organizationcontributors_table.py @@ -0,0 +1,72 @@ +# Generated by Django 5.2.8 on 2025-11-21 21:54 + +import django.db.models.deletion +from django.db import migrations, models + +import sentry.db.models.fields.bounded +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", "1007_cleanup_failed_safe_deletes"), + ] + + operations = [ + migrations.CreateModel( + name="OrganizationContributors", + fields=[ + ( + "id", + sentry.db.models.fields.bounded.BoundedBigAutoField( + primary_key=True, serialize=False + ), + ), + ( + "integration_id", + sentry.db.models.fields.hybrid_cloud_foreign_key.HybridCloudForeignKey( + "sentry.Integration", db_index=True, on_delete="CASCADE" + ), + ), + ("external_identifier", models.CharField(db_index=True, max_length=255)), + ("alias", models.CharField(blank=True, max_length=255, null=True)), + ("num_actions", sentry.db.models.fields.bounded.BoundedIntegerField(default=0)), + ("date_added", models.DateTimeField(auto_now_add=True)), + ("date_updated", models.DateTimeField(auto_now=True)), + ( + "organization", + sentry.db.models.fields.foreignkey.FlexibleForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="sentry.organization" + ), + ), + ], + options={ + "db_table": "sentry_organizationcontributors", + "indexes": [ + models.Index(fields=["date_updated"], name="sentry_orgcont_date_upd_idx") + ], + "constraints": [ + models.UniqueConstraint( + fields=("organization_id", "integration_id", "external_identifier"), + name="sentry_orgcont_unique_org_cont", + ) + ], + }, + ), + ] diff --git a/src/sentry/models/__init__.py b/src/sentry/models/__init__.py index c4219227436d2e..c34615e484f620 100644 --- a/src/sentry/models/__init__.py +++ b/src/sentry/models/__init__.py @@ -69,6 +69,7 @@ from .options import * # NOQA from .organization import * # NOQA from .organizationaccessrequest import * # NOQA +from .organizationcontributors import * # NOQA from .organizationmapping import * # NOQA from .organizationmember import * # NOQA from .organizationmemberinvite import * # NOQA diff --git a/src/sentry/models/organizationcontributors.py b/src/sentry/models/organizationcontributors.py new file mode 100644 index 00000000000000..f15113ea436be0 --- /dev/null +++ b/src/sentry/models/organizationcontributors.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +from django.db import models + +from sentry.backup.scopes import RelocationScope +from sentry.db.models import BoundedIntegerField, FlexibleForeignKey, Model, region_silo_model +from sentry.db.models.fields.hybrid_cloud_foreign_key import HybridCloudForeignKey + + +@region_silo_model +class OrganizationContributors(Model): + """ + Tracks external contributors and their activity for an organization. + This model stores information about contributors associated with an + integration for a specific organization, including their external identity + and how many actions they have taken. + """ + + __relocation_scope__ = RelocationScope.Excluded + + organization = FlexibleForeignKey("sentry.Organization", on_delete=models.CASCADE) + + integration_id = HybridCloudForeignKey("sentry.Integration", on_delete="CASCADE") + + external_identifier = models.CharField(max_length=255, db_index=True) + alias = models.CharField(max_length=255, null=True, blank=True) + num_actions = BoundedIntegerField(default=0) + date_added = models.DateTimeField(auto_now_add=True) + date_updated = models.DateTimeField(auto_now=True) + + class Meta: + app_label = "sentry" + db_table = "sentry_organizationcontributors" + constraints = [ + models.UniqueConstraint( + fields=["organization_id", "integration_id", "external_identifier"], + name="sentry_orgcont_unique_org_cont", + ), + ] + indexes = [ + models.Index( + fields=["date_updated"], + name="sentry_orgcont_date_upd_idx", + ), + ]