feat(seer): Add bulk-project Seer settings endpoint#115234
feat(seer): Add bulk-project Seer settings endpoint#115234srest2021 wants to merge 7 commits intosrest2021/CW-1285-singlefrom
Conversation
|
🚨 Warning: This pull request contains Frontend and Backend changes! It's discouraged to make changes to Sentry's Frontend and Backend in a single pull request. The Frontend and Backend are not atomically deployed. If the changes are interdependent of each other, they must be separated into two pull requests and be made forward or backwards compatible, such that the Backend or Frontend can be safely deployed independently. Have questions? Please ask in the |
📊 Type Coverage Diff✅ No new type safety issues introduced. Coverage: 93.45% |
| stopping_point=Case( | ||
| When(_tuning=AutofixAutomationTuningSettings.OFF, then=Value(json.dumps("off"))), | ||
| default=Coalesce( | ||
| _project_option_subquery("sentry:seer_automated_run_stopping_point"), | ||
| Value(json.dumps(SEER_AUTOMATED_RUN_STOPPING_POINT_DEFAULT)), | ||
| output_field=LegacyTextJSONField(), | ||
| ), | ||
| output_field=LegacyTextJSONField(), | ||
| ), |
There was a problem hiding this comment.
Bug: Annotations like _tuning are created with Value(json.dumps(...), output_field=LegacyTextJSONField()), causing values to be double-encoded. This breaks filtering and sorting comparisons.
Severity: CRITICAL
Suggested Fix
Remove the explicit json.dumps() call when creating the annotations. Pass the raw Python objects directly to Value() (e.g., Value(AUTOFIX_AUTOMATION_TUNING_DEFAULT)) and let the output_field=LegacyTextJSONField() handle the serialization to JSON.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: src/sentry/seer/endpoints/project_seer_settings.py#L199-L207
Potential issue: In `_annotate_queryset`, annotations such as `_tuning`,
`stopping_point`, and `agent` are created using `Value(json.dumps(...))` with
`output_field=LegacyTextJSONField()`. The `LegacyTextJSONField`'s `get_prep_value`
method calls `json.dumps()` on the value, which has already been JSON-encoded. This
results in double-encoded values in the database query results (e.g., `"'\"off\"'"`
instead of `"'off'"`). Consequently, filtering in `_apply_search_filters` and sorting
logic using `Case` statements fail because they compare these double-encoded values
against single-encoded or raw string values. This renders the search and sort
functionality of the endpoint non-operational.
Also affects:
src/sentry/seer/endpoints/project_seer_settings.py:278~296src/sentry/seer/endpoints/project_seer_settings.py:209~217
Did we get this right? 👍 / 👎 to inform future reviews.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1d2a17f. Configure here.
| data = serializer.validated_data | ||
| search_query = data.pop("query") | ||
|
|
||
| accessible_projects = self.get_projects(request, organization) |
There was a problem hiding this comment.
PUT endpoint missing include_all_accessible flag
Medium Severity
The get handler calls self.get_projects(request, organization, include_all_accessible=True) but the put handler calls self.get_projects(request, organization) without include_all_accessible=True. Without this flag and no explicit project IDs in the request, get_projects defaults to filter_by_membership=True, returning only projects the user is a team member of — not all accessible projects. This creates an inconsistency where GET shows projects that PUT silently skips during bulk updates.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1d2a17f. Configure here.
|
|
||
| projects = list(queryset) | ||
| for project in projects: | ||
| update_seer_project_settings(project, data) |
There was a problem hiding this comment.
is there a way to do this with one call? i assume this makes database calls in a loop? or would it be very difficult since it has to do project options updates?
There was a problem hiding this comment.
Yeah these are all project options updates--the minimum I can do here is probably moving the transaction and the project lock out of the helper. If I bulk create the options I will still have to reload the cache for each project, but that's still better than what I have now. Will fix this when I get back :)


Fixes CW-1285
Depends on #115230
Adds a bulk/org-level endpoint for managing per-project Seer settings across multiple projects:
GET /api/0/organizations/{org}/seer/projects/— paginated list with search/sort/filterPUT /api/0/organizations/{org}/seer/projects/— bulk update across multiple projectsBoth endpoints use the
update_seer_project_settingshelper from #115037 to translate high-level fields (agent, integrationId, stoppingPoint, scannerAutomation) into project options.Supported filters (via
queryparameter)id=,!=,IN,NOT INid:1,id:[1,2,3]name(free text)=,!=my-projectreposCount=,!=,>,<,>=,<=reposCount:>0stoppingPoint=,!=,IN,NOT INstoppingPoint:offagent=,!=,IN,NOT INagent:seer,!agent:cursor_background_agentSupported sort fields (via
sortByparameter)name,-name,reposCount,-reposCount,agent,-agent,stoppingPoint,-stoppingPointExample: GET paginated list with default sort
[ { "projectId": "2", "projectSlug": "test-seer-settings", "agent": "seer", "integrationId": null, "stoppingPoint": "code_changes", "scannerAutomation": true, "reposCount": 1 }, { "projectId": "5", "projectSlug": "another-project", "agent": "cursor_background_agent", "integrationId": "42", "stoppingPoint": "open_pr", "scannerAutomation": false, "reposCount": 3 } ]Example: GET with search and sort
Returns only Seer-agent projects with at least one repo, sorted by repo count descending.
Example: GET with free text search
Matches against both project name and slug (case-insensitive).
Example: PUT bulk update with filter
Returns
204 No Content. Updates only projects matching the query.Example: PUT bulk update all projects
Returns
204 No Content. With no query, updates all accessible projects.