Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0e65821

Browse files
authored
feat: support filtering users table by login type (#17238)
#15896 Mentions ability to add support for filtering by login type The issue mentions that backend API support exists but the backend did not seem to have the support for this filter. So I have added the ability to filter it. I also added a corresponding update to readme file to make sure the docs will correctly showcase this feature
1 parent f2d24bc commit 0e65821

File tree

10 files changed

+226
-24
lines changed

10 files changed

+226
-24
lines changed

coderd/database/dbmem/dbmem.go

+12
Original file line numberDiff line numberDiff line change
@@ -6824,6 +6824,18 @@ func (q *FakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams
68246824
users = usersFilteredByRole
68256825
}
68266826

6827+
if len(params.LoginType) > 0 {
6828+
usersFilteredByLoginType := make([]database.User, 0, len(users))
6829+
for i, user := range users {
6830+
if slice.ContainsCompare(params.LoginType, user.LoginType, func(a, b database.LoginType) bool {
6831+
return strings.EqualFold(string(a), string(b))
6832+
}) {
6833+
usersFilteredByLoginType = append(usersFilteredByLoginType, users[i])
6834+
}
6835+
}
6836+
users = usersFilteredByLoginType
6837+
}
6838+
68276839
if !params.CreatedBefore.IsZero() {
68286840
usersFilteredByCreatedAt := make([]database.User, 0, len(users))
68296841
for i, user := range users {

coderd/database/modelqueries.go

+1
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ func (q *sqlQuerier) GetAuthorizedUsers(ctx context.Context, arg GetUsersParams,
395395
arg.CreatedAfter,
396396
arg.IncludeSystem,
397397
arg.GithubComUserID,
398+
pq.Array(arg.LoginType),
398399
arg.OffsetOpt,
399400
arg.LimitOpt,
400401
)

coderd/database/queries.sql.go

+10-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/users.sql

+6
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,12 @@ WHERE
260260
github_com_user_id = @github_com_user_id
261261
ELSE true
262262
END
263+
-- Filter by login_type
264+
AND CASE
265+
WHEN cardinality(@login_type :: login_type[]) > 0 THEN
266+
login_type = ANY(@login_type :: login_type[])
267+
ELSE true
268+
END
263269
-- End of filters
264270

265271
-- Authorize Filter clause will be injected below in GetAuthorizedUsers

coderd/searchquery/search.go

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func Users(query string) (database.GetUsersParams, []codersdk.ValidationError) {
8888
CreatedAfter: parser.Time3339Nano(values, time.Time{}, "created_after"),
8989
CreatedBefore: parser.Time3339Nano(values, time.Time{}, "created_before"),
9090
GithubComUserID: parser.Int64(values, 0, "github_com_user_id"),
91+
LoginType: httpapi.ParseCustomList(parser, values, []database.LoginType{}, "login_type", httpapi.ParseEnum[database.LoginType]),
9192
}
9293
parser.ErrorExcessParams(values)
9394
return filter, parser.Errors

coderd/searchquery/search_test.go

+67-21
Original file line numberDiff line numberDiff line change
@@ -386,72 +386,118 @@ func TestSearchUsers(t *testing.T) {
386386
Name: "Empty",
387387
Query: "",
388388
Expected: database.GetUsersParams{
389-
Status: []database.UserStatus{},
390-
RbacRole: []string{},
389+
Status: []database.UserStatus{},
390+
RbacRole: []string{},
391+
LoginType: []database.LoginType{},
391392
},
392393
},
393394
{
394395
Name: "Username",
395396
Query: "user-name",
396397
Expected: database.GetUsersParams{
397-
Search: "user-name",
398-
Status: []database.UserStatus{},
399-
RbacRole: []string{},
398+
Search: "user-name",
399+
Status: []database.UserStatus{},
400+
RbacRole: []string{},
401+
LoginType: []database.LoginType{},
400402
},
401403
},
402404
{
403405
Name: "UsernameWithSpaces",
404406
Query: " user-name ",
405407
Expected: database.GetUsersParams{
406-
Search: "user-name",
407-
Status: []database.UserStatus{},
408-
RbacRole: []string{},
408+
Search: "user-name",
409+
Status: []database.UserStatus{},
410+
RbacRole: []string{},
411+
LoginType: []database.LoginType{},
409412
},
410413
},
411414
{
412415
Name: "Username+Param",
413416
Query: "usEr-name stAtus:actiVe",
414417
Expected: database.GetUsersParams{
415-
Search: "user-name",
416-
Status: []database.UserStatus{database.UserStatusActive},
417-
RbacRole: []string{},
418+
Search: "user-name",
419+
Status: []database.UserStatus{database.UserStatusActive},
420+
RbacRole: []string{},
421+
LoginType: []database.LoginType{},
418422
},
419423
},
420424
{
421425
Name: "OnlyParams",
422426
Query: "status:acTIve sEArch:User-Name role:Owner",
423427
Expected: database.GetUsersParams{
424-
Search: "user-name",
425-
Status: []database.UserStatus{database.UserStatusActive},
426-
RbacRole: []string{codersdk.RoleOwner},
428+
Search: "user-name",
429+
Status: []database.UserStatus{database.UserStatusActive},
430+
RbacRole: []string{codersdk.RoleOwner},
431+
LoginType: []database.LoginType{},
427432
},
428433
},
429434
{
430435
Name: "QuotedParam",
431436
Query: `status:SuSpenDeD sEArch:"User Name" role:meMber`,
432437
Expected: database.GetUsersParams{
433-
Search: "user name",
434-
Status: []database.UserStatus{database.UserStatusSuspended},
435-
RbacRole: []string{codersdk.RoleMember},
438+
Search: "user name",
439+
Status: []database.UserStatus{database.UserStatusSuspended},
440+
RbacRole: []string{codersdk.RoleMember},
441+
LoginType: []database.LoginType{},
436442
},
437443
},
438444
{
439445
Name: "QuotedKey",
440446
Query: `"status":acTIve "sEArch":User-Name "role":Owner`,
441447
Expected: database.GetUsersParams{
442-
Search: "user-name",
443-
Status: []database.UserStatus{database.UserStatusActive},
444-
RbacRole: []string{codersdk.RoleOwner},
448+
Search: "user-name",
449+
Status: []database.UserStatus{database.UserStatusActive},
450+
RbacRole: []string{codersdk.RoleOwner},
451+
LoginType: []database.LoginType{},
445452
},
446453
},
447454
{
448455
// Quotes keep elements together
449456
Name: "QuotedSpecial",
450457
Query: `search:"user:name"`,
451458
Expected: database.GetUsersParams{
452-
Search: "user:name",
459+
Search: "user:name",
460+
Status: []database.UserStatus{},
461+
RbacRole: []string{},
462+
LoginType: []database.LoginType{},
463+
},
464+
},
465+
{
466+
Name: "LoginType",
467+
Query: "login_type:github",
468+
Expected: database.GetUsersParams{
469+
Search: "",
470+
Status: []database.UserStatus{},
471+
RbacRole: []string{},
472+
LoginType: []database.LoginType{database.LoginTypeGithub},
473+
},
474+
},
475+
{
476+
Name: "MultipleLoginTypesWithSpaces",
477+
Query: "login_type:github login_type:password",
478+
Expected: database.GetUsersParams{
479+
Search: "",
453480
Status: []database.UserStatus{},
454481
RbacRole: []string{},
482+
LoginType: []database.LoginType{
483+
database.LoginTypeGithub,
484+
database.LoginTypePassword,
485+
},
486+
},
487+
},
488+
{
489+
Name: "MultipleLoginTypesWithCommas",
490+
Query: "login_type:github,password,none,oidc",
491+
Expected: database.GetUsersParams{
492+
Search: "",
493+
Status: []database.UserStatus{},
494+
RbacRole: []string{},
495+
LoginType: []database.LoginType{
496+
database.LoginTypeGithub,
497+
database.LoginTypePassword,
498+
database.LoginTypeNone,
499+
database.LoginTypeOIDC,
500+
},
455501
},
456502
},
457503

coderd/users.go

+1
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ func (api *API) GetUsers(rw http.ResponseWriter, r *http.Request) ([]database.Us
307307
CreatedAfter: params.CreatedAfter,
308308
CreatedBefore: params.CreatedBefore,
309309
GithubComUserID: params.GithubComUserID,
310+
LoginType: params.LoginType,
310311
// #nosec G115 - Pagination offsets are small and fit in int32
311312
OffsetOpt: int32(paginationParams.Offset),
312313
// #nosec G115 - Pagination limits are small and fit in int32

coderd/users_test.go

+120
Original file line numberDiff line numberDiff line change
@@ -1902,6 +1902,126 @@ func TestGetUsers(t *testing.T) {
19021902
require.Len(t, res.Users, 1)
19031903
require.Equal(t, res.Users[0].ID, first.UserID)
19041904
})
1905+
1906+
t.Run("LoginTypeNoneFilter", func(t *testing.T) {
1907+
t.Parallel()
1908+
client := coderdtest.New(t, nil)
1909+
first := coderdtest.CreateFirstUser(t, client)
1910+
ctx := testutil.Context(t, testutil.WaitLong)
1911+
1912+
_, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
1913+
1914+
Username: "bob",
1915+
OrganizationIDs: []uuid.UUID{first.OrganizationID},
1916+
UserLoginType: codersdk.LoginTypeNone,
1917+
})
1918+
require.NoError(t, err)
1919+
1920+
res, err := client.Users(ctx, codersdk.UsersRequest{
1921+
LoginType: []codersdk.LoginType{codersdk.LoginTypeNone},
1922+
})
1923+
require.NoError(t, err)
1924+
require.Len(t, res.Users, 1)
1925+
require.Equal(t, res.Users[0].LoginType, codersdk.LoginTypeNone)
1926+
})
1927+
1928+
t.Run("LoginTypeMultipleFilter", func(t *testing.T) {
1929+
t.Parallel()
1930+
client := coderdtest.New(t, nil)
1931+
first := coderdtest.CreateFirstUser(t, client)
1932+
ctx := testutil.Context(t, testutil.WaitLong)
1933+
filtered := make([]codersdk.User, 0)
1934+
1935+
bob, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
1936+
1937+
Username: "bob",
1938+
OrganizationIDs: []uuid.UUID{first.OrganizationID},
1939+
UserLoginType: codersdk.LoginTypeNone,
1940+
})
1941+
require.NoError(t, err)
1942+
filtered = append(filtered, bob)
1943+
1944+
charlie, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
1945+
1946+
Username: "charlie",
1947+
OrganizationIDs: []uuid.UUID{first.OrganizationID},
1948+
UserLoginType: codersdk.LoginTypeGithub,
1949+
})
1950+
require.NoError(t, err)
1951+
filtered = append(filtered, charlie)
1952+
1953+
res, err := client.Users(ctx, codersdk.UsersRequest{
1954+
LoginType: []codersdk.LoginType{codersdk.LoginTypeNone, codersdk.LoginTypeGithub},
1955+
})
1956+
require.NoError(t, err)
1957+
require.Len(t, res.Users, 2)
1958+
require.ElementsMatch(t, filtered, res.Users)
1959+
})
1960+
1961+
t.Run("DormantUserWithLoginTypeNone", func(t *testing.T) {
1962+
t.Parallel()
1963+
client := coderdtest.New(t, nil)
1964+
first := coderdtest.CreateFirstUser(t, client)
1965+
ctx := testutil.Context(t, testutil.WaitLong)
1966+
1967+
_, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
1968+
1969+
Username: "bob",
1970+
OrganizationIDs: []uuid.UUID{first.OrganizationID},
1971+
UserLoginType: codersdk.LoginTypeNone,
1972+
})
1973+
require.NoError(t, err)
1974+
1975+
_, err = client.UpdateUserStatus(ctx, "bob", codersdk.UserStatusSuspended)
1976+
require.NoError(t, err)
1977+
1978+
res, err := client.Users(ctx, codersdk.UsersRequest{
1979+
Status: codersdk.UserStatusSuspended,
1980+
LoginType: []codersdk.LoginType{codersdk.LoginTypeNone, codersdk.LoginTypeGithub},
1981+
})
1982+
require.NoError(t, err)
1983+
require.Len(t, res.Users, 1)
1984+
require.Equal(t, res.Users[0].Username, "bob")
1985+
require.Equal(t, res.Users[0].Status, codersdk.UserStatusSuspended)
1986+
require.Equal(t, res.Users[0].LoginType, codersdk.LoginTypeNone)
1987+
})
1988+
1989+
t.Run("LoginTypeOidcFromMultipleUser", func(t *testing.T) {
1990+
t.Parallel()
1991+
client := coderdtest.New(t, &coderdtest.Options{
1992+
OIDCConfig: &coderd.OIDCConfig{
1993+
AllowSignups: true,
1994+
},
1995+
})
1996+
first := coderdtest.CreateFirstUser(t, client)
1997+
ctx := testutil.Context(t, testutil.WaitLong)
1998+
1999+
_, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
2000+
2001+
Username: "bob",
2002+
OrganizationIDs: []uuid.UUID{first.OrganizationID},
2003+
UserLoginType: codersdk.LoginTypeOIDC,
2004+
})
2005+
require.NoError(t, err)
2006+
2007+
for i := range 5 {
2008+
_, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
2009+
Email: fmt.Sprintf("%[email protected]", i),
2010+
Username: fmt.Sprintf("user%d", i),
2011+
OrganizationIDs: []uuid.UUID{first.OrganizationID},
2012+
UserLoginType: codersdk.LoginTypeNone,
2013+
})
2014+
require.NoError(t, err)
2015+
}
2016+
2017+
res, err := client.Users(ctx, codersdk.UsersRequest{
2018+
LoginType: []codersdk.LoginType{codersdk.LoginTypeOIDC},
2019+
})
2020+
require.NoError(t, err)
2021+
require.Len(t, res.Users, 1)
2022+
require.Equal(t, res.Users[0].Username, "bob")
2023+
require.Equal(t, res.Users[0].LoginType, codersdk.LoginTypeOIDC)
2024+
})
19052025
}
19062026

19072027
func TestGetUsersPagination(t *testing.T) {

codersdk/users.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ type UsersRequest struct {
2828
// Filter users by status.
2929
Status UserStatus `json:"status,omitempty" typescript:"-"`
3030
// Filter users that have the given role.
31-
Role string `json:"role,omitempty" typescript:"-"`
31+
Role string `json:"role,omitempty" typescript:"-"`
32+
LoginType []LoginType `json:"login_type,omitempty" typescript:"-"`
3233

3334
SearchQuery string `json:"q,omitempty"`
3435
Pagination
@@ -755,6 +756,9 @@ func (c *Client) Users(ctx context.Context, req UsersRequest) (GetUsersResponse,
755756
if req.SearchQuery != "" {
756757
params = append(params, req.SearchQuery)
757758
}
759+
for _, lt := range req.LoginType {
760+
params = append(params, "login_type:"+string(lt))
761+
}
758762
q.Set("q", strings.Join(params, " "))
759763
r.URL.RawQuery = q.Encode()
760764
},

docs/admin/users/index.md

+3
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ to use the Coder's filter query:
190190
`status:active last_seen_before:"2023-07-01T00:00:00Z"`
191191
- To find users who were created between January 1 and January 18, 2023:
192192
`created_before:"2023-01-18T00:00:00Z" created_after:"2023-01-01T23:59:59Z"`
193+
- To find users who login using Github:
194+
`login_type:github`
193195

194196
The following filters are supported:
195197

@@ -203,3 +205,4 @@ The following filters are supported:
203205
the RFC3339Nano format.
204206
- `created_before` and `created_after` - The time a user was created. Uses the
205207
RFC3339Nano format.
208+
- `login_type` - Represents the login type of the user. Refer to the [LoginType documentation](https://pkg.go.dev/github.com/coder/coder/v2/codersdk#LoginType) for a list of supported values

0 commit comments

Comments
 (0)