Skip to content

Commit f117f34

Browse files
1st round of human review
* Renaming things from `cli-bug-prediction*` to `code-review-local*` * The killswtch for the feature was created but was not being used. Added to the endpint. * Resolving some mypy complaints ! Still no local tests end-to-end. There are probably bugs lurking
1 parent 508ad57 commit f117f34

7 files changed

Lines changed: 151 additions & 122 deletions

File tree

src/sentry/api/endpoints/organization_cli_bug_prediction.py

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424

2525

2626
@region_silo_endpoint
27-
class OrganizationCliBugPredictionEndpoint(OrganizationEndpoint):
27+
class OrganizationCodeReviewLocalEndpoint(OrganizationEndpoint):
2828
"""
29-
Handle CLI-initiated bug prediction requests.
29+
Handle local code review requests from sentry-cli.
3030
3131
Synchronously polls Seer and returns results.
3232
"""
@@ -39,7 +39,7 @@ class OrganizationCliBugPredictionEndpoint(OrganizationEndpoint):
3939

4040
def post(self, request: Request, organization: Organization) -> Response:
4141
"""
42-
Trigger bug prediction for a git diff from sentry-cli.
42+
Trigger local code review for a git diff from sentry-cli.
4343
4444
This endpoint:
4545
1. Validates the request (diff size, file count, etc.)
@@ -50,22 +50,29 @@ def post(self, request: Request, organization: Organization) -> Response:
5050
5151
Returns 200 with predictions on success, various error codes on failure.
5252
"""
53+
# Check if feature is globally enabled
54+
if not settings.CODE_REVIEW_LOCAL_ENABLED:
55+
return Response(
56+
{"detail": "Local code review is not enabled"},
57+
status=503,
58+
)
59+
5360
# Check feature flag
54-
if not features.has("organizations:cli-bug-prediction", organization):
61+
if not features.has("organizations:code-review-local", organization):
5562
return Response(
56-
{"detail": "CLI bug prediction is not enabled for this organization"},
63+
{"detail": "Local code review is not enabled for this organization"},
5764
status=403,
5865
)
5966

6067
# Rate limiting
61-
user_key = f"cli_bug_prediction:user:{request.user.id}"
62-
org_key = f"cli_bug_prediction:org:{organization.id}"
68+
user_key = f"code_review_local:user:{request.user.id}"
69+
org_key = f"code_review_local:org:{organization.id}"
6370

64-
user_limit, user_window = settings.CLI_BUG_PREDICTION_USER_RATE_LIMIT
65-
org_limit, org_window = settings.CLI_BUG_PREDICTION_ORG_RATE_LIMIT
71+
user_limit, user_window = settings.CODE_REVIEW_LOCAL_USER_RATE_LIMIT
72+
org_limit, org_window = settings.CODE_REVIEW_LOCAL_ORG_RATE_LIMIT
6673

6774
if ratelimits.backend.is_limited(user_key, limit=user_limit, window=user_window):
68-
metrics.incr("cli_bug_prediction.rate_limited", tags={"type": "user"})
75+
metrics.incr("code_review_local.rate_limited", tags={"type": "user"})
6976
return Response(
7077
{
7178
"detail": f"Rate limit exceeded. Maximum {user_limit} requests per {user_window // 3600} hour(s) per user"
@@ -74,7 +81,7 @@ def post(self, request: Request, organization: Organization) -> Response:
7481
)
7582

7683
if ratelimits.backend.is_limited(org_key, limit=org_limit, window=org_window):
77-
metrics.incr("cli_bug_prediction.rate_limited", tags={"type": "org"})
84+
metrics.incr("code_review_local.rate_limited", tags={"type": "org"})
7885
return Response(
7986
{
8087
"detail": f"Organization rate limit exceeded. Maximum {org_limit} requests per {org_window // 3600} hour(s)"
@@ -90,13 +97,8 @@ def post(self, request: Request, organization: Organization) -> Response:
9097
validated_data = serializer.validated_data
9198
repo_data = validated_data["repository"]
9299
diff = validated_data["diff"]
93-
current_branch = validated_data.get("current_branch")
94100
commit_message = validated_data.get("commit_message")
95101

96-
# Record rate limits
97-
ratelimits.backend.record(user_key, limit=user_limit, window=user_window)
98-
ratelimits.backend.record(org_key, limit=org_limit, window=org_window)
99-
100102
# Resolve repository
101103
try:
102104
repository = self._resolve_repository(
@@ -115,20 +117,23 @@ def post(self, request: Request, organization: Organization) -> Response:
115117

116118
# Log request
117119
logger.info(
118-
"cli_bug_prediction.request",
120+
"code_review_local.request",
119121
extra={
120122
"organization_id": organization.id,
121123
"user_id": request.user.id,
122124
"repository_id": repository.id,
123125
"diff_size_bytes": len(diff),
124-
"has_commit_message": commit_message is not None,
125-
"has_current_branch": current_branch is not None,
126126
},
127127
)
128128

129-
metrics.incr("cli_bug_prediction.request", tags={"org": organization.slug})
129+
metrics.incr("code_review_local.request", tags={"org": organization.slug})
130130

131131
# Trigger Seer
132+
# user.id is guaranteed to be non-None since this endpoint requires authentication
133+
user_id = request.user.id
134+
assert user_id is not None
135+
user_name = request.user.username or getattr(request.user, "email", None) or str(user_id)
136+
132137
try:
133138
trigger_response = trigger_cli_bug_prediction(
134139
repo_provider=repo_data["provider"],
@@ -139,35 +144,35 @@ def post(self, request: Request, organization: Organization) -> Response:
139144
diff=diff,
140145
organization_id=organization.id,
141146
organization_slug=organization.slug,
142-
user_id=request.user.id,
143-
user_name=request.user.username or request.user.email or str(request.user.id),
147+
user_id=user_id,
148+
user_name=user_name,
144149
commit_message=commit_message,
145150
)
146151
except (UrllibTimeoutError, MaxRetryError):
147152
logger.exception(
148-
"cli_bug_prediction.trigger.timeout",
153+
"code_review_local.trigger.timeout",
149154
extra={
150155
"organization_id": organization.id,
151156
"user_id": request.user.id,
152157
},
153158
)
154159
return Response(
155-
{"detail": "Bug prediction service is temporarily unavailable"}, status=503
160+
{"detail": "Code review service is temporarily unavailable"}, status=503
156161
)
157162
except ValueError:
158163
logger.exception(
159-
"cli_bug_prediction.trigger.error",
164+
"code_review_local.trigger.error",
160165
extra={
161166
"organization_id": organization.id,
162167
"user_id": request.user.id,
163168
},
164169
)
165-
return Response({"detail": "Failed to start bug prediction analysis"}, status=502)
170+
return Response({"detail": "Failed to start code review analysis"}, status=502)
166171

167172
run_id = trigger_response["run_id"]
168173

169174
logger.info(
170-
"cli_bug_prediction.seer_triggered",
175+
"code_review_local.seer_triggered",
171176
extra={
172177
"seer_run_id": run_id,
173178
"organization_id": organization.id,
@@ -179,19 +184,19 @@ def post(self, request: Request, organization: Organization) -> Response:
179184
try:
180185
final_response = self._poll_seer_for_results(
181186
run_id=run_id,
182-
timeout_seconds=settings.CLI_BUG_PREDICTION_TIMEOUT,
183-
poll_interval_seconds=settings.CLI_BUG_PREDICTION_POLL_INTERVAL,
187+
timeout_seconds=settings.CODE_REVIEW_LOCAL_TIMEOUT,
188+
poll_interval_seconds=settings.CODE_REVIEW_LOCAL_POLL_INTERVAL,
184189
)
185190
except TimeoutError:
186191
logger.exception(
187-
"cli_bug_prediction.timeout",
192+
"code_review_local.timeout",
188193
extra={
189194
"seer_run_id": run_id,
190195
"organization_id": organization.id,
191196
"user_id": request.user.id,
192197
},
193198
)
194-
metrics.incr("cli_bug_prediction.timeout")
199+
metrics.incr("code_review_local.timeout")
195200
return Response(
196201
{
197202
"detail": "Analysis exceeded maximum processing time (10 minutes). Please try again with a smaller diff."
@@ -202,23 +207,23 @@ def post(self, request: Request, organization: Organization) -> Response:
202207
# Seer returned error status
203208
status_code, error_code, error_message = self._map_seer_error_to_response(str(e))
204209
logger.exception(
205-
"cli_bug_prediction.seer_error",
210+
"code_review_local.seer_error",
206211
extra={
207212
"seer_run_id": run_id,
208213
"organization_id": organization.id,
209214
"user_id": request.user.id,
210215
"mapped_status": status_code,
211216
},
212217
)
213-
metrics.incr("cli_bug_prediction.seer_error", tags={"error_code": error_code})
218+
metrics.incr("code_review_local.seer_error", tags={"error_code": error_code})
214219
return Response({"detail": error_message}, status=status_code)
215220

216221
# Success
217222
predictions = final_response.get("predictions", [])
218223
diagnostics = final_response.get("diagnostics", {})
219224

220225
logger.info(
221-
"cli_bug_prediction.completed",
226+
"code_review_local.completed",
222227
extra={
223228
"seer_run_id": run_id,
224229
"organization_id": organization.id,
@@ -228,8 +233,8 @@ def post(self, request: Request, organization: Organization) -> Response:
228233
},
229234
)
230235

231-
metrics.incr("cli_bug_prediction.completed", tags={"status": "success"})
232-
metrics.incr("cli_bug_prediction.predictions", amount=len(predictions))
236+
metrics.incr("code_review_local.completed", tags={"status": "success"})
237+
metrics.incr("code_review_local.predictions", amount=len(predictions))
233238

234239
response_data = {
235240
"status": final_response.get("status"),
@@ -289,7 +294,7 @@ def _poll_seer_for_results(
289294

290295
attempt += 1
291296
logger.debug(
292-
"cli_bug_prediction.polling",
297+
"code_review_local.polling",
293298
extra={
294299
"seer_run_id": run_id,
295300
"attempt": attempt,
@@ -302,7 +307,7 @@ def _poll_seer_for_results(
302307
except (UrllibTimeoutError, MaxRetryError):
303308
# If status check times out, wait and retry
304309
logger.warning(
305-
"cli_bug_prediction.poll.timeout",
310+
"code_review_local.poll.timeout",
306311
extra={"seer_run_id": run_id, "attempt": attempt},
307312
)
308313
time.sleep(poll_interval_seconds)
@@ -362,4 +367,4 @@ def _map_seer_error_to_response(self, seer_error_message: str) -> tuple[int, str
362367
)
363368

364369
# Default to bad gateway for unknown Seer errors
365-
return (502, "bad_gateway", "Bug prediction service encountered an error")
370+
return (502, "bad_gateway", "Code review service encountered an error")

src/sentry/api/urls.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
OrganizationAuthTokenDetailsEndpoint,
1212
)
1313
from sentry.api.endpoints.organization_auth_tokens import OrganizationAuthTokensEndpoint
14-
from sentry.api.endpoints.organization_cli_bug_prediction import (
15-
OrganizationCliBugPredictionEndpoint,
16-
)
14+
from sentry.api.endpoints.organization_cli_bug_prediction import OrganizationCodeReviewLocalEndpoint
1715
from sentry.api.endpoints.organization_events_root_cause_analysis import (
1816
OrganizationEventsRootCauseAnalysisEndpoint,
1917
)
@@ -2402,9 +2400,9 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]:
24022400
name="sentry-api-0-organization-autofix-automation-settings",
24032401
),
24042402
re_path(
2405-
r"^(?P<organization_id_or_slug>[^/]+)/bug-prediction/cli-review/$",
2406-
OrganizationCliBugPredictionEndpoint.as_view(),
2407-
name="sentry-api-0-organization-cli-bug-prediction",
2403+
r"^(?P<organization_id_or_slug>[^/]+)/code-review/local-review/$",
2404+
OrganizationCodeReviewLocalEndpoint.as_view(),
2405+
name="sentry-api-0-organization-code-review-local",
24082406
),
24092407
re_path(
24102408
r"^(?P<organization_id_or_slug>[^/]+)/seer-rpc/(?P<method_name>\w+)/$",

src/sentry/conf/server.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2855,12 +2855,12 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
28552855
# For encrypting the access token for the GHE integration
28562856
SEER_GHE_ENCRYPT_KEY: str | None = os.getenv("SEER_GHE_ENCRYPT_KEY")
28572857

2858-
# CLI Bug Prediction
2859-
CLI_BUG_PREDICTION_ENABLED = True
2860-
CLI_BUG_PREDICTION_TIMEOUT = 600 # 10 minutes in seconds
2861-
CLI_BUG_PREDICTION_POLL_INTERVAL = 2 # seconds between Seer polls
2862-
CLI_BUG_PREDICTION_USER_RATE_LIMIT = (10, 3600) # 10 per hour
2863-
CLI_BUG_PREDICTION_ORG_RATE_LIMIT = (100, 3600) # 100 per hour
2858+
# Code Review Local (sentry-cli review command)
2859+
CODE_REVIEW_LOCAL_ENABLED = True
2860+
CODE_REVIEW_LOCAL_TIMEOUT = 600 # 10 minutes in seconds
2861+
CODE_REVIEW_LOCAL_POLL_INTERVAL = 2 # seconds between Seer polls
2862+
CODE_REVIEW_LOCAL_USER_RATE_LIMIT = (10, 3600) # 10 per hour
2863+
CODE_REVIEW_LOCAL_ORG_RATE_LIMIT = (100, 3600) # 100 per hour
28642864

28652865
# Used to validate RPC requests from the Overwatch service
28662866
OVERWATCH_RPC_SHARED_SECRET: list[str] | None = None

src/sentry/features/temporary.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ def register_temporary_features(manager: FeatureManager) -> None:
6868
manager.add("organizations:detailed-data-for-seer", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
6969
# Enable GenAI features such as Autofix and Issue Summary
7070
manager.add("organizations:autofix-seer-preferences", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
71-
# Enable CLI bug prediction for sentry-cli review command
72-
manager.add("organizations:cli-bug-prediction", OrganizationFeature, FeatureHandlerStrategy.INTERNAL, api_expose=False, default=False)
71+
# Enable local code review for sentry-cli review command
72+
manager.add("organizations:code-review-local", OrganizationFeature, FeatureHandlerStrategy.INTERNAL, api_expose=False, default=False)
7373
# Enables Route Preloading
7474
manager.add("organizations:route-intent-preloading", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
7575
# Enable Prevent AI code review to run per commit

src/sentry/seer/cli_bug_prediction.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# Connection pool for CLI bug prediction requests
1515
seer_cli_bug_prediction_connection_pool = connection_from_url(
1616
settings.SEER_DEFAULT_URL,
17-
timeout=settings.CLI_BUG_PREDICTION_TIMEOUT,
17+
timeout=settings.CODE_REVIEW_LOCAL_TIMEOUT,
1818
)
1919

2020

0 commit comments

Comments
 (0)