Skip to content

Commit 557a28e

Browse files
feat(code-review): Handle auto enable code review on repoCreated (#104666)
For seer launch, there's a toggle where an org can turn on auto-enable code review on repos. As in sentry org turns this toggle on, they add a new repo in github that has sentry github app installed on it, that means we should consider this repo enabled for code review. For new repos, they get written to `sentry_repository` table upon repositoryCreated gh webhook, or if the user manually adds the repo using sentry UI. Instead of doing this by finding every existing repo.objects.create (and remembering to do it if any new cases arise), probably most durable to add as a signal. There are downsides to using signals but it seems like a common pattern across sentry codebase to use these. Add that signal handler in this PR Closes https://linear.app/getsentry/issue/ENG-6094/handle-auto-enable-code-review-org-setting Depends on #104645
1 parent 45f0d9a commit 557a28e

File tree

2 files changed

+213
-2
lines changed

2 files changed

+213
-2
lines changed

src/sentry/models/repository.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
from typing import Any
44

55
from django.contrib.postgres.fields.array import ArrayField
6-
from django.db import models
6+
from django.db import models, router, transaction
77
from django.db.models.signals import pre_delete
88
from django.utils import timezone
99

1010
from sentry.backup.dependencies import NormalizedModelName, get_model_name
1111
from sentry.backup.sanitize import SanitizableField, Sanitizer
1212
from sentry.backup.scopes import RelocationScope
13-
from sentry.constants import ObjectStatus
13+
from sentry.constants import DEFAULT_CODE_REVIEW_TRIGGERS, ObjectStatus
1414
from sentry.db.models import (
1515
BoundedBigIntegerField,
1616
BoundedPositiveIntegerField,
@@ -24,6 +24,8 @@
2424
rename_on_pending_deletion,
2525
reset_pending_deletion_field_names,
2626
)
27+
from sentry.models.options.organization_option import OrganizationOption
28+
from sentry.models.repositorysettings import RepositorySettings
2729
from sentry.organizations.services.organization.service import organization_service
2830
from sentry.signals import pending_delete
2931
from sentry.users.services.user import RpcUser
@@ -147,6 +149,40 @@ def sanitize_relocation_json(
147149
sanitizer.set_string(json, SanitizableField(model_name, "provider"))
148150
json["fields"]["languages"] = "[]"
149151

152+
def save(self, *args: Any, **kwargs: Any) -> None:
153+
is_new = self.pk is None
154+
with transaction.atomic(router.db_for_write(Repository)):
155+
super().save(*args, **kwargs)
156+
if is_new:
157+
self._handle_auto_enable_code_review()
158+
159+
def _handle_auto_enable_code_review(self) -> None:
160+
"""
161+
When a new repository is created, auto enable code review if applicable.
162+
"""
163+
SUPPORTED_PROVIDERS = {"integrations:github"}
164+
165+
if self.provider not in SUPPORTED_PROVIDERS:
166+
return
167+
168+
if OrganizationOption.objects.get_value(
169+
organization=self.organization_id,
170+
key="sentry:auto_enable_code_review",
171+
default=False,
172+
):
173+
triggers = OrganizationOption.objects.get_value(
174+
organization=self.organization_id,
175+
key="sentry:default_code_review_triggers",
176+
default=DEFAULT_CODE_REVIEW_TRIGGERS,
177+
)
178+
if not isinstance(triggers, list):
179+
triggers = DEFAULT_CODE_REVIEW_TRIGGERS
180+
181+
RepositorySettings.objects.get_or_create(
182+
repository_id=self.id,
183+
defaults={"enabled_code_review": True, "code_review_triggers": triggers},
184+
)
185+
150186

151187
def on_delete(instance, actor: RpcUser | None = None, **kwargs):
152188
"""

tests/sentry/models/test_repository.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
from unittest.mock import patch
2+
3+
import pytest
14
from django.core import mail
25

6+
from sentry.constants import DEFAULT_CODE_REVIEW_TRIGGERS
7+
from sentry.models.options.organization_option import OrganizationOption
38
from sentry.models.repository import Repository
9+
from sentry.models.repositorysettings import RepositorySettings
410
from sentry.plugins.providers.dummy import DummyRepositoryProvider
511
from sentry.testutils.cases import TestCase
612
from sentry.testutils.helpers.features import with_feature
@@ -64,3 +70,172 @@ def test_generate_delete_fail_email(self) -> None:
6470
assert msg.context["repo"] == self.repo
6571
assert msg.context["error_message"] == "Test error message"
6672
assert msg.context["provider_name"] == "Example"
73+
74+
75+
class RepositoryCodeReviewSettingsTest(TestCase):
76+
"""Tests for auto-enabling code review settings on repository creation."""
77+
78+
def test_no_settings_created_when_auto_enable_disabled(self):
79+
org = self.create_organization()
80+
81+
repo = Repository.objects.create(
82+
organization_id=org.id,
83+
name="test-repo",
84+
provider="integrations:github",
85+
)
86+
87+
assert not RepositorySettings.objects.filter(repository=repo).exists()
88+
89+
def test_settings_created_when_auto_enable_enabled(self):
90+
org = self.create_organization()
91+
92+
OrganizationOption.objects.set_value(
93+
organization=org,
94+
key="sentry:auto_enable_code_review",
95+
value=True,
96+
)
97+
98+
repo = Repository.objects.create(
99+
organization_id=org.id,
100+
name="test-repo",
101+
provider="integrations:github",
102+
)
103+
104+
settings = RepositorySettings.objects.get(repository=repo)
105+
assert settings.enabled_code_review is True
106+
assert settings.code_review_triggers == DEFAULT_CODE_REVIEW_TRIGGERS
107+
108+
def test_settings_created_with_triggers(self):
109+
org = self.create_organization()
110+
111+
OrganizationOption.objects.set_value(
112+
organization=org,
113+
key="sentry:auto_enable_code_review",
114+
value=True,
115+
)
116+
OrganizationOption.objects.set_value(
117+
organization=org,
118+
key="sentry:default_code_review_triggers",
119+
value=["on_new_commit", "on_ready_for_review"],
120+
)
121+
122+
repo = Repository.objects.create(
123+
organization_id=org.id,
124+
name="test-repo",
125+
provider="integrations:github",
126+
)
127+
128+
settings = RepositorySettings.objects.get(repository=repo)
129+
assert settings.enabled_code_review is True
130+
assert settings.code_review_triggers == ["on_new_commit", "on_ready_for_review"]
131+
132+
def test_no_settings_for_unsupported_provider(self):
133+
org = self.create_organization()
134+
135+
OrganizationOption.objects.set_value(
136+
organization=org,
137+
key="sentry:auto_enable_code_review",
138+
value=True,
139+
)
140+
141+
repo = Repository.objects.create(
142+
organization_id=org.id,
143+
name="test-repo",
144+
provider="unsupported_provider",
145+
)
146+
147+
assert not RepositorySettings.objects.filter(repository=repo).exists()
148+
149+
def test_invalid_triggers_type_defaults_to_empty_list(self):
150+
org = self.create_organization()
151+
152+
OrganizationOption.objects.set_value(
153+
organization=org,
154+
key="sentry:auto_enable_code_review",
155+
value=True,
156+
)
157+
# Set invalid triggers type (string instead of list)
158+
OrganizationOption.objects.set_value(
159+
organization=org,
160+
key="sentry:default_code_review_triggers",
161+
value="invalid_string",
162+
)
163+
164+
repo = Repository.objects.create(
165+
organization_id=org.id,
166+
name="test-repo",
167+
provider="integrations:github",
168+
)
169+
170+
settings = RepositorySettings.objects.get(repository=repo)
171+
assert settings.code_review_triggers == DEFAULT_CODE_REVIEW_TRIGGERS
172+
173+
def test_settings_not_duplicated_on_update(self):
174+
org = self.create_organization()
175+
176+
OrganizationOption.objects.set_value(
177+
organization=org,
178+
key="sentry:auto_enable_code_review",
179+
value=True,
180+
)
181+
182+
repo = Repository.objects.create(
183+
organization_id=org.id,
184+
name="test-repo",
185+
provider="integrations:github",
186+
)
187+
188+
repo.name = "updated-repo"
189+
repo.save()
190+
191+
assert RepositorySettings.objects.filter(repository=repo).count() == 1
192+
193+
def test_transaction_rollback_on_auto_enable_failure(self):
194+
org = self.create_organization()
195+
196+
OrganizationOption.objects.set_value(
197+
organization=org,
198+
key="sentry:auto_enable_code_review",
199+
value=True,
200+
)
201+
202+
initial_repo_count = Repository.objects.filter(organization_id=org.id).count()
203+
204+
with patch.object(
205+
Repository,
206+
"_handle_auto_enable_code_review",
207+
side_effect=Exception("Test exception"),
208+
):
209+
with pytest.raises(Exception, match="Test exception"):
210+
Repository.objects.create(
211+
organization_id=org.id,
212+
name="test-repo",
213+
provider="integrations:github",
214+
)
215+
216+
# Neither Repository nor RepositorySettings should be saved due to transaction rollback
217+
assert Repository.objects.filter(organization_id=org.id).count() == initial_repo_count
218+
assert not RepositorySettings.objects.filter(repository__organization_id=org.id).exists()
219+
220+
def test_both_repository_and_settings_saved_atomically(self):
221+
org = self.create_organization()
222+
223+
OrganizationOption.objects.set_value(
224+
organization=org,
225+
key="sentry:auto_enable_code_review",
226+
value=True,
227+
)
228+
229+
repo = Repository.objects.create(
230+
organization_id=org.id,
231+
name="test-repo",
232+
provider="integrations:github",
233+
)
234+
235+
# Both should exist
236+
assert Repository.objects.filter(id=repo.id).exists()
237+
assert RepositorySettings.objects.filter(repository=repo).exists()
238+
239+
# Verify the settings are correct
240+
settings = RepositorySettings.objects.get(repository=repo)
241+
assert settings.enabled_code_review is True

0 commit comments

Comments
 (0)