feat: add pr, repo, pr_title chat search filters (#25569)

Relates to CODAGT-432

Adds three new search filters to the chat list endpoint (`GET
/api/experimental/chats/`):

- `pr:<number>` - exact PR number match
- `repo:<owner/repo>` - substring match against git remote origin or URL
- `pr_title:<text>` - case-insensitive PR title substring match

Includes SQL filter clauses (EXISTS against `chat_diff_statuses`),
parser with validation, handler wiring, unit tests, swagger annotation
update, and a new search syntax documentation page.

> 🤖 Generated with [Coder Agents](https://coder.com/agents)
This commit is contained in:
Cian Johnston
2026-05-22 13:58:07 +01:00
committed by GitHub
parent 5deab9f721
commit 15ada66e14
12 changed files with 286 additions and 12 deletions
+19
View File
@@ -5,6 +5,7 @@ import (
"database/sql"
"fmt"
"net/url"
"strconv"
"strings"
"time"
@@ -553,6 +554,9 @@ func Tasks(ctx context.Context, db database.Store, query string, actorID uuid.UU
// - diff_url: string (matches chats whose linked diff URL equals the
// given value, case-insensitively; URLs typically contain ':' so
// they must be quoted, e.g. q=diff_url:"https://github.com/o/r/pull/1")
// - pr: positive integer (exact PR number match)
// - repo: string (case-insensitive substring match against git remote origin or URL)
// - pr_title: string (case-insensitive PR title substring match)
func Chats(query string) (database.GetChatsParams, []codersdk.ValidationError) {
filter := database.GetChatsParams{
// Default to hiding archived chats.
@@ -598,6 +602,21 @@ func Chats(query string) (database.GetChatsParams, []codersdk.ValidationError) {
}
filter.TitleQuery = parser.String(values, "", "title")
filter.PrTitleQuery = parser.String(values, "", "pr_title")
filter.RepoQuery = parser.String(values, "", "repo")
// pr: requires a positive integer.
if prStr := parser.String(values, "", "pr"); prStr != "" {
n, err := strconv.ParseInt(prStr, 10, 32)
if err != nil || n <= 0 {
parser.Errors = append(parser.Errors, codersdk.ValidationError{
Field: "pr",
Detail: fmt.Sprintf("%q is not a valid positive integer", prStr),
})
} else {
filter.PrNumber = int32(n)
}
}
parser.ErrorExcessParams(values)
return filter, parser.Errors
+49
View File
@@ -1459,6 +1459,55 @@ func TestSearchChats(t *testing.T) {
DiffURL: sql.NullString{String: "https://github.com/coder/coder/pull/456", Valid: true},
},
},
{
Name: "PrNumber",
Query: "pr:42",
Expected: database.GetChatsParams{
Archived: sql.NullBool{Bool: false, Valid: true},
PrNumber: 42,
},
},
{
Name: "PrNumberInvalid",
Query: "pr:abc",
ExpectedErrorContains: "pr",
},
{
Name: "PrNumberZero",
Query: "pr:0",
ExpectedErrorContains: "pr",
},
{
Name: "PrNumberNegative",
Query: "pr:-1",
ExpectedErrorContains: "pr",
},
{
Name: "RepoQuery",
Query: "repo:coder/coder",
Expected: database.GetChatsParams{
Archived: sql.NullBool{Bool: false, Valid: true},
RepoQuery: "coder/coder",
},
},
{
Name: "PrTitleQuery",
Query: `pr_title:"fix auth bug"`,
Expected: database.GetChatsParams{
Archived: sql.NullBool{Bool: false, Valid: true},
PrTitleQuery: "fix auth bug",
},
},
{
Name: "CombinedPRRepoTitle",
Query: "pr:99 repo:coder/coder pr_title:deploy",
Expected: database.GetChatsParams{
Archived: sql.NullBool{Bool: false, Valid: true},
PrNumber: 99,
RepoQuery: "coder/coder",
PrTitleQuery: "deploy",
},
},
{
Name: "BareTermsRejected",
Query: "some random words",