Skip to content

feat(coderd): support multi-value status, owner, and template workspace filters - #27909

Draft
jakehwll wants to merge 1 commit into
mainfrom
jakehwll/devex-424-backend-multi-value-filter-support-for-workspaces-status
Draft

feat(coderd): support multi-value status, owner, and template workspace filters#27909
jakehwll wants to merge 1 commit into
mainfrom
jakehwll/devex-424-backend-multi-value-filter-support-for-workspaces-status

Conversation

@jakehwll

@jakehwll jakehwll commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🤖 This PR was written by Coder Agents on behalf of Jake Howell.

Resolves DEVEX-424.

Problem

When the frontend sends multiple chips for the same filter key (e.g. status:running status:stopped), Go's URL parser produces {"status": ["running", "stopped"]} but parser.String silently keeps only the first value. The Workspaces list endpoint therefore drops the extra values, making multi-select filters appear broken even though the client serializes correctly.

Changes

The status, owner, and template filters on the Workspaces list endpoint now accept multiple values.

  • coderd/database/queries/workspaces.sql
    • status: the single-value @status :: text CASE becomes an @statuses :: text[] membership check. Each requested status is OR'd using the existing transition/job-status mapping. The raw job-status fallback excludes the running/succeeded job statuses so single-value semantics are preserved (e.g. running still means succeeded+start, not an in-progress build).
    • owner: @owner_username :: text becomes @owner_usernames :: text[] via owner_id = ANY(SELECT id FROM users WHERE lower(username) = ANY(@owner_usernames) AND deleted = false).
    • template: @template_name :: text becomes @template_names :: text[] via template_id = ANY(SELECT id FROM templates WHERE lower(name) = ANY(@template_names) AND deleted = false).
  • coderd/searchquery/search.go: status parses with ParseCustomList(..., ParseEnum[WorkspaceStatus]) (keeps per-value validation); owner/template use parser.Strings. Repeated keys and CSV both work.
  • coderd/workspaces.go: the owner:me shortcut now resolves any me entry to the requesting user's username so it composes with other owners via ANY(...).
  • coderd/database/modelqueries.go: wrap the three params with pq.Array(...) in GetAuthorizedWorkspaces.
  • Regenerated queries.sql.go via make gen. GetWorkspacesParams fields become Statuses []string, OwnerUsernames []string, TemplateNames []string (WorkspaceStatus is a Go string type, not a Postgres enum, so sqlc emits []string).

Scope note (backend only)

The ticket's Definition of Done includes a frontend multiSelect: true flip in WorkspacesPageView.tsx. That change depends on FilterSearchField, introduced by the blocker DEVEX-422, which is not yet on main (no component, no category definitions). This PR delivers the backend half named in the ticket title / need-backend label; the one-line frontend flip lands once DEVEX-422 merges.

dbmem was removed from the codebase (#18803), so the ticket's dbmem.go step no longer applies. Filter tests run against real Postgres.

Testing

  • coderd/searchquery/search_test.go: migrated expected values to slices; added multi-value owner/template/status cases (repeated keys + CSV) and an invalid-status case; normalized nil vs empty slices.
  • coderd/workspaces_test.go: added end-to-end MultipleStatuses, MultipleOwners, MultipleTemplates subtests (frontend → API → correct results), and kept single-value assertions to confirm no breaking change.
  • make gen, make fmt, golangci-lint, and the full pre-commit suite pass.
Implementation plan

Problem

When the frontend sends multiple chips for the same filter key (e.g. status:running status:stopped), Go's URL parser produces {"status": ["running", "stopped"]} but parser.String silently keeps only the first value. The Workspaces list endpoint therefore drops the extra values, making multi-select filters appear broken.

Scope (backend only)

The ticket's DoD includes a frontend multiSelect: true change in WorkspacesPageView.tsx, which depends on FilterSearchField from the blocker DEVEX-422 and is not yet on main. This PR delivers the backend half. dbmem was removed (#18803), so that ticket step no longer applies.

Changes

  1. coderd/database/queries/workspaces.sql (GetWorkspaces)
    • Status: @status :: text single-value CASE -> @statuses :: text[] membership; per-status OR using existing transition/job-status mapping; raw job-status fallback excludes running/succeeded so single-value semantics hold.
    • Owner: @owner_username -> @owner_usernames with owner_id = ANY(SELECT id FROM users WHERE lower(username) = ANY(...)).
    • Template: @template_name -> @template_names with template_id = ANY(SELECT id FROM templates WHERE lower(name) = ANY(...)).
  2. make gen regenerates queries.sql.go; struct fields become Statuses/OwnerUsernames/TemplateNames []string.
  3. coderd/database/modelqueries.go: wrap the three params with pq.Array(...).
  4. coderd/searchquery/search.go: ParseCustomList for status (validated) + parser.Strings for owner/template.
  5. coderd/workspaces.go: resolve owner:me entries to the requester's username.
  6. Tests: searchquery slice migration + multi-value cases; end-to-end querier/API multi-value cases; confirm single-value still works.

Validation

make gen, make fmt lint, go test ./coderd/searchquery/..., targeted go test ./coderd/ -run TestWorkspaceFilter....

…pace filters

Change the Workspaces list filter parsing, GetWorkspaces query, struct,
and authorized query args so status, owner, and template accept multiple
values (e.g. status:running status:stopped). Previously parser.String
kept only the first value, silently dropping the rest.

Status uses ParseCustomList to validate each value; owner and template
use parser.Strings. Repeated keys and CSV both work. Single-value URLs
continue to behave as before.

Backend only: the frontend multiSelect flip depends on FilterSearchField
(DEVEX-422), which is not yet on main.

Link: https://linear.app/codercom/issue/DEVEX-424
@linear-code

linear-code Bot commented Aug 6, 2026

Copy link
Copy Markdown

DEVEX-424

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant