Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion migrations_lockfile.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ releases: 0004_cleanup_failed_safe_deletes

replays: 0007_organizationmember_replay_access

sentry: 1015_backfill_self_hosted_sentry_app_emails
sentry: 1016_remove_on_command_phrase_trigger

social_auth: 0003_social_auth_json_field

Expand Down
1 change: 0 additions & 1 deletion src/sentry/apidocs/examples/organization_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,6 @@ class OrganizationExamples:
"defaultAutofixAutomationTuning": AutofixAutomationTuningSettings.OFF,
"autoEnableCodeReview": True,
"defaultCodeReviewTriggers": [
"on_command_phrase",
"on_ready_for_review",
"on_new_commit",
],
Expand Down
1 change: 0 additions & 1 deletion src/sentry/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,6 @@ class InsightModules(Enum):
AUTO_ENABLE_CODE_REVIEW = False
# Seer Org level default for code review triggers
DEFAULT_CODE_REVIEW_TRIGGERS: list[str] = [
"on_command_phrase",
"on_ready_for_review",
"on_new_commit",
]
Expand Down
4 changes: 1 addition & 3 deletions src/sentry/core/endpoints/organization_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,9 +369,7 @@ class OrganizationSerializer(BaseOrganizationSerializer):
autoOpenPrs = serializers.BooleanField(required=False)
autoEnableCodeReview = serializers.BooleanField(required=False)
defaultCodeReviewTriggers = serializers.ListField(
child=serializers.ChoiceField(
choices=["on_command_phrase", "on_ready_for_review", "on_new_commit"]
),
child=serializers.ChoiceField(choices=["on_ready_for_review", "on_new_commit"]),
required=False,
allow_empty=True,
help_text="The default code review triggers for new repositories.",
Expand Down
76 changes: 76 additions & 0 deletions src/sentry/migrations/1016_remove_on_command_phrase_trigger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Generated by Django 5.2.8 on 2026-01-08 18:18

import django.contrib.postgres.fields
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.migrations.state import StateApps

from sentry.new_migrations.migrations import CheckedMigration


def remove_on_command_phrase_from_repository_settings(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
"""Remove 'on_command_phrase' from RepositorySettings.code_review_triggers."""
RepositorySettings = apps.get_model("sentry", "RepositorySettings")

all_settings = list(RepositorySettings.objects.all())
Copy link
Member Author

Choose a reason for hiding this comment

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

This table is less than 200 rows, so should be safe to skip doing it post-deployment.


updated_settings = []
for settings in all_settings:
if (
settings.code_review_triggers
and isinstance(settings.code_review_triggers, list)
and "on_command_phrase" in settings.code_review_triggers
):
settings.code_review_triggers = [
trigger
for trigger in settings.code_review_triggers
if trigger != "on_command_phrase"
]
updated_settings.append(settings)

if updated_settings:
RepositorySettings.objects.bulk_update(updated_settings, fields=["code_review_triggers"])


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", "1015_backfill_self_hosted_sentry_app_emails"),
]

operations = [
migrations.RunPython(
remove_on_command_phrase_from_repository_settings,
reverse_code=migrations.RunPython.noop,
hints={"tables": ["sentry_repositorysettings"]},
),
migrations.AlterField(
model_name="repositorysettings",
name="code_review_triggers",
field=django.contrib.postgres.fields.ArrayField(
base_field=models.CharField(
choices=[
("on_new_commit", "on_new_commit"),
("on_ready_for_review", "on_ready_for_review"),
],
max_length=32,
),
default=list,
),
),
]
1 change: 0 additions & 1 deletion src/sentry/models/repositorysettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@


class CodeReviewTrigger(StrEnum):
ON_COMMAND_PHRASE = "on_command_phrase"
Copy link
Contributor

Choose a reason for hiding this comment

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

Uncleaned org options can propagate invalid triggers to new repos

Low Severity

The migration removes on_command_phrase from existing RepositorySettings but does not clean up organization options (sentry:default_code_review_triggers). If a new repository is created for an org that still has on_command_phrase in their org options, _maybe_auto_enable_code_review() in repository.py will propagate this value to the new RepositorySettings. When get_code_review_settings() is later called, it will raise a ValueError trying to convert "on_command_phrase" to the now-reduced CodeReviewTrigger enum. The PR discussion mentions manual cleanup of the ~5 affected orgs is planned, but if not completed before deployment, new repos for those orgs could trigger this error.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

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

This has now been cleaned up by hand in US and DE.

image image

ON_NEW_COMMIT = "on_new_commit"
ON_READY_FOR_REVIEW = "on_ready_for_review"

Expand Down
16 changes: 14 additions & 2 deletions src/sentry/seer/code_review/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from sentry.integrations.services.integration.model import RpcIntegration
from sentry.models.organization import Organization
from sentry.models.repository import Repository
from sentry.models.repositorysettings import CodeReviewTrigger
from sentry.net.http import connection_from_url
from sentry.seer.signed_seer_api import make_signed_seer_api_request

Expand All @@ -27,6 +26,19 @@ class ClientError(Exception):
pass


class SeerCodeReviewTrigger(StrEnum):
"""
Internal code review trigger type used for Seer flows.

This includes all user-configurable CodeReviewTrigger values, plus on_command_phrase,
which is always enabled and cannot be turned off by users.
"""

ON_COMMAND_PHRASE = "on_command_phrase"
ON_NEW_COMMIT = "on_new_commit"
ON_READY_FOR_REVIEW = "on_ready_for_review"


# These values need to match the value defined in the Seer API.
class SeerEndpoint(StrEnum):
# https://github.com/getsentry/seer/blob/main/src/seer/routes/automation_request.py#L57
Expand Down Expand Up @@ -194,7 +206,7 @@ def transform_webhook_to_codegen_request(
organization: Organization,
repo: Repository,
target_commit_sha: str,
trigger: CodeReviewTrigger,
trigger: SeerCodeReviewTrigger,
) -> dict[str, Any] | None:
"""
Transform a GitHub webhook payload into CodecovTaskRequest format for Seer.
Expand Down
5 changes: 2 additions & 3 deletions src/sentry/seer/code_review/webhooks/issue_comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from sentry.integrations.services.integration import RpcIntegration
from sentry.models.organization import Organization
from sentry.models.repository import Repository
from sentry.models.repositorysettings import CodeReviewTrigger

from ..metrics import (
CodeReviewErrorType,
Expand All @@ -24,7 +23,7 @@
record_webhook_handler_error,
record_webhook_received,
)
from ..utils import _get_target_commit_sha
from ..utils import SeerCodeReviewTrigger, _get_target_commit_sha
from .config import get_direct_to_seer_gh_orgs

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -153,6 +152,6 @@ def handle_issue_comment_event(
organization=organization,
repo=repo,
target_commit_sha=target_commit_sha,
trigger=CodeReviewTrigger.ON_COMMAND_PHRASE,
trigger=SeerCodeReviewTrigger.ON_COMMAND_PHRASE,
)
record_webhook_enqueued(github_event, github_event_action)
9 changes: 4 additions & 5 deletions src/sentry/seer/code_review/webhooks/pull_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from sentry.integrations.services.integration import RpcIntegration
from sentry.models.organization import Organization
from sentry.models.repository import Repository
from sentry.models.repositorysettings import CodeReviewTrigger

from ..metrics import (
CodeReviewErrorType,
Expand All @@ -24,7 +23,7 @@
record_webhook_handler_error,
record_webhook_received,
)
from ..utils import _get_target_commit_sha
from ..utils import SeerCodeReviewTrigger, _get_target_commit_sha
from .config import get_direct_to_seer_gh_orgs

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -74,12 +73,12 @@ class PullRequestAction(enum.StrEnum):
}


def _get_trigger_for_action(action: PullRequestAction) -> CodeReviewTrigger:
def _get_trigger_for_action(action: PullRequestAction) -> SeerCodeReviewTrigger:
match action:
case PullRequestAction.OPENED | PullRequestAction.READY_FOR_REVIEW:
return CodeReviewTrigger.ON_READY_FOR_REVIEW
return SeerCodeReviewTrigger.ON_READY_FOR_REVIEW
case PullRequestAction.SYNCHRONIZE:
return CodeReviewTrigger.ON_NEW_COMMIT
return SeerCodeReviewTrigger.ON_NEW_COMMIT
case _:
raise ValueError(f"Unsupported pull request action: {action}")

Expand Down
5 changes: 2 additions & 3 deletions src/sentry/seer/code_review/webhooks/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from sentry.integrations.github.webhook_types import GithubWebhookType
from sentry.models.organization import Organization
from sentry.models.repository import Repository
from sentry.models.repositorysettings import CodeReviewTrigger
from sentry.seer.code_review.utils import (
get_webhook_option_key,
transform_webhook_to_codegen_request,
Expand All @@ -24,7 +23,7 @@
from sentry.utils import metrics

from ..metrics import WebhookFilteredReason, record_webhook_filtered
from ..utils import get_seer_endpoint_for_event, make_seer_request
from ..utils import SeerCodeReviewTrigger, get_seer_endpoint_for_event, make_seer_request
from .config import get_direct_to_seer_gh_orgs

logger = logging.getLogger(__name__)
Expand All @@ -44,7 +43,7 @@ def schedule_task(
organization: Organization,
repo: Repository,
target_commit_sha: str,
trigger: CodeReviewTrigger,
trigger: SeerCodeReviewTrigger,
) -> None:
"""Transform and forward a webhook event to Seer for processing."""
from .task import process_github_webhook_event
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def test_bulk_update_existing_settings(self) -> None:
self.create_repository_settings(
repository=repo1,
enabled_code_review=False,
code_review_triggers=[CodeReviewTrigger.ON_COMMAND_PHRASE],
code_review_triggers=[CodeReviewTrigger.ON_READY_FOR_REVIEW],
)
self.create_repository_settings(
repository=repo2,
Expand Down Expand Up @@ -168,7 +168,7 @@ def test_partial_bulk_update_enabled_code_review_preserves_triggers(self) -> Non
repository=repo1,
enabled_code_review=False,
code_review_triggers=[
CodeReviewTrigger.ON_COMMAND_PHRASE,
CodeReviewTrigger.ON_READY_FOR_REVIEW,
CodeReviewTrigger.ON_NEW_COMMIT,
],
)
Expand All @@ -191,7 +191,7 @@ def test_partial_bulk_update_enabled_code_review_preserves_triggers(self) -> Non

settings1 = RepositorySettings.objects.get(repository=repo1)
assert settings1.enabled_code_review is True
assert settings1.code_review_triggers == ["on_command_phrase", "on_new_commit"]
assert settings1.code_review_triggers == ["on_ready_for_review", "on_new_commit"]

settings2 = RepositorySettings.objects.get(repository=repo2)
assert settings2.enabled_code_review is True
Expand All @@ -204,7 +204,7 @@ def test_partial_bulk_update_code_review_triggers_preserves_enabled(self) -> Non
self.create_repository_settings(
repository=repo1,
enabled_code_review=True,
code_review_triggers=[CodeReviewTrigger.ON_COMMAND_PHRASE],
code_review_triggers=[CodeReviewTrigger.ON_NEW_COMMIT],
)
self.create_repository_settings(
repository=repo2,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from sentry.models.repositorysettings import RepositorySettings
from sentry.testutils.cases import TestMigrations


class RemoveOnCommandPhraseTriggerTest(TestMigrations):
migrate_from = "1015_backfill_self_hosted_sentry_app_emails"
migrate_to = "1016_remove_on_command_phrase_trigger"

def setup_initial_state(self) -> None:
org = self.create_organization()
self.project1 = self.create_project(organization=org)
self.repo1 = self.create_repo(project=self.project1, name="org/repo1")
self.repo_settings1 = RepositorySettings.objects.create(
repository=self.repo1,
enabled_code_review=True,
code_review_triggers=["on_command_phrase", "on_ready_for_review", "on_new_commit"],
)

self.project2 = self.create_project(organization=org)
self.repo2 = self.create_repo(project=self.project2, name="org/repo2")
self.repo_settings2 = RepositorySettings.objects.create(
repository=self.repo2,
enabled_code_review=True,
code_review_triggers=["on_ready_for_review", "on_new_commit"],
)

self.project3 = self.create_project(organization=org)
self.repo3 = self.create_repo(project=self.project3, name="org/repo3")
self.repo_settings3 = RepositorySettings.objects.create(
repository=self.repo3, enabled_code_review=True
)

def test(self) -> None:
repo_settings = RepositorySettings.objects.get(id=self.repo_settings1.id)
assert repo_settings.code_review_triggers == ["on_ready_for_review", "on_new_commit"]
assert "on_command_phrase" not in repo_settings.code_review_triggers

repo_settings = RepositorySettings.objects.get(id=self.repo_settings2.id)
assert repo_settings.code_review_triggers == ["on_ready_for_review", "on_new_commit"]
assert "on_command_phrase" not in repo_settings.code_review_triggers

repo_settings = RepositorySettings.objects.get(id=self.repo_settings3.id)
assert repo_settings.code_review_triggers == []
6 changes: 3 additions & 3 deletions tests/sentry/models/test_repositorysettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

class TestCodeReviewSettings(TestCase):
def test_initialization(self) -> None:
triggers = [CodeReviewTrigger.ON_COMMAND_PHRASE, CodeReviewTrigger.ON_NEW_COMMIT]
triggers = [CodeReviewTrigger.ON_READY_FOR_REVIEW, CodeReviewTrigger.ON_NEW_COMMIT]
settings = CodeReviewSettings(enabled=True, triggers=triggers)

assert settings.enabled is True
Expand All @@ -38,7 +38,7 @@ def test_get_code_review_settings_with_enabled_and_triggers(self) -> None:
repository=self.repo,
enabled_code_review=True,
code_review_triggers=[
CodeReviewTrigger.ON_COMMAND_PHRASE.value,
CodeReviewTrigger.ON_NEW_COMMIT.value,
CodeReviewTrigger.ON_READY_FOR_REVIEW.value,
],
)
Expand All @@ -47,7 +47,7 @@ def test_get_code_review_settings_with_enabled_and_triggers(self) -> None:

assert settings.enabled is True
assert len(settings.triggers) == 2
assert CodeReviewTrigger.ON_COMMAND_PHRASE in settings.triggers
assert CodeReviewTrigger.ON_NEW_COMMIT in settings.triggers
assert CodeReviewTrigger.ON_READY_FOR_REVIEW in settings.triggers

def test_get_code_review_settings_converts_string_triggers_to_enum(self) -> None:
Expand Down
Loading
Loading