From 2877168c2153d98f3c1fe40028a21d8713780a11 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Tue, 21 Jul 2026 19:06:47 +0000 Subject: [PATCH] feat: match display name in member search Free-text member search previously matched only username and email. Broaden the @search filter in GetUsers, PaginatedOrganizationMembers, and GetGroupMembersByGroupIDPaginated to also match the user's display name (users.name). This covers the Users, Organization Members, and Group Members pages, plus the server-filtered user pickers. Add display name to the client-filtered MemberAutocomplete keywords so the organization member picker matches display name locally. Refs DEVEX-484 Refs DEVEX-565 --- coderd/coderdtest/users.go | 18 +++++++++++++++--- coderd/database/queries.sql.go | 9 ++++++--- coderd/database/queries/groupmembers.sql | 3 ++- .../database/queries/organizationmembers.sql | 3 ++- coderd/database/queries/users.sql | 3 ++- docs/admin/users/index.md | 5 +++++ .../UserAutocomplete/UserAutocomplete.tsx | 7 ++++++- 7 files changed, 38 insertions(+), 10 deletions(-) diff --git a/coderd/coderdtest/users.go b/coderd/coderdtest/users.go index 6023b2b072dad..9b2130bcc7c57 100644 --- a/coderd/coderdtest/users.go +++ b/coderd/coderdtest/users.go @@ -401,7 +401,19 @@ func UsersFilter( Search: "a", }, FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { - return (strings.ContainsAny(u.Username, "aA") || strings.ContainsAny(u.Email, "aA")) + return (strings.ContainsAny(u.Username, "aA") || strings.ContainsAny(u.Email, "aA") || strings.ContainsAny(u.Name, "aA")) + }, + }, + { + Name: "DisplayNameSearch", + Filter: codersdk.UsersRequest{ + Search: "user", + }, + FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { + const term = "user" + return strings.Contains(strings.ToLower(u.Username), term) || + strings.Contains(strings.ToLower(u.Email), term) || + strings.Contains(strings.ToLower(u.Name), term) }, }, { @@ -470,7 +482,7 @@ func UsersFilter( FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { for _, r := range u.Roles { if r.Name == codersdk.RoleOwner { - return (strings.ContainsAny(u.Username, "iI") || strings.ContainsAny(u.Email, "iI")) && + return (strings.ContainsAny(u.Username, "iI") || strings.ContainsAny(u.Email, "iI") || strings.ContainsAny(u.Name, "iI")) && u.Status == codersdk.UserStatusActive } } @@ -485,7 +497,7 @@ func UsersFilter( FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { for _, r := range u.Roles { if r.Name == codersdk.RoleOwner { - return (strings.ContainsAny(u.Username, "iI") || strings.ContainsAny(u.Email, "iI")) && + return (strings.ContainsAny(u.Username, "iI") || strings.ContainsAny(u.Email, "iI") || strings.ContainsAny(u.Name, "iI")) && u.Status == codersdk.UserStatusActive } } diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 63dc190f4301e..eebfe8a74473a 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -14526,11 +14526,12 @@ WHERE ELSE true END -- Start filters - -- Filter by email or username + -- Filter by email, username, or name (display name) AND CASE WHEN $3 :: text != '' THEN ( user_email ILIKE concat('%', $3, '%') OR user_username ILIKE concat('%', $3, '%') + OR user_name ILIKE concat('%', $3, '%') ) ELSE true END @@ -19925,11 +19926,12 @@ WHERE organization_id = $2 ELSE true END - -- Filter by email or username + -- Filter by email, username, or name (display name) AND CASE WHEN $3 :: text != '' THEN ( users.email ILIKE concat('%', $3, '%') OR users.username ILIKE concat('%', $3, '%') + OR users.name ILIKE concat('%', $3, '%') ) ELSE true END @@ -30301,11 +30303,12 @@ WHERE ELSE true END -- Start filters - -- Filter by email or username + -- Filter by email, username, or name (display name) AND CASE WHEN $2 :: text != '' THEN ( email ILIKE concat('%', $2, '%') OR username ILIKE concat('%', $2, '%') + OR name ILIKE concat('%', $2, '%') ) ELSE true END diff --git a/coderd/database/queries/groupmembers.sql b/coderd/database/queries/groupmembers.sql index fd167d219a716..1be98155217c0 100644 --- a/coderd/database/queries/groupmembers.sql +++ b/coderd/database/queries/groupmembers.sql @@ -45,11 +45,12 @@ WHERE ELSE true END -- Start filters - -- Filter by email or username + -- Filter by email, username, or name (display name) AND CASE WHEN @search :: text != '' THEN ( user_email ILIKE concat('%', @search, '%') OR user_username ILIKE concat('%', @search, '%') + OR user_name ILIKE concat('%', @search, '%') ) ELSE true END diff --git a/coderd/database/queries/organizationmembers.sql b/coderd/database/queries/organizationmembers.sql index 78e7e3116327f..bf75d1e5d72ff 100644 --- a/coderd/database/queries/organizationmembers.sql +++ b/coderd/database/queries/organizationmembers.sql @@ -121,11 +121,12 @@ WHERE organization_id = @organization_id ELSE true END - -- Filter by email or username + -- Filter by email, username, or name (display name) AND CASE WHEN @search :: text != '' THEN ( users.email ILIKE concat('%', @search, '%') OR users.username ILIKE concat('%', @search, '%') + OR users.name ILIKE concat('%', @search, '%') ) ELSE true END diff --git a/coderd/database/queries/users.sql b/coderd/database/queries/users.sql index 3c79b4052256d..4a78ed5f1b677 100644 --- a/coderd/database/queries/users.sql +++ b/coderd/database/queries/users.sql @@ -472,11 +472,12 @@ WHERE ELSE true END -- Start filters - -- Filter by email or username + -- Filter by email, username, or name (display name) AND CASE WHEN @search :: text != '' THEN ( email ILIKE concat('%', @search, '%') OR username ILIKE concat('%', @search, '%') + OR name ILIKE concat('%', @search, '%') ) ELSE true END diff --git a/docs/admin/users/index.md b/docs/admin/users/index.md index 0a38fa8710abc..32707ab430281 100644 --- a/docs/admin/users/index.md +++ b/docs/admin/users/index.md @@ -194,6 +194,11 @@ to use the Coder's filter query: `login_type:github` - To find service accounts: `service_account:true`. +Any text that is not part of a `key:value` filter is treated as a free-text +search and matches against a user's username, email, and display name. For +example, entering `jane` returns users whose username, email, or display name +contains `jane`. + The following filters are supported: - `status` - Indicates the status of the user. It can be either `active`, diff --git a/site/src/components/UserAutocomplete/UserAutocomplete.tsx b/site/src/components/UserAutocomplete/UserAutocomplete.tsx index 7a0bf3f2b41da..b04f4251eb868 100644 --- a/site/src/components/UserAutocomplete/UserAutocomplete.tsx +++ b/site/src/components/UserAutocomplete/UserAutocomplete.tsx @@ -31,6 +31,7 @@ import { prepareQuery } from "#/utils/filters"; type SelectedUser = { avatar_url?: string; email?: string; + name?: string; username: string; }; @@ -233,7 +234,11 @@ const InnerAutocomplete = ({