mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: filter users by github user id in the users list CLI command (#17029)
Add the `--github-user-id` option to `coder users list`, which makes the command only return users with a matching GitHub user id. This will enable https://github.com/coder/start-workspace-action to find a Coder user that corresponds to a GitHub user requesting to start a workspace.
This commit is contained in:
@@ -6578,6 +6578,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 {
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
+11
-10
@@ -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{
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user