Skip to content

feat(seer): Add bulk-project Seer settings endpoint#115234

Open
srest2021 wants to merge 7 commits intosrest2021/CW-1285-singlefrom
srest2021/CW-1285-bulk
Open

feat(seer): Add bulk-project Seer settings endpoint#115234
srest2021 wants to merge 7 commits intosrest2021/CW-1285-singlefrom
srest2021/CW-1285-bulk

Conversation

@srest2021
Copy link
Copy Markdown
Member

@srest2021 srest2021 commented May 8, 2026

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/filter
  • PUT /api/0/organizations/{org}/seer/projects/ — bulk update across multiple projects

Both endpoints use the update_seer_project_settings helper from #115037 to translate high-level fields (agent, integrationId, stoppingPoint, scannerAutomation) into project options.

Supported filters (via query parameter)

Filter Operators Example
id =, !=, IN, NOT IN id:1, id:[1,2,3]
name (free text) =, != my-project
reposCount =, !=, >, <, >=, <= reposCount:>0
stoppingPoint =, !=, IN, NOT IN stoppingPoint:off
agent =, !=, IN, NOT IN agent:seer, !agent:cursor_background_agent

Supported sort fields (via sortBy parameter)

name, -name, reposCount, -reposCount, agent, -agent, stoppingPoint, -stoppingPoint

Example: GET paginated list with default sort

GET /api/0/organizations/sentry/seer/projects/
[
    {
        "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

GET /api/0/organizations/sentry/seer/projects/

{"query": "reposCount:>0 agent:seer", "sortBy": "-reposCount"}

Returns only Seer-agent projects with at least one repo, sorted by repo count descending.

Example: GET with free text search

GET /api/0/organizations/sentry/seer/projects/

{"query": "my-project"}

Matches against both project name and slug (case-insensitive).

Example: PUT bulk update with filter

PUT /api/0/organizations/sentry/seer/projects/

{"query": "agent:cursor_background_agent reposCount:>0", "stoppingPoint": "open_pr"}

Returns 204 No Content. Updates only projects matching the query.

Example: PUT bulk update all projects

PUT /api/0/organizations/sentry/seer/projects/

{"scannerAutomation": false}

Returns 204 No Content. With no query, updates all accessible projects.

@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 8, 2026

CW-1285

@github-actions github-actions Bot added Scope: Frontend Automatically applied to PRs that change frontend components Scope: Backend Automatically applied to PRs that change backend components labels May 8, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

🚨 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 #discuss-dev-infra channel.

@srest2021 srest2021 changed the base branch from master to srest2021/CW-1285-single May 8, 2026 21:12
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

📊 Type Coverage Diff

✅ No new type safety issues introduced. Coverage: 93.45%

@srest2021 srest2021 marked this pull request as ready for review May 8, 2026 23:06
@srest2021 srest2021 requested review from a team as code owners May 8, 2026 23:06
Comment on lines +199 to +207
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(),
),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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~296
  • src/sentry/seer/endpoints/project_seer_settings.py:209~217

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1d2a17f. Configure here.


projects = list(queryset)
for project in projects:
update_seer_project_settings(project, data)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants