fix: add support for spaces in search & enable searching by display name in templates (#19552)

## Summary

In this pull request we're updating search to support queries with
spaces in addition to the `field:value` pattern that is currently
supported.

Additionally templates search now defaults to `display_name` (since
`display_name` is optional the search will fallback to `name`) when
searching without the `field:value` pattern

Closes: https://github.com/coder/coder/issues/14384

### Downsides with searching on `name` and `display_name`

Because the `name` field cannot include spaces, we end up in a situation
where including a space in the query will result in no results since the
query searches on both `name` AND `display_name`. In the following
example, we can see the results of searching by both `name` and
`display_name` on these templates:

| Name | Display Name |
| ------ | ------------- |
| docker | Docker Template |
| faketemplate | A Fake Template |
| azure | Fake Azure Template |
| anotherfake | Another Fake Template |
| azurefake | Another Fake Fake Azure Template |



https://github.com/user-attachments/assets/b0e0793e-e77d-46bc-9a42-d7cf4f8bd910

### Proposal: Search on `display_name` by default and allow for `name`
using the `field:value` pattern

If we remove `name` from the default template search, we're now able to
search with spaces on template `display_names`. Since `display_names`
are what users see in the templates list they might expect the search to
work this way.

Below is an example of `name` being removed from the default template
search.


https://github.com/user-attachments/assets/9aba5911-4960-4384-befb-08ea1acaa3ab

With this approach users would still be able to search on template names
by specifying `exact_name:foo`.

### Testing

Added additional test cases to ensure spaces were handled as expected in
combination with `field:value` patterns.
This commit is contained in:
Rafael Rodriguez
2025-09-08 17:13:27 -05:00
committed by GitHub
parent ff18499cb0
commit 1677a30a1d
5 changed files with 121 additions and 18 deletions
+36 -14
View File
@@ -12230,23 +12230,41 @@ WHERE
LOWER(t.name) = LOWER($3)
ELSE true
END
-- Filter by name, matching on substring
-- Filter by exact display name
AND CASE
WHEN $4 :: text != '' THEN
lower(t.name) ILIKE '%' || lower($4) || '%'
LOWER(t.display_name) = LOWER($4)
ELSE true
END
-- Filter by name, matching on substring
AND CASE
WHEN $5 :: text != '' THEN
lower(t.name) ILIKE '%' || lower($5) || '%'
ELSE true
END
-- Filter by display_name, matching on substring (fallback to name if display_name is empty)
AND CASE
WHEN $6 :: text != '' THEN
CASE
WHEN t.display_name IS NOT NULL AND t.display_name != '' THEN
lower(t.display_name) ILIKE '%' || lower($6) || '%'
ELSE
-- Remove spaces if present since 't.name' cannot have any spaces
lower(t.name) ILIKE '%' || REPLACE(lower($6), ' ', '') || '%'
END
ELSE true
END
-- Filter by ids
AND CASE
WHEN array_length($5 :: uuid[], 1) > 0 THEN
t.id = ANY($5)
WHEN array_length($7 :: uuid[], 1) > 0 THEN
t.id = ANY($7)
ELSE true
END
-- Filter by deprecated
AND CASE
WHEN $6 :: boolean IS NOT NULL THEN
WHEN $8 :: boolean IS NOT NULL THEN
CASE
WHEN $6 :: boolean THEN
WHEN $8 :: boolean THEN
t.deprecated != ''
ELSE
t.deprecated = ''
@@ -12255,27 +12273,27 @@ WHERE
END
-- Filter by has_ai_task in latest version
AND CASE
WHEN $7 :: boolean IS NOT NULL THEN
tv.has_ai_task = $7 :: boolean
WHEN $9 :: boolean IS NOT NULL THEN
tv.has_ai_task = $9 :: boolean
ELSE true
END
-- Filter by author_id
AND CASE
WHEN $8 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN
t.created_by = $8
WHEN $10 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN
t.created_by = $10
ELSE true
END
-- Filter by author_username
AND CASE
WHEN $9 :: text != '' THEN
t.created_by = (SELECT id FROM users WHERE lower(users.username) = lower($9) AND deleted = false)
WHEN $11 :: text != '' THEN
t.created_by = (SELECT id FROM users WHERE lower(users.username) = lower($11) AND deleted = false)
ELSE true
END
-- Filter by has_external_agent in latest version
AND CASE
WHEN $10 :: boolean IS NOT NULL THEN
tv.has_external_agent = $10 :: boolean
WHEN $12 :: boolean IS NOT NULL THEN
tv.has_external_agent = $12 :: boolean
ELSE true
END
-- Authorize Filter clause will be injected below in GetAuthorizedTemplates
@@ -12287,7 +12305,9 @@ type GetTemplatesWithFilterParams struct {
Deleted bool `db:"deleted" json:"deleted"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
ExactName string `db:"exact_name" json:"exact_name"`
ExactDisplayName string `db:"exact_display_name" json:"exact_display_name"`
FuzzyName string `db:"fuzzy_name" json:"fuzzy_name"`
FuzzyDisplayName string `db:"fuzzy_display_name" json:"fuzzy_display_name"`
IDs []uuid.UUID `db:"ids" json:"ids"`
Deprecated sql.NullBool `db:"deprecated" json:"deprecated"`
HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"`
@@ -12301,7 +12321,9 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate
arg.Deleted,
arg.OrganizationID,
arg.ExactName,
arg.ExactDisplayName,
arg.FuzzyName,
arg.FuzzyDisplayName,
pq.Array(arg.IDs),
arg.Deprecated,
arg.HasAITask,