feat(coderd): add q search parameter to chats endpoint (#22913)

Replace the standalone `?archived=` query parameter on the chats listing
endpoint with a `?q=` search parameter, consistent with how workspaces,
tasks, templates, and other list endpoints work.

The `q` parameter uses the standard `key:value` search syntax parsed by
the `searchquery` package. Currently supports:

- `archived:true/false` (default: `false`, hides archived chats)

When `q` is empty or omits the archived filter, archived chats are
excluded by default. This is a behavioral change — the previous API
returned all chats (including archived) when no filter was specified.

### Changes

**Backend:**
- Add `searchquery.Chats()` parser following the same pattern as
`Tasks()`, `Workspaces()`, etc.
- Update `listChats` handler to read `q` instead of `archived`
- Update `codersdk.ListChatsOptions` to use `Q string` instead of
`Archived *bool`

**Frontend:**
- Update `getChats` API method to accept `q` parameter
- Update `infiniteChats` query to pass `q` instead of `archived`

**Tests:**
- Add `TestSearchChats` unit tests for the parser
- Update existing archive/unarchive integration tests to use `Q:
"archived:true"` syntax
This commit is contained in:
Kyle Carberry
2026-03-11 10:21:47 -04:00
committed by GitHub
parent bb59477648
commit 196c6702fd
8 changed files with 135 additions and 36 deletions
+30
View File
@@ -467,6 +467,36 @@ func Tasks(ctx context.Context, db database.Store, query string, actorID uuid.UU
return filter, parser.Errors
}
// Chats parses a search query for chats.
//
// Supported query parameters:
// - archived: boolean (default: false, excludes archived chats unless explicitly set)
func Chats(query string) (database.GetChatsByOwnerIDParams, []codersdk.ValidationError) {
filter := database.GetChatsByOwnerIDParams{
// Default to hiding archived chats.
Archived: sql.NullBool{Bool: false, Valid: true},
}
if query == "" {
return filter, nil
}
// Always lowercase for all searches.
query = strings.ToLower(query)
values, errors := searchTerms(query, func(term string, _ url.Values) error {
return xerrors.Errorf("unsupported search term: %q", term)
})
if len(errors) > 0 {
return filter, errors
}
parser := httpapi.NewQueryParamParser()
filter.Archived = parser.NullableBoolean(values, filter.Archived, "archived")
parser.ErrorExcessParams(values)
return filter, parser.Errors
}
func searchTerms(query string, defaultKey func(term string, values url.Values) error) (url.Values, []codersdk.ValidationError) {
searchValues := make(url.Values)
+72
View File
@@ -1215,3 +1215,75 @@ func TestSearchTasks(t *testing.T) {
})
}
}
func TestSearchChats(t *testing.T) {
t.Parallel()
testCases := []struct {
Name string
Query string
Expected database.GetChatsByOwnerIDParams
ExpectedErrorContains string
}{
{
Name: "Empty",
Query: "",
Expected: database.GetChatsByOwnerIDParams{
Archived: sql.NullBool{Bool: false, Valid: true},
},
},
{
Name: "ArchivedTrue",
Query: "archived:true",
Expected: database.GetChatsByOwnerIDParams{
Archived: sql.NullBool{Bool: true, Valid: true},
},
},
{
Name: "ArchivedFalse",
Query: "archived:false",
Expected: database.GetChatsByOwnerIDParams{
Archived: sql.NullBool{Bool: false, Valid: true},
},
},
{
Name: "ExtraParam",
Query: "archived:true invalid:param",
ExpectedErrorContains: "is not a valid query param",
},
{
Name: "ExtraColon",
Query: "archived:true:extra",
ExpectedErrorContains: "can only contain 1 ':'",
},
{
Name: "PrefixColon",
Query: ":archived",
ExpectedErrorContains: "cannot start or end with ':'",
},
{
Name: "SuffixColon",
Query: "archived:",
ExpectedErrorContains: "cannot start or end with ':'",
},
}
for _, c := range testCases {
t.Run(c.Name, func(t *testing.T) {
t.Parallel()
values, errs := searchquery.Chats(c.Query)
if c.ExpectedErrorContains != "" {
require.True(t, len(errs) > 0, "expect some errors")
var s strings.Builder
for _, err := range errs {
_, _ = s.WriteString(fmt.Sprintf("%s: %s\n", err.Field, err.Detail))
}
require.Contains(t, s.String(), c.ExpectedErrorContains)
} else {
require.Len(t, errs, 0, "expected no error")
require.Equal(t, c.Expected, values, "expected values")
}
})
}
}