diff --git a/migrations_lockfile.txt b/migrations_lockfile.txt index 77523d6ec01735..38c7df916d9b03 100644 --- a/migrations_lockfile.txt +++ b/migrations_lockfile.txt @@ -10,7 +10,7 @@ hybridcloud: 0016_add_control_cacheversion nodestore: 0002_nodestore_no_dictfield remote_subscriptions: 0003_drop_remote_subscription replays: 0004_index_together -sentry: 0769_add_seer_fields_to_grouphash_metadata +sentry: 0770_increase_project_slug_max_length social_auth: 0002_default_auto_field uptime: 0014_add_uptime_enviromnet workflow_engine: 0007_loosen_workflow_action_relationship diff --git a/src/sentry/api/endpoints/organization_teams.py b/src/sentry/api/endpoints/organization_teams.py index 505c6bc3d76fb0..5b7f1f6bea4562 100644 --- a/src/sentry/api/endpoints/organization_teams.py +++ b/src/sentry/api/endpoints/organization_teams.py @@ -18,6 +18,7 @@ from sentry.apidocs.examples.team_examples import TeamExamples from sentry.apidocs.parameters import CursorQueryParam, GlobalParams, TeamParams from sentry.apidocs.utils import inline_sentry_response_serializer +from sentry.db.models.fields.slug import DEFAULT_SLUG_MAX_LENGTH from sentry.integrations.models.external_actor import ExternalActor from sentry.models.organizationmember import OrganizationMember from sentry.models.organizationmemberteam import OrganizationMemberTeam @@ -44,7 +45,7 @@ class TeamPostSerializer(serializers.Serializer): slug = SentrySerializerSlugField( help_text="""Uniquely identifies a team and is used for the interface. If not provided, it is automatically generated from the name.""", - max_length=50, + max_length=DEFAULT_SLUG_MAX_LENGTH, required=False, allow_null=True, ) diff --git a/src/sentry/api/endpoints/project_details.py b/src/sentry/api/endpoints/project_details.py index 8643183113771c..96e265e5eec1d2 100644 --- a/src/sentry/api/endpoints/project_details.py +++ b/src/sentry/api/endpoints/project_details.py @@ -45,7 +45,7 @@ ) from sentry.lang.native.utils import STORE_CRASH_REPORTS_MAX, convert_crashreport_count from sentry.models.group import Group, GroupStatus -from sentry.models.project import Project +from sentry.models.project import PROJECT_SLUG_MAX_LENGTH, Project from sentry.models.projectbookmark import ProjectBookmark from sentry.models.projectredirect import ProjectRedirect from sentry.notifications.utils import has_alert_integration @@ -135,7 +135,7 @@ class ProjectAdminSerializer(ProjectMemberSerializer): ) slug = SentrySerializerSlugField( help_text="Uniquely identifies a project and is used for the interface.", - max_length=50, + max_length=PROJECT_SLUG_MAX_LENGTH, required=False, ) platform = serializers.CharField( diff --git a/src/sentry/api/endpoints/team_details.py b/src/sentry/api/endpoints/team_details.py index 69ec7a73b26cdb..13c984e014afe5 100644 --- a/src/sentry/api/endpoints/team_details.py +++ b/src/sentry/api/endpoints/team_details.py @@ -23,6 +23,7 @@ ) from sentry.apidocs.examples.team_examples import TeamExamples from sentry.apidocs.parameters import GlobalParams, TeamParams +from sentry.db.models.fields.slug import DEFAULT_SLUG_MAX_LENGTH from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.models.team import Team, TeamStatus @@ -30,7 +31,7 @@ @extend_schema_serializer(exclude_fields=["name"]) class TeamDetailsSerializer(CamelSnakeModelSerializer): slug = SentrySerializerSlugField( - max_length=50, + max_length=DEFAULT_SLUG_MAX_LENGTH, help_text="Uniquely identifies a team. This is must be available.", ) diff --git a/src/sentry/api/endpoints/team_projects.py b/src/sentry/api/endpoints/team_projects.py index 73ce44a11de871..98fadbfd2a7142 100644 --- a/src/sentry/api/endpoints/team_projects.py +++ b/src/sentry/api/endpoints/team_projects.py @@ -22,7 +22,7 @@ from sentry.apidocs.parameters import CursorQueryParam, GlobalParams from sentry.apidocs.utils import inline_sentry_response_serializer from sentry.constants import RESERVED_PROJECT_SLUGS, ObjectStatus -from sentry.models.project import Project +from sentry.models.project import PROJECT_SLUG_MAX_LENGTH, Project from sentry.models.team import Team from sentry.seer.similarity.utils import project_is_seer_eligible from sentry.signals import project_created @@ -38,7 +38,7 @@ class ProjectPostSerializer(serializers.Serializer): slug = SentrySerializerSlugField( help_text="""Uniquely identifies a project and is used for the interface. If not provided, it is automatically generated from the name.""", - max_length=50, + max_length=PROJECT_SLUG_MAX_LENGTH, required=False, allow_null=True, ) diff --git a/src/sentry/api/fields/sentry_slug.py b/src/sentry/api/fields/sentry_slug.py index 6301e9483eaffc..24eecce61da290 100644 --- a/src/sentry/api/fields/sentry_slug.py +++ b/src/sentry/api/fields/sentry_slug.py @@ -4,6 +4,7 @@ from drf_spectacular.utils import extend_schema_field from rest_framework import serializers +from sentry.db.models.fields.slug import DEFAULT_SLUG_MAX_LENGTH from sentry.slug.errors import DEFAULT_SLUG_ERROR_MESSAGE, ORG_SLUG_ERROR_MESSAGE from sentry.slug.patterns import MIXED_SLUG_PATTERN, ORG_SLUG_PATTERN @@ -24,6 +25,7 @@ def __init__( self, error_messages=None, org_slug: bool = False, + max_length: int = DEFAULT_SLUG_MAX_LENGTH, *args, **kwargs, ): @@ -37,4 +39,6 @@ def __init__( pattern = ORG_SLUG_PATTERN error_messages["invalid"] = ORG_SLUG_ERROR_MESSAGE - super().__init__(pattern, error_messages=error_messages, *args, **kwargs) + super().__init__( + pattern, error_messages=error_messages, max_length=max_length, *args, **kwargs + ) diff --git a/src/sentry/api/serializers/models/organization.py b/src/sentry/api/serializers/models/organization.py index 0b4d6c72caaf19..8cba629e2739c2 100644 --- a/src/sentry/api/serializers/models/organization.py +++ b/src/sentry/api/serializers/models/organization.py @@ -51,6 +51,7 @@ UPTIME_AUTODETECTION, ObjectStatus, ) +from sentry.db.models.fields.slug import DEFAULT_SLUG_MAX_LENGTH from sentry.dynamic_sampling.tasks.common import get_organization_volume from sentry.dynamic_sampling.tasks.helpers.sliding_window import get_sliding_window_org_sample_rate from sentry.killswitches import killswitch_matches_context @@ -101,7 +102,7 @@ class BaseOrganizationSerializer(serializers.Serializer): # 3. cannot end with a dash slug = SentrySerializerSlugField( org_slug=True, - max_length=50, + max_length=DEFAULT_SLUG_MAX_LENGTH, ) def validate_slug(self, value: str) -> str: diff --git a/src/sentry/db/models/fields/slug.py b/src/sentry/db/models/fields/slug.py index fa435e4a930665..ebb57ea2efbbc2 100644 --- a/src/sentry/db/models/fields/slug.py +++ b/src/sentry/db/models/fields/slug.py @@ -3,6 +3,8 @@ from sentry.slug.validators import no_numeric_validator, org_slug_validator +DEFAULT_SLUG_MAX_LENGTH = 50 + class SentrySlugField(SlugField): default_validators = [*SlugField.default_validators, no_numeric_validator] diff --git a/src/sentry/migrations/0770_increase_project_slug_max_length.py b/src/sentry/migrations/0770_increase_project_slug_max_length.py new file mode 100644 index 00000000000000..c131b8b6fe76a2 --- /dev/null +++ b/src/sentry/migrations/0770_increase_project_slug_max_length.py @@ -0,0 +1,34 @@ +# Generated by Django 5.1.1 on 2024-09-30 19:46 + +from django.db import migrations + +import sentry.db.models.fields.slug +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 = True + + dependencies = [ + ("sentry", "0769_add_seer_fields_to_grouphash_metadata"), + ] + + operations = [ + migrations.AlterField( + model_name="project", + name="slug", + field=sentry.db.models.fields.slug.SentrySlugField(max_length=100, null=True), + ), + ] diff --git a/src/sentry/models/project.py b/src/sentry/models/project.py index 7959088256ab8f..8cadf8fad3a5a2 100644 --- a/src/sentry/models/project.py +++ b/src/sentry/models/project.py @@ -54,6 +54,7 @@ from sentry.users.models.user import User SENTRY_USE_SNOWFLAKE = getattr(settings, "SENTRY_USE_SNOWFLAKE", False) +PROJECT_SLUG_MAX_LENGTH = 100 # NOTE: # - When you modify this list, ensure that the platform IDs listed in "sentry/static/app/data/platforms.tsx" match. @@ -232,7 +233,7 @@ class Project(Model, PendingDeletionMixin): __relocation_scope__ = RelocationScope.Organization - slug = SentrySlugField(null=True) + slug = SentrySlugField(null=True, max_length=PROJECT_SLUG_MAX_LENGTH) # DEPRECATED do not use, prefer slug name = models.CharField(max_length=200) forced_color = models.CharField(max_length=6, null=True, blank=True) diff --git a/src/sentry/monitors/constants.py b/src/sentry/monitors/constants.py index 8f06d3589dff4a..fb19678754fc46 100644 --- a/src/sentry/monitors/constants.py +++ b/src/sentry/monitors/constants.py @@ -16,9 +16,6 @@ # being marked as missed DEFAULT_CHECKIN_MARGIN = 1 -# Enforced maximum length of the monitor slug -MAX_SLUG_LENGTH = 50 - class PermitCheckInStatus(Enum): ACCEPT = 0 diff --git a/src/sentry/monitors/models.py b/src/sentry/monitors/models.py index 01d40e872da4c6..017d0c9e1dbf49 100644 --- a/src/sentry/monitors/models.py +++ b/src/sentry/monitors/models.py @@ -31,13 +31,12 @@ sane_repr, ) from sentry.db.models.fields.hybrid_cloud_foreign_key import HybridCloudForeignKey -from sentry.db.models.fields.slug import SentrySlugField +from sentry.db.models.fields.slug import DEFAULT_SLUG_MAX_LENGTH, SentrySlugField from sentry.db.models.manager.base import BaseManager from sentry.db.models.utils import slugify_instance from sentry.locks import locks from sentry.models.environment import Environment from sentry.models.rule import Rule, RuleSource -from sentry.monitors.constants import MAX_SLUG_LENGTH from sentry.monitors.types import CrontabSchedule, IntervalSchedule from sentry.types.actor import Actor from sentry.utils.retries import TimedRetryPolicy @@ -296,7 +295,7 @@ def save(self, *args, **kwargs): self, self.name, organization_id=self.organization_id, - max_length=MAX_SLUG_LENGTH, + max_length=DEFAULT_SLUG_MAX_LENGTH, ) return super().save(*args, **kwargs) diff --git a/src/sentry/monitors/types.py b/src/sentry/monitors/types.py index 7a1832983c490f..50f17140da8558 100644 --- a/src/sentry/monitors/types.py +++ b/src/sentry/monitors/types.py @@ -8,7 +8,7 @@ from django.utils.text import slugify from sentry_kafka_schemas.schema_types.ingest_monitors_v1 import CheckIn -from sentry.monitors.constants import MAX_SLUG_LENGTH +from sentry.db.models.fields.slug import DEFAULT_SLUG_MAX_LENGTH class CheckinTrace(TypedDict): @@ -70,7 +70,7 @@ class CheckinItem: @cached_property def valid_monitor_slug(self): - return slugify(self.payload["monitor_slug"])[:MAX_SLUG_LENGTH].strip("-") + return slugify(self.payload["monitor_slug"])[:DEFAULT_SLUG_MAX_LENGTH].strip("-") @property def processing_key(self): diff --git a/src/sentry/monitors/validators.py b/src/sentry/monitors/validators.py index 931c497e6cead2..417540964f9fb2 100644 --- a/src/sentry/monitors/validators.py +++ b/src/sentry/monitors/validators.py @@ -16,7 +16,8 @@ from sentry.api.serializers.rest_framework.project import ProjectField from sentry.constants import ObjectStatus from sentry.db.models import BoundedPositiveIntegerField -from sentry.monitors.constants import MAX_SLUG_LENGTH, MAX_THRESHOLD, MAX_TIMEOUT +from sentry.db.models.fields.slug import DEFAULT_SLUG_MAX_LENGTH +from sentry.monitors.constants import MAX_THRESHOLD, MAX_TIMEOUT from sentry.monitors.models import CheckInStatus, Monitor, MonitorType, ScheduleType from sentry.monitors.schedule import get_next_schedule, get_prev_schedule from sentry.monitors.types import CrontabSchedule @@ -246,7 +247,7 @@ class MonitorValidator(CamelSnakeSerializer): help_text="Name of the monitor. Used for notifications.", ) slug = SentrySerializerSlugField( - max_length=MAX_SLUG_LENGTH, + max_length=DEFAULT_SLUG_MAX_LENGTH, required=False, help_text="Uniquely identifies your monitor within your organization. Changing this slug will require updates to any instrumented check-in calls.", )