From ab3d254c1d6999e4bacaf4b35107b014ffc059ca Mon Sep 17 00:00:00 2001 From: Hugo Dutka Date: Thu, 20 Mar 2025 13:24:15 +0000 Subject: [PATCH 1/6] query --- coderd/database/modelqueries.go | 1 + coderd/database/queries/users.sql | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index cc19de5132f37..c8c6ec2d968ec 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -393,6 +393,7 @@ func (q *sqlQuerier) GetAuthorizedUsers(ctx context.Context, arg GetUsersParams, arg.LastSeenAfter, arg.CreatedBefore, arg.CreatedAfter, + arg.GithubComUserID, arg.OffsetOpt, arg.LimitOpt, ) diff --git a/coderd/database/queries/users.sql b/coderd/database/queries/users.sql index 79f19c1784155..0c29cf723f7ef 100644 --- a/coderd/database/queries/users.sql +++ b/coderd/database/queries/users.sql @@ -223,6 +223,11 @@ WHERE created_at >= @created_after ELSE true END + AND CASE + WHEN @github_com_user_id :: bigint != 0 THEN + github_com_user_id = @github_com_user_id + ELSE true + END -- End of filters -- Authorize Filter clause will be injected below in GetAuthorizedUsers From 2e96d64388d69e9b38938af5ee973f63d2e198c7 Mon Sep 17 00:00:00 2001 From: Hugo Dutka Date: Thu, 20 Mar 2025 14:53:00 +0000 Subject: [PATCH 2/6] make gen --- coderd/database/queries.sql.go | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 4ec8f7d243b16..9d5ce07d908af 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -11632,29 +11632,35 @@ WHERE created_at >= $8 ELSE true END + AND CASE + WHEN $9 :: bigint != 0 THEN + github_com_user_id = $9 + ELSE true + END -- End of filters -- Authorize Filter clause will be injected below in GetAuthorizedUsers -- @authorize_filter ORDER BY -- Deterministic and consistent ordering of all users. This is to ensure consistent pagination. - LOWER(username) ASC OFFSET $9 + LOWER(username) ASC OFFSET $10 LIMIT -- A null limit means "no limit", so 0 means return all - NULLIF($10 :: int, 0) + NULLIF($11 :: int, 0) ` type GetUsersParams struct { - AfterID uuid.UUID `db:"after_id" json:"after_id"` - Search string `db:"search" json:"search"` - Status []UserStatus `db:"status" json:"status"` - RbacRole []string `db:"rbac_role" json:"rbac_role"` - LastSeenBefore time.Time `db:"last_seen_before" json:"last_seen_before"` - LastSeenAfter time.Time `db:"last_seen_after" json:"last_seen_after"` - CreatedBefore time.Time `db:"created_before" json:"created_before"` - CreatedAfter time.Time `db:"created_after" json:"created_after"` - OffsetOpt int32 `db:"offset_opt" json:"offset_opt"` - LimitOpt int32 `db:"limit_opt" json:"limit_opt"` + AfterID uuid.UUID `db:"after_id" json:"after_id"` + Search string `db:"search" json:"search"` + Status []UserStatus `db:"status" json:"status"` + RbacRole []string `db:"rbac_role" json:"rbac_role"` + LastSeenBefore time.Time `db:"last_seen_before" json:"last_seen_before"` + LastSeenAfter time.Time `db:"last_seen_after" json:"last_seen_after"` + CreatedBefore time.Time `db:"created_before" json:"created_before"` + CreatedAfter time.Time `db:"created_after" json:"created_after"` + GithubComUserID int64 `db:"github_com_user_id" json:"github_com_user_id"` + OffsetOpt int32 `db:"offset_opt" json:"offset_opt"` + LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } type GetUsersRow struct { @@ -11689,6 +11695,7 @@ func (q *sqlQuerier) GetUsers(ctx context.Context, arg GetUsersParams) ([]GetUse arg.LastSeenAfter, arg.CreatedBefore, arg.CreatedAfter, + arg.GithubComUserID, arg.OffsetOpt, arg.LimitOpt, ) From 5394f264e473d2a572cb29b9f77de0105172ff32 Mon Sep 17 00:00:00 2001 From: Hugo Dutka Date: Thu, 20 Mar 2025 17:03:55 +0000 Subject: [PATCH 3/6] impl GetUsers github user id filter --- coderd/database/dbmem/dbmem.go | 10 ++++++++++ coderd/httpapi/queryparams.go | 14 ++++++++++++++ coderd/searchquery/search.go | 15 ++++++++------- coderd/users.go | 21 +++++++++++---------- coderd/users_test.go | 28 ++++++++++++++++++++++++++++ 5 files changed, 71 insertions(+), 17 deletions(-) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index c9a4940419ad6..e470159960608 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -6577,6 +6577,16 @@ func (q *FakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams users = usersFilteredByLastSeen } + if params.GithubComUserID != 0 { + usersFilteredByGithubComUserID := make([]database.User, 0, len(users)) + for i, user := range users { + if user.GithubComUserID.Int64 == params.GithubComUserID { + usersFilteredByGithubComUserID = append(usersFilteredByGithubComUserID, users[i]) + } + } + users = usersFilteredByGithubComUserID + } + beforePageCount := len(users) if params.OffsetOpt > 0 { diff --git a/coderd/httpapi/queryparams.go b/coderd/httpapi/queryparams.go index 9eb5325eca53e..1d814b863a85f 100644 --- a/coderd/httpapi/queryparams.go +++ b/coderd/httpapi/queryparams.go @@ -82,6 +82,20 @@ func (p *QueryParamParser) Int(vals url.Values, def int, queryParam string) int return v } +func (p *QueryParamParser) Int64(vals url.Values, def int64, queryParam string) int64 { + v, err := parseQueryParam(p, vals, func(v string) (int64, error) { + return strconv.ParseInt(v, 10, 64) + }, def, queryParam) + if err != nil { + p.Errors = append(p.Errors, codersdk.ValidationError{ + Field: queryParam, + Detail: fmt.Sprintf("Query param %q must be a valid 64-bit integer: %s", queryParam, err.Error()), + }) + return 0 + } + return v +} + // PositiveInt32 function checks if the given value is 32-bit and positive. // // We can't use `uint32` as the value must be within the range <0,2147483647> diff --git a/coderd/searchquery/search.go b/coderd/searchquery/search.go index 103dc80601ad9..b31eca2206e18 100644 --- a/coderd/searchquery/search.go +++ b/coderd/searchquery/search.go @@ -80,13 +80,14 @@ func Users(query string) (database.GetUsersParams, []codersdk.ValidationError) { parser := httpapi.NewQueryParamParser() filter := database.GetUsersParams{ - Search: parser.String(values, "", "search"), - Status: httpapi.ParseCustomList(parser, values, []database.UserStatus{}, "status", httpapi.ParseEnum[database.UserStatus]), - RbacRole: parser.Strings(values, []string{}, "role"), - LastSeenAfter: parser.Time3339Nano(values, time.Time{}, "last_seen_after"), - LastSeenBefore: parser.Time3339Nano(values, time.Time{}, "last_seen_before"), - CreatedAfter: parser.Time3339Nano(values, time.Time{}, "created_after"), - CreatedBefore: parser.Time3339Nano(values, time.Time{}, "created_before"), + Search: parser.String(values, "", "search"), + Status: httpapi.ParseCustomList(parser, values, []database.UserStatus{}, "status", httpapi.ParseEnum[database.UserStatus]), + RbacRole: parser.Strings(values, []string{}, "role"), + LastSeenAfter: parser.Time3339Nano(values, time.Time{}, "last_seen_after"), + LastSeenBefore: parser.Time3339Nano(values, time.Time{}, "last_seen_before"), + CreatedAfter: parser.Time3339Nano(values, time.Time{}, "created_after"), + CreatedBefore: parser.Time3339Nano(values, time.Time{}, "created_before"), + GithubComUserID: parser.Int64(values, 0, "github_com_user_id"), } parser.ErrorExcessParams(values) return filter, parser.Errors diff --git a/coderd/users.go b/coderd/users.go index bbb10c4787a27..34969f363737c 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -297,16 +297,17 @@ func (api *API) GetUsers(rw http.ResponseWriter, r *http.Request) ([]database.Us } userRows, err := api.Database.GetUsers(ctx, database.GetUsersParams{ - AfterID: paginationParams.AfterID, - Search: params.Search, - Status: params.Status, - RbacRole: params.RbacRole, - LastSeenBefore: params.LastSeenBefore, - LastSeenAfter: params.LastSeenAfter, - CreatedAfter: params.CreatedAfter, - CreatedBefore: params.CreatedBefore, - OffsetOpt: int32(paginationParams.Offset), - LimitOpt: int32(paginationParams.Limit), + AfterID: paginationParams.AfterID, + Search: params.Search, + Status: params.Status, + RbacRole: params.RbacRole, + LastSeenBefore: params.LastSeenBefore, + LastSeenAfter: params.LastSeenAfter, + CreatedAfter: params.CreatedAfter, + CreatedBefore: params.CreatedBefore, + GithubComUserID: params.GithubComUserID, + OffsetOpt: int32(paginationParams.Offset), + LimitOpt: int32(paginationParams.Limit), }) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ diff --git a/coderd/users_test.go b/coderd/users_test.go index 2d85a9823a587..cbd7607701c1f 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -2,6 +2,7 @@ package coderd_test import ( "context" + "database/sql" "fmt" "net/http" "slices" @@ -1873,6 +1874,33 @@ func TestGetUsers(t *testing.T) { require.NoError(t, err) require.ElementsMatch(t, active, res.Users) }) + t.Run("GithubComUserID", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + client, db := coderdtest.NewWithDatabase(t, nil) + first := coderdtest.CreateFirstUser(t, client) + _ = dbgen.User(t, db, database.User{ + Email: "test2@coder.com", + Username: "test2", + }) + // nolint:gocritic // Unit test + err := db.UpdateUserGithubComUserID(dbauthz.AsSystemRestricted(ctx), database.UpdateUserGithubComUserIDParams{ + ID: first.UserID, + GithubComUserID: sql.NullInt64{ + Int64: 123, + Valid: true, + }, + }) + require.NoError(t, err) + res, err := client.Users(ctx, codersdk.UsersRequest{ + SearchQuery: "github_com_user_id:123", + }) + require.NoError(t, err) + require.Len(t, res.Users, 1) + require.Equal(t, res.Users[0].ID, first.UserID) + }) } func TestGetUsersPagination(t *testing.T) { From 4318781502e81c9120b6114657811628c5154100 Mon Sep 17 00:00:00 2001 From: Hugo Dutka Date: Thu, 20 Mar 2025 17:19:12 +0000 Subject: [PATCH 4/6] add github user id filter to the users list cli command --- cli/userlist.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/cli/userlist.go b/cli/userlist.go index ad567868799d7..48f27f83119a4 100644 --- a/cli/userlist.go +++ b/cli/userlist.go @@ -19,6 +19,7 @@ func (r *RootCmd) userList() *serpent.Command { cliui.JSONFormat(), ) client := new(codersdk.Client) + var githubUserID int64 cmd := &serpent.Command{ Use: "list", @@ -27,8 +28,23 @@ func (r *RootCmd) userList() *serpent.Command { serpent.RequireNArgs(0), r.InitClient(client), ), + Options: serpent.OptionSet{ + { + Name: "github-user-id", + Description: "Filter users by their GitHub user ID.", + Default: "", + Flag: "github-user-id", + Required: false, + Value: serpent.Int64Of(&githubUserID), + }, + }, Handler: func(inv *serpent.Invocation) error { - res, err := client.Users(inv.Context(), codersdk.UsersRequest{}) + req := codersdk.UsersRequest{} + if githubUserID != 0 { + req.Search = fmt.Sprintf("github_com_user_id:%d", githubUserID) + } + + res, err := client.Users(inv.Context(), req) if err != nil { return err } From 1f2b078c66015fa16a5f6fc28b034130da129f7a Mon Sep 17 00:00:00 2001 From: Hugo Dutka Date: Thu, 20 Mar 2025 17:40:19 +0000 Subject: [PATCH 5/6] make update-golden-files --- cli/testdata/coder_users_list_--help.golden | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/testdata/coder_users_list_--help.golden b/cli/testdata/coder_users_list_--help.golden index 33d52b1feb498..563ad76e1dc72 100644 --- a/cli/testdata/coder_users_list_--help.golden +++ b/cli/testdata/coder_users_list_--help.golden @@ -9,6 +9,9 @@ OPTIONS: -c, --column [id|username|email|created at|updated at|status] (default: username,email,created at,status) Columns to display in table output. + --github-user-id int + Filter users by their GitHub user ID. + -o, --output table|json (default: table) Output format. From d08dd3b0f5126684a4b825a81af18697c0718171 Mon Sep 17 00:00:00 2001 From: Hugo Dutka Date: Thu, 20 Mar 2025 18:09:28 +0000 Subject: [PATCH 6/6] make gen --- docs/reference/cli/users_list.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/reference/cli/users_list.md b/docs/reference/cli/users_list.md index 42adf1df8e2c1..9293ff13c923c 100644 --- a/docs/reference/cli/users_list.md +++ b/docs/reference/cli/users_list.md @@ -13,6 +13,14 @@ coder users list [flags] ## Options +### --github-user-id + +| | | +|------|------------------| +| Type | int | + +Filter users by their GitHub user ID. + ### -c, --column | | |