Skip to content
Draft
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
3 changes: 0 additions & 3 deletions src/sentry/conf/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2856,11 +2856,8 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
SEER_GHE_ENCRYPT_KEY: str | None = os.getenv("SEER_GHE_ENCRYPT_KEY")

# Code Review Local (sentry-cli review command)
CODE_REVIEW_LOCAL_ENABLED = True
CODE_REVIEW_LOCAL_TIMEOUT = 600 # 10 minutes in seconds
CODE_REVIEW_LOCAL_POLL_INTERVAL = 2 # seconds between Seer polls
CODE_REVIEW_LOCAL_USER_RATE_LIMIT = (10, 3600) # 10 per hour
CODE_REVIEW_LOCAL_ORG_RATE_LIMIT = (100, 3600) # 100 per hour

# Used to validate RPC requests from the Overwatch service
OVERWATCH_RPC_SHARED_SECRET: list[str] | None = None
Expand Down
2 changes: 0 additions & 2 deletions src/sentry/features/temporary.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ def register_temporary_features(manager: FeatureManager) -> None:
manager.add("organizations:detailed-data-for-seer", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
# Enable GenAI features such as Autofix and Issue Summary
manager.add("organizations:autofix-seer-preferences", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enable local code review for sentry-cli review command
manager.add("organizations:code-review-local", OrganizationFeature, FeatureHandlerStrategy.INTERNAL, api_expose=False, default=False)
# Enables Route Preloading
manager.add("organizations:route-intent-preloading", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enable Prevent AI code review to run per commit
Expand Down
55 changes: 9 additions & 46 deletions src/sentry/seer/code_review/endpoints/code_review_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from urllib3.exceptions import MaxRetryError
from urllib3.exceptions import TimeoutError as UrllibTimeoutError

from sentry import features, ratelimits
from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import region_silo_endpoint
Expand Down Expand Up @@ -50,45 +49,6 @@

Returns 200 with predictions on success, various error codes on failure.
"""
# Check if feature is globally enabled
if not settings.CODE_REVIEW_LOCAL_ENABLED:
return Response(
{"detail": "Local code review is not enabled"},
status=503,
)

# Check feature flag
if not features.has("organizations:code-review-local", organization):
return Response(
{"detail": "Local code review is not enabled for this organization"},
status=403,
)

# Rate limiting
user_key = f"code_review_local:user:{request.user.id}"
org_key = f"code_review_local:org:{organization.id}"

user_limit, user_window = settings.CODE_REVIEW_LOCAL_USER_RATE_LIMIT
org_limit, org_window = settings.CODE_REVIEW_LOCAL_ORG_RATE_LIMIT

if ratelimits.backend.is_limited(user_key, limit=user_limit, window=user_window):
metrics.incr("code_review_local.rate_limited", tags={"type": "user"})
return Response(
{
"detail": f"Rate limit exceeded. Maximum {user_limit} requests per {user_window // 3600} hour(s) per user"
},
status=429,
)

if ratelimits.backend.is_limited(org_key, limit=org_limit, window=org_window):
metrics.incr("code_review_local.rate_limited", tags={"type": "org"})
return Response(
{
"detail": f"Organization rate limit exceeded. Maximum {org_limit} requests per {org_window // 3600} hour(s)"
},
status=429,
)

# Validate request
serializer = CodeReviewLocalRequestSerializer(data=request.data)
if not serializer.is_valid():
Expand All @@ -99,14 +59,17 @@
diff = validated_data["diff"]
commit_message = validated_data.get("commit_message")

# Parse repository name (already in "owner/repo" format)
full_repo_name = repo_data["name"]
owner, repo_name = full_repo_name.split("/")
provider = "github" # GitHub-only for PoC

# Resolve repository
# Repository names in the database are stored as "owner/name" (e.g., "getsentry/sentry")
full_repo_name = f"{repo_data['owner']}/{repo_data['name']}"
try:
repository = self._resolve_repository(
organization=organization,
repo_name=full_repo_name,
repo_provider=repo_data["provider"],
repo_provider=provider,
)
except Repository.DoesNotExist:
return Response(
Expand Down Expand Up @@ -138,9 +101,9 @@

try:
trigger_response = trigger_code_review_local(
repo_provider=repo_data["provider"],
repo_owner=repo_data["owner"],
repo_name=repo_data["name"],
repo_provider=provider,
repo_owner=owner,
repo_name=repo_name,
repo_external_id=repository.external_id or "",
base_commit_sha=repo_data["base_commit_sha"],
diff=diff,
Expand Down Expand Up @@ -173,7 +136,7 @@
# Include the error message from Seer if available
error_msg = str(e)
if "Seer error" in error_msg:
return Response({"detail": error_msg}, status=502)
return Response({"detail": "Failed to start code review analysis"}, status=502)
except Exception as e:
# Catch-all for unexpected errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@


class RepositoryInfoSerializer(serializers.Serializer):
owner = serializers.CharField(required=True)
name = serializers.CharField(required=True)
provider = serializers.CharField(required=True)
name = serializers.CharField(required=True) # "owner/repo" format
base_commit_sha = serializers.CharField(required=True, min_length=40, max_length=40)

def validate_name(self, value):
"""Validate repository name is in owner/repo format."""
if "/" not in value or value.count("/") != 1:
raise serializers.ValidationError("Repository name must be in 'owner/repo' format")
owner, repo = value.split("/")
if not owner or not repo:
raise serializers.ValidationError("Repository name must be in 'owner/repo' format")
return value

def validate_base_commit_sha(self, value):
"""Validate that base_commit_sha is a valid 40-character hex string"""
if not all(c in "0123456789abcdefABCDEF" for c in value):
Expand Down
Loading
Loading