Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions src/sentry/models/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

from django.contrib.postgres.fields.array import ArrayField
from django.db import models
from django.db.models.signals import pre_delete
from django.db.models.signals import post_save, pre_delete
from django.utils import timezone

from sentry.backup.dependencies import NormalizedModelName, get_model_name
from sentry.backup.sanitize import SanitizableField, Sanitizer
from sentry.backup.scopes import RelocationScope
from sentry.constants import ObjectStatus
from sentry.constants import DEFAULT_CODE_REVIEW_TRIGGERS, ObjectStatus
from sentry.db.models import (
BoundedBigIntegerField,
BoundedPositiveIntegerField,
Expand All @@ -24,6 +24,8 @@
rename_on_pending_deletion,
reset_pending_deletion_field_names,
)
from sentry.models.options.organization_option import OrganizationOption
from sentry.models.repositorysettings import RepositorySettings
from sentry.organizations.services.organization.service import organization_service
from sentry.signals import pending_delete
from sentry.users.services.user import RpcUser
Expand Down Expand Up @@ -186,3 +188,40 @@ def handle_exception(e):
sender=Repository,
weak=False,
)


def handle_auto_enable_code_review(instance: Repository) -> None:
"""
When a new repository is created, auto enable code review if applicable.
"""

SUPPORTED_PROVIDERS = {"integrations:github"}

if instance.provider not in SUPPORTED_PROVIDERS:
return

if OrganizationOption.objects.get_value(
organization=instance.organization_id,
key="sentry:auto_enable_code_review",
default=False,
):
triggers = OrganizationOption.objects.get_value(
organization=instance.organization_id,
key="sentry:default_code_review_triggers",
default=[],
)
Copy link
Member

@ryan953 ryan953 Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pointing out that we're only going to save triggers when the default is to enable code review. So we won't have extra rows representing repo's that might never have code-review enabled.

Therefore: If the default is Not to automatically enable code-review for a this repo, then we don't save a record into out RepositorySettings table.
Instead, later the user can still manually enable code-review for this repo, and at that time, in the UI, default trigger setting will be presented to the user. 💯

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup correct, that was my intent - good summary. I wanted to minimize the number of writes we're doing to this new table and err on the side of if there is no row, no user has ever actively chosen a setting.
I thought this paradigm would give us more flexibility in the future to change any programmatic default behavior

if not isinstance(triggers, list):
triggers = DEFAULT_CODE_REVIEW_TRIGGERS

RepositorySettings.objects.get_or_create(
repository_id=instance.id,
defaults={"enabled_code_review": True, "code_review_triggers": triggers},
)


def on_repository_created(sender, instance, created, **kwargs):
if created:
handle_auto_enable_code_review(instance)


post_save.connect(on_repository_created, sender=Repository, weak=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After talking through this a bit more with @kcons, I think we should move this to override the Repository model's save method, ensuring both of these models are created within a transaction. It's a bit safer, and it's a pattern we follow for our outboxes, which have similar concerns.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense to me. Making the changes now!

137 changes: 137 additions & 0 deletions tests/sentry/models/test_repository.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from django.core import mail

from sentry.constants import DEFAULT_CODE_REVIEW_TRIGGERS
from sentry.models.options.organization_option import OrganizationOption
from sentry.models.repository import Repository
from sentry.models.repositorysettings import RepositorySettings
from sentry.plugins.providers.dummy import DummyRepositoryProvider
from sentry.testutils.cases import TestCase
from sentry.testutils.helpers.features import with_feature
Expand Down Expand Up @@ -64,3 +67,137 @@ def test_generate_delete_fail_email(self) -> None:
assert msg.context["repo"] == self.repo
assert msg.context["error_message"] == "Test error message"
assert msg.context["provider_name"] == "Example"


class RepositoryCodeReviewSettingsTest(TestCase):
"""Tests for the post_save signal that creates RepositorySettings on repository creation."""

def test_no_settings_created_when_auto_enable_disabled(self):
"""When auto_enable_code_review is False, no RepositorySettings should be created."""
org = self.create_organization()

# Ensure auto_enable is not set (defaults to False)
repo = Repository.objects.create(
organization_id=org.id,
name="test-repo",
provider="integrations:github",
)

# No settings should be created
assert not RepositorySettings.objects.filter(repository=repo).exists()

def test_settings_created_when_auto_enable_enabled(self):
"""When auto_enable_code_review is True, RepositorySettings should be created."""
org = self.create_organization()

# Enable auto code review for the organization
OrganizationOption.objects.set_value(
organization=org,
key="sentry:auto_enable_code_review",
value=True,
)

repo = Repository.objects.create(
organization_id=org.id,
name="test-repo",
provider="integrations:github",
)

# Settings should be created with code review enabled
settings = RepositorySettings.objects.get(repository=repo)
assert settings.enabled_code_review is True
assert settings.code_review_triggers == []

def test_settings_created_with_triggers(self):
"""When triggers are configured, they should be copied to RepositorySettings."""
org = self.create_organization()

# Enable auto code review with triggers
OrganizationOption.objects.set_value(
organization=org,
key="sentry:auto_enable_code_review",
value=True,
)
OrganizationOption.objects.set_value(
organization=org,
key="sentry:default_code_review_triggers",
value=["on_new_commit", "on_ready_for_review"],
)

repo = Repository.objects.create(
organization_id=org.id,
name="test-repo",
provider="integrations:github",
)

settings = RepositorySettings.objects.get(repository=repo)
assert settings.enabled_code_review is True
assert settings.code_review_triggers == ["on_new_commit", "on_ready_for_review"]

def test_no_settings_for_unsupported_provider(self):
"""Repositories with unsupported providers should not get settings."""
org = self.create_organization()

# Enable auto code review
OrganizationOption.objects.set_value(
organization=org,
key="sentry:auto_enable_code_review",
value=True,
)

repo = Repository.objects.create(
organization_id=org.id,
name="test-repo",
provider="unsupported_provider",
)

# No settings should be created for unsupported provider
assert not RepositorySettings.objects.filter(repository=repo).exists()

def test_invalid_triggers_type_defaults_to_empty_list(self):
"""When triggers is not a list, it should default to empty list."""
org = self.create_organization()

OrganizationOption.objects.set_value(
organization=org,
key="sentry:auto_enable_code_review",
value=True,
)
# Set invalid triggers type (string instead of list)
OrganizationOption.objects.set_value(
organization=org,
key="sentry:default_code_review_triggers",
value="invalid_string",
)

repo = Repository.objects.create(
organization_id=org.id,
name="test-repo",
provider="integrations:github",
)

settings = RepositorySettings.objects.get(repository=repo)
assert settings.code_review_triggers == DEFAULT_CODE_REVIEW_TRIGGERS

def test_settings_not_duplicated_on_update(self):
"""Updating a repository should not create duplicate settings."""
org = self.create_organization()

OrganizationOption.objects.set_value(
organization=org,
key="sentry:auto_enable_code_review",
value=True,
)

repo = Repository.objects.create(
organization_id=org.id,
name="test-repo",
provider="integrations:github",
)

# Update the repository
repo.name = "updated-repo"
repo.save()

# Should still have exactly one settings record
assert RepositorySettings.objects.filter(repository=repo).count() == 1
Loading