From c0ff16314529b7b2bba753abde87a180227e5fa6 Mon Sep 17 00:00:00 2001 From: joobisb Date: Sat, 23 Nov 2024 00:21:05 +0530 Subject: [PATCH 01/14] feat: support created_at filter for the GET /users endpoint --- coderd/database/dbmem/dbmem.go | 10 ++++++++++ coderd/database/modelqueries.go | 1 + coderd/database/queries.sql.go | 12 ++++++++++-- coderd/database/queries/users.sql | 6 ++++++ coderd/searchquery/search.go | 1 + coderd/users.go | 1 + coderd/users_test.go | 11 +++++++++++ docs/admin/users/index.md | 6 ++++-- 8 files changed, 44 insertions(+), 4 deletions(-) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index aed57e9284b3a..90da06e09320a 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -5648,6 +5648,16 @@ func (q *FakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams users = usersFilteredByRole } + if !params.CreatedAt.IsZero() { + usersFilteredByCreatedAt := make([]database.User, 0, len(users)) + for i, user := range users { + if user.CreatedAt.Equal(params.CreatedAt) { + usersFilteredByCreatedAt = append(usersFilteredByCreatedAt, users[i]) + } + } + users = usersFilteredByCreatedAt + } + if !params.LastSeenBefore.IsZero() { usersFilteredByLastSeen := make([]database.User, 0, len(users)) for i, user := range users { diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index ff77012755fa2..58d0f9cbb3bba 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -390,6 +390,7 @@ func (q *sqlQuerier) GetAuthorizedUsers(ctx context.Context, arg GetUsersParams, pq.Array(arg.RbacRole), arg.LastSeenBefore, arg.LastSeenAfter, + arg.CreatedAt, arg.OffsetOpt, arg.LimitOpt, ) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 09dd4c1fbc488..40cfe4579436e 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -10239,16 +10239,22 @@ WHERE last_seen_at >= $6 ELSE true END + -- Filter by created_at + AND CASE + WHEN $7 :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN + DATE(created_at) = DATE($7) + 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 $7 + LOWER(username) ASC OFFSET $8 LIMIT -- A null limit means "no limit", so 0 means return all - NULLIF($8 :: int, 0) + NULLIF($9 :: int, 0) ` type GetUsersParams struct { @@ -10258,6 +10264,7 @@ type GetUsersParams struct { 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"` + CreatedAt time.Time `db:"created_at" json:"created_at"` OffsetOpt int32 `db:"offset_opt" json:"offset_opt"` LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } @@ -10293,6 +10300,7 @@ func (q *sqlQuerier) GetUsers(ctx context.Context, arg GetUsersParams) ([]GetUse pq.Array(arg.RbacRole), arg.LastSeenBefore, arg.LastSeenAfter, + arg.CreatedAt, arg.OffsetOpt, arg.LimitOpt, ) diff --git a/coderd/database/queries/users.sql b/coderd/database/queries/users.sql index a4f8844fd2db5..604c625d73bc8 100644 --- a/coderd/database/queries/users.sql +++ b/coderd/database/queries/users.sql @@ -199,6 +199,12 @@ WHERE last_seen_at >= @last_seen_after ELSE true END + -- Filter by created_at + AND CASE + WHEN @created_at :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN + DATE(created_at) = DATE(@created_at) + ELSE true + END -- End of filters -- Authorize Filter clause will be injected below in GetAuthorizedUsers diff --git a/coderd/searchquery/search.go b/coderd/searchquery/search.go index 707f32bfc7d32..104f83c5380e7 100644 --- a/coderd/searchquery/search.go +++ b/coderd/searchquery/search.go @@ -70,6 +70,7 @@ func Users(query string) (database.GetUsersParams, []codersdk.ValidationError) { RbacRole: parser.Strings(values, []string{}, "role"), LastSeenAfter: parser.Time3339Nano(values, time.Time{}, "last_seen_after"), LastSeenBefore: parser.Time3339Nano(values, time.Time{}, "last_seen_before"), + CreatedAt: parser.Time3339Nano(values, time.Time{}, "created_at"), } parser.ErrorExcessParams(values) return filter, parser.Errors diff --git a/coderd/users.go b/coderd/users.go index 2fccef83f2013..b494e248dfc0e 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -317,6 +317,7 @@ func (api *API) GetUsers(rw http.ResponseWriter, r *http.Request) ([]database.Us RbacRole: params.RbacRole, LastSeenBefore: params.LastSeenBefore, LastSeenAfter: params.LastSeenAfter, + CreatedAt: params.CreatedAt, OffsetOpt: int32(paginationParams.Offset), LimitOpt: int32(paginationParams.Limit), }) diff --git a/coderd/users_test.go b/coderd/users_test.go index c9038c7418034..3c04c1d770011 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1467,6 +1467,7 @@ func TestUsersFilter(t *testing.T) { firstUser, err := client.User(ctx, codersdk.Me) require.NoError(t, err, "fetch me") + createdAt := firstUser.CreatedAt // Noon on Jan 18 is the "now" for this test for last_seen timestamps. // All these values are equal @@ -1655,6 +1656,16 @@ func TestUsersFilter(t *testing.T) { return u.LastSeenAt.Before(end) && u.LastSeenAt.After(start) }, }, + { + Name: "CreatedAt", + Filter: codersdk.UsersRequest{ + SearchQuery: fmt.Sprintf(`created_at:%q`, createdAt.Format(time.RFC3339)), + }, + FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { + target := time.Date(2023, 1, 18, 12, 0, 0, 0, time.UTC) + return u.CreatedAt.Equal(target) + }, + }, } for _, c := range testCases { diff --git a/docs/admin/users/index.md b/docs/admin/users/index.md index a00030a514f05..482b940285d95 100644 --- a/docs/admin/users/index.md +++ b/docs/admin/users/index.md @@ -185,8 +185,9 @@ to use the Coder's filter query: - To find active users, use the filter `status:active`. - To find admin users, use the filter `role:admin`. -- To find users have not been active since July 2023: +- To find users who have not been active since July 2023: `status:active last_seen_before:"2023-07-01T00:00:00Z"` +- To find users created at January 18, 2023: `created_at:"2023-01-18T00:00:00Z"` The following filters are supported: @@ -195,6 +196,7 @@ The following filters are supported: - `role` - Represents the role of the user. You can refer to the [TemplateRole documentation](https://pkg.go.dev/github.com/coder/coder/v2/codersdk#TemplateRole) for a list of supported user roles. -- `last_seen_before` and `last_seen_after` - The last time a used has used the +- `last_seen_before` and `last_seen_after` - The last time a user has used the platform (e.g. logging in, any API requests, connecting to workspaces). Uses the RFC3339Nano format. +- `created_at` - The time a user was created. Uses the RFC3339Nano format. From e29a208749c1d72bd7f4abb3a27bb4a57201d43c Mon Sep 17 00:00:00 2001 From: joobisb Date: Thu, 5 Dec 2024 01:47:24 +0530 Subject: [PATCH 02/14] update the query to created_at_before and created_at_after --- coderd/database/dbmem/dbmem.go | 14 +++++++++++-- coderd/database/modelqueries.go | 3 ++- coderd/database/queries.sql.go | 33 +++++++++++++++++++------------ coderd/database/queries/users.sql | 9 +++++++-- coderd/searchquery/search.go | 13 ++++++------ coderd/users.go | 19 +++++++++--------- coderd/users_test.go | 30 +++++++++++++++++++++++----- docs/admin/users/index.md | 6 ++++-- 8 files changed, 87 insertions(+), 40 deletions(-) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 90da06e09320a..5d33d944014e0 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -5648,10 +5648,20 @@ func (q *FakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams users = usersFilteredByRole } - if !params.CreatedAt.IsZero() { + if !params.CreatedAtBefore.IsZero() { usersFilteredByCreatedAt := make([]database.User, 0, len(users)) for i, user := range users { - if user.CreatedAt.Equal(params.CreatedAt) { + if user.CreatedAt.Before(params.CreatedAtBefore) { + usersFilteredByCreatedAt = append(usersFilteredByCreatedAt, users[i]) + } + } + users = usersFilteredByCreatedAt + } + + if !params.CreatedAtAfter.IsZero() { + usersFilteredByCreatedAt := make([]database.User, 0, len(users)) + for i, user := range users { + if user.CreatedAt.After(params.CreatedAtAfter) { usersFilteredByCreatedAt = append(usersFilteredByCreatedAt, users[i]) } } diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 58d0f9cbb3bba..0eb29dbcb54b9 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -390,7 +390,8 @@ func (q *sqlQuerier) GetAuthorizedUsers(ctx context.Context, arg GetUsersParams, pq.Array(arg.RbacRole), arg.LastSeenBefore, arg.LastSeenAfter, - arg.CreatedAt, + arg.CreatedAtBefore, + arg.CreatedAtAfter, arg.OffsetOpt, arg.LimitOpt, ) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 40cfe4579436e..5879ef798f978 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -10242,7 +10242,12 @@ WHERE -- Filter by created_at AND CASE WHEN $7 :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN - DATE(created_at) = DATE($7) + created_at <= $7 + ELSE true + END + AND CASE + WHEN $8 :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN + created_at >= $8 ELSE true END -- End of filters @@ -10251,22 +10256,23 @@ WHERE -- @authorize_filter ORDER BY -- Deterministic and consistent ordering of all users. This is to ensure consistent pagination. - LOWER(username) ASC OFFSET $8 + LOWER(username) ASC OFFSET $9 LIMIT -- A null limit means "no limit", so 0 means return all - NULLIF($9 :: int, 0) + NULLIF($10 :: 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"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - 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"` + CreatedAtBefore time.Time `db:"created_at_before" json:"created_at_before"` + CreatedAtAfter time.Time `db:"created_at_after" json:"created_at_after"` + OffsetOpt int32 `db:"offset_opt" json:"offset_opt"` + LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } type GetUsersRow struct { @@ -10300,7 +10306,8 @@ func (q *sqlQuerier) GetUsers(ctx context.Context, arg GetUsersParams) ([]GetUse pq.Array(arg.RbacRole), arg.LastSeenBefore, arg.LastSeenAfter, - arg.CreatedAt, + arg.CreatedAtBefore, + arg.CreatedAtAfter, arg.OffsetOpt, arg.LimitOpt, ) diff --git a/coderd/database/queries/users.sql b/coderd/database/queries/users.sql index 604c625d73bc8..dce51d45bb907 100644 --- a/coderd/database/queries/users.sql +++ b/coderd/database/queries/users.sql @@ -201,8 +201,13 @@ WHERE END -- Filter by created_at AND CASE - WHEN @created_at :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN - DATE(created_at) = DATE(@created_at) + WHEN @created_at_before :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN + created_at <= @created_at_before + ELSE true + END + AND CASE + WHEN @created_at_after :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN + created_at >= @created_at_after ELSE true END -- End of filters diff --git a/coderd/searchquery/search.go b/coderd/searchquery/search.go index 104f83c5380e7..970ccb287a5ec 100644 --- a/coderd/searchquery/search.go +++ b/coderd/searchquery/search.go @@ -65,12 +65,13 @@ 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"), - CreatedAt: parser.Time3339Nano(values, time.Time{}, "created_at"), + 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"), + CreatedAtAfter: parser.Time3339Nano(values, time.Time{}, "created_at_after"), + CreatedAtBefore: parser.Time3339Nano(values, time.Time{}, "created_at_before"), } parser.ErrorExcessParams(values) return filter, parser.Errors diff --git a/coderd/users.go b/coderd/users.go index b494e248dfc0e..959579ffbadcb 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -311,15 +311,16 @@ 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, - CreatedAt: params.CreatedAt, - 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, + CreatedAtAfter: params.CreatedAtAfter, + CreatedAtBefore: params.CreatedAtBefore, + 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 3c04c1d770011..324c4c57756e1 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1467,7 +1467,6 @@ func TestUsersFilter(t *testing.T) { firstUser, err := client.User(ctx, codersdk.Me) require.NoError(t, err, "fetch me") - createdAt := firstUser.CreatedAt // Noon on Jan 18 is the "now" for this test for last_seen timestamps. // All these values are equal @@ -1657,13 +1656,34 @@ func TestUsersFilter(t *testing.T) { }, }, { - Name: "CreatedAt", + Name: "CreatedAtBefore", Filter: codersdk.UsersRequest{ - SearchQuery: fmt.Sprintf(`created_at:%q`, createdAt.Format(time.RFC3339)), + SearchQuery: `created_at_before:"2023-01-31T23:59:59Z"`, }, FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { - target := time.Date(2023, 1, 18, 12, 0, 0, 0, time.UTC) - return u.CreatedAt.Equal(target) + end := time.Date(2023, 1, 31, 23, 59, 59, 0, time.UTC) + return u.CreatedAt.Before(end) + }, + }, + { + Name: "CreatedAtAfter", + Filter: codersdk.UsersRequest{ + SearchQuery: `created_at_after:"2023-01-01T00:00:00Z"`, + }, + FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { + start := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) + return u.CreatedAt.After(start) + }, + }, + { + Name: "CreatedAtRange", + Filter: codersdk.UsersRequest{ + SearchQuery: `created_at_after:"2023-01-01T00:00:00Z" created_at_before:"2023-01-31T23:59:59Z"`, + }, + FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { + start := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) + end := time.Date(2023, 1, 31, 23, 59, 59, 0, time.UTC) + return u.CreatedAt.After(start) && u.CreatedAt.Before(end) }, }, } diff --git a/docs/admin/users/index.md b/docs/admin/users/index.md index 482b940285d95..f51ea88e45372 100644 --- a/docs/admin/users/index.md +++ b/docs/admin/users/index.md @@ -187,7 +187,8 @@ to use the Coder's filter query: - To find admin users, use the filter `role:admin`. - To find users who have not been active since July 2023: `status:active last_seen_before:"2023-07-01T00:00:00Z"` -- To find users created at January 18, 2023: `created_at:"2023-01-18T00:00:00Z"` +- To find users who were created between January 1 and January 18, 2023: + `created_at_before:"2023-01-18T00:00:00Z" created_at_after:"2023-01-01T23:59:59Z"` The following filters are supported: @@ -199,4 +200,5 @@ The following filters are supported: - `last_seen_before` and `last_seen_after` - The last time a user has used the platform (e.g. logging in, any API requests, connecting to workspaces). Uses the RFC3339Nano format. -- `created_at` - The time a user was created. Uses the RFC3339Nano format. +- `created_at_before` and `created_at_after` - The time a user was created. Uses + the RFC3339Nano format. From 8f246c50bbc9352a897c38eb08543855d8edb6b8 Mon Sep 17 00:00:00 2001 From: joobisb Date: Thu, 5 Dec 2024 19:16:41 +0530 Subject: [PATCH 03/14] rename the created fields --- coderd/database/dbmem/dbmem.go | 8 ++++---- coderd/database/modelqueries.go | 4 ++-- coderd/database/queries.sql.go | 24 ++++++++++++------------ coderd/database/queries/users.sql | 8 ++++---- coderd/searchquery/search.go | 14 +++++++------- coderd/users.go | 20 ++++++++++---------- coderd/users_test.go | 6 +++--- docs/admin/users/index.md | 4 ++-- 8 files changed, 44 insertions(+), 44 deletions(-) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 5d33d944014e0..4ea18cbd05033 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -5648,20 +5648,20 @@ func (q *FakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams users = usersFilteredByRole } - if !params.CreatedAtBefore.IsZero() { + if !params.CreatedBefore.IsZero() { usersFilteredByCreatedAt := make([]database.User, 0, len(users)) for i, user := range users { - if user.CreatedAt.Before(params.CreatedAtBefore) { + if user.CreatedAt.Before(params.CreatedBefore) { usersFilteredByCreatedAt = append(usersFilteredByCreatedAt, users[i]) } } users = usersFilteredByCreatedAt } - if !params.CreatedAtAfter.IsZero() { + if !params.CreatedAfter.IsZero() { usersFilteredByCreatedAt := make([]database.User, 0, len(users)) for i, user := range users { - if user.CreatedAt.After(params.CreatedAtAfter) { + if user.CreatedAt.After(params.CreatedAfter) { usersFilteredByCreatedAt = append(usersFilteredByCreatedAt, users[i]) } } diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 0eb29dbcb54b9..0929cb9962f74 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -390,8 +390,8 @@ func (q *sqlQuerier) GetAuthorizedUsers(ctx context.Context, arg GetUsersParams, pq.Array(arg.RbacRole), arg.LastSeenBefore, arg.LastSeenAfter, - arg.CreatedAtBefore, - arg.CreatedAtAfter, + arg.CreatedBefore, + arg.CreatedAfter, arg.OffsetOpt, arg.LimitOpt, ) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 5879ef798f978..9239a383d70df 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -10263,16 +10263,16 @@ LIMIT ` 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"` - CreatedAtBefore time.Time `db:"created_at_before" json:"created_at_before"` - CreatedAtAfter time.Time `db:"created_at_after" json:"created_at_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"` + OffsetOpt int32 `db:"offset_opt" json:"offset_opt"` + LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } type GetUsersRow struct { @@ -10306,8 +10306,8 @@ func (q *sqlQuerier) GetUsers(ctx context.Context, arg GetUsersParams) ([]GetUse pq.Array(arg.RbacRole), arg.LastSeenBefore, arg.LastSeenAfter, - arg.CreatedAtBefore, - arg.CreatedAtAfter, + arg.CreatedBefore, + arg.CreatedAfter, arg.OffsetOpt, arg.LimitOpt, ) diff --git a/coderd/database/queries/users.sql b/coderd/database/queries/users.sql index dce51d45bb907..1f30a2c2c1d24 100644 --- a/coderd/database/queries/users.sql +++ b/coderd/database/queries/users.sql @@ -201,13 +201,13 @@ WHERE END -- Filter by created_at AND CASE - WHEN @created_at_before :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN - created_at <= @created_at_before + WHEN @created_before :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN + created_at <= @created_before ELSE true END AND CASE - WHEN @created_at_after :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN - created_at >= @created_at_after + WHEN @created_after :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN + created_at >= @created_after ELSE true END -- End of filters diff --git a/coderd/searchquery/search.go b/coderd/searchquery/search.go index 970ccb287a5ec..a4fe5d4775d6c 100644 --- a/coderd/searchquery/search.go +++ b/coderd/searchquery/search.go @@ -65,13 +65,13 @@ 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"), - CreatedAtAfter: parser.Time3339Nano(values, time.Time{}, "created_at_after"), - CreatedAtBefore: parser.Time3339Nano(values, time.Time{}, "created_at_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"), } parser.ErrorExcessParams(values) return filter, parser.Errors diff --git a/coderd/users.go b/coderd/users.go index 959579ffbadcb..56f295986859c 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -311,16 +311,16 @@ 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, - CreatedAtAfter: params.CreatedAtAfter, - CreatedAtBefore: params.CreatedAtBefore, - 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, + 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 324c4c57756e1..cd73357a15987 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1658,7 +1658,7 @@ func TestUsersFilter(t *testing.T) { { Name: "CreatedAtBefore", Filter: codersdk.UsersRequest{ - SearchQuery: `created_at_before:"2023-01-31T23:59:59Z"`, + SearchQuery: `created_before:"2023-01-31T23:59:59Z"`, }, FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { end := time.Date(2023, 1, 31, 23, 59, 59, 0, time.UTC) @@ -1668,7 +1668,7 @@ func TestUsersFilter(t *testing.T) { { Name: "CreatedAtAfter", Filter: codersdk.UsersRequest{ - SearchQuery: `created_at_after:"2023-01-01T00:00:00Z"`, + SearchQuery: `created_after:"2023-01-01T00:00:00Z"`, }, FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { start := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) @@ -1678,7 +1678,7 @@ func TestUsersFilter(t *testing.T) { { Name: "CreatedAtRange", Filter: codersdk.UsersRequest{ - SearchQuery: `created_at_after:"2023-01-01T00:00:00Z" created_at_before:"2023-01-31T23:59:59Z"`, + SearchQuery: `created_after:"2023-01-01T00:00:00Z" created_before:"2023-01-31T23:59:59Z"`, }, FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { start := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) diff --git a/docs/admin/users/index.md b/docs/admin/users/index.md index f51ea88e45372..2dc7a56010a08 100644 --- a/docs/admin/users/index.md +++ b/docs/admin/users/index.md @@ -188,7 +188,7 @@ to use the Coder's filter query: - To find users who have not been active since July 2023: `status:active last_seen_before:"2023-07-01T00:00:00Z"` - To find users who were created between January 1 and January 18, 2023: - `created_at_before:"2023-01-18T00:00:00Z" created_at_after:"2023-01-01T23:59:59Z"` + `created_before:"2023-01-18T00:00:00Z" created_after:"2023-01-01T23:59:59Z"` The following filters are supported: @@ -200,5 +200,5 @@ The following filters are supported: - `last_seen_before` and `last_seen_after` - The last time a user has used the platform (e.g. logging in, any API requests, connecting to workspaces). Uses the RFC3339Nano format. -- `created_at_before` and `created_at_after` - The time a user was created. Uses +- `created_before` and `created_after` - The time a user was created. Uses the RFC3339Nano format. From 70895a4bcf3c047eb398ada997f5156c7e353f46 Mon Sep 17 00:00:00 2001 From: joobisb Date: Tue, 10 Dec 2024 17:20:36 +0530 Subject: [PATCH 04/14] chore: update tests to handle user created filters --- coderd/users_test.go | 69 ++++++++++++++++++++++++++++++++++++--- docs/admin/users/index.md | 4 +-- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/coderd/users_test.go b/coderd/users_test.go index cd73357a15987..fe88ebe08a6b3 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1513,6 +1513,45 @@ func TestUsersFilter(t *testing.T) { users = append(users, user) } + // Add users with different creation dates for testing date filters + for i := 0; i < 3; i++ { + // nolint:gocritic + user1, err := api.Database.InsertUser(dbauthz.AsSystemRestricted(ctx), database.InsertUserParams{ + ID: uuid.New(), + Email: fmt.Sprintf("before%d@coder.com", i), + Username: fmt.Sprintf("before%d", i), + LoginType: database.LoginTypeNone, + CreatedAt: time.Date(2022, 12, 15+i, 12, 0, 0, 0, time.UTC), + UpdatedAt: dbtime.Now(), + }) + require.NoError(t, err) + users = append(users, dbUserToSDKUser(user1)) + + // nolint:gocritic + user2, err := api.Database.InsertUser(dbauthz.AsSystemRestricted(ctx), database.InsertUserParams{ + ID: uuid.New(), + Email: fmt.Sprintf("during%d@coder.com", i), + Username: fmt.Sprintf("during%d", i), + LoginType: database.LoginTypeNone, + CreatedAt: time.Date(2023, 1, 15+i, 12, 0, 0, 0, time.UTC), + UpdatedAt: dbtime.Now(), + }) + require.NoError(t, err) + users = append(users, dbUserToSDKUser(user2)) + + // nolint:gocritic + user3, err := api.Database.InsertUser(dbauthz.AsSystemRestricted(ctx), database.InsertUserParams{ + ID: uuid.New(), + Email: fmt.Sprintf("after%d@coder.com", i), + Username: fmt.Sprintf("after%d", i), + LoginType: database.LoginTypeNone, + CreatedAt: time.Date(2023, 2, 15+i, 12, 0, 0, 0, time.UTC), + UpdatedAt: dbtime.Now(), + }) + require.NoError(t, err) + users = append(users, dbUserToSDKUser(user3)) + } + // --- Setup done --- testCases := []struct { Name string @@ -1521,10 +1560,8 @@ func TestUsersFilter(t *testing.T) { FilterF func(f codersdk.UsersRequest, user codersdk.User) bool }{ { - Name: "All", - Filter: codersdk.UsersRequest{ - Status: codersdk.UserStatusSuspended + "," + codersdk.UserStatusActive, - }, + Name: "All", + Filter: codersdk.UsersRequest{}, FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { return true }, @@ -1602,7 +1639,7 @@ func TestUsersFilter(t *testing.T) { Status: codersdk.UserStatusSuspended + "," + codersdk.UserStatusActive, }, FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { - return true + return u.Status == codersdk.UserStatusSuspended || u.Status == codersdk.UserStatusActive }, }, { @@ -2304,6 +2341,28 @@ func onlyUsernames[U codersdk.User | database.User](users []U) []string { return out } +// dbUserToSDKUser converts database.User to codersdk.User +func dbUserToSDKUser(dbUser database.User) codersdk.User { + return codersdk.User{ + ReducedUser: codersdk.ReducedUser{ + MinimalUser: codersdk.MinimalUser{ + ID: dbUser.ID, + Username: dbUser.Username, + AvatarURL: dbUser.AvatarURL, + }, + Email: dbUser.Email, + CreatedAt: dbUser.CreatedAt, + UpdatedAt: dbUser.UpdatedAt, + LastSeenAt: dbUser.LastSeenAt, + Status: codersdk.UserStatus(dbUser.Status), + LoginType: codersdk.LoginType(dbUser.LoginType), + ThemePreference: dbUser.ThemePreference, + }, + OrganizationIDs: make([]uuid.UUID, 0), + Roles: make([]codersdk.SlimRole, 0), + } +} + func BenchmarkUsersMe(b *testing.B) { client := coderdtest.New(b, nil) _ = coderdtest.CreateFirstUser(b, client) diff --git a/docs/admin/users/index.md b/docs/admin/users/index.md index 2dc7a56010a08..9dcdb237eb764 100644 --- a/docs/admin/users/index.md +++ b/docs/admin/users/index.md @@ -200,5 +200,5 @@ The following filters are supported: - `last_seen_before` and `last_seen_after` - The last time a user has used the platform (e.g. logging in, any API requests, connecting to workspaces). Uses the RFC3339Nano format. -- `created_before` and `created_after` - The time a user was created. Uses - the RFC3339Nano format. +- `created_before` and `created_after` - The time a user was created. Uses the + RFC3339Nano format. From daf481e83f2cc9524137dea3dbcdfa15182babad Mon Sep 17 00:00:00 2001 From: joobisb Date: Wed, 11 Dec 2024 00:27:32 +0530 Subject: [PATCH 05/14] addressed comments --- coderd/users_test.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/coderd/users_test.go b/coderd/users_test.go index fe88ebe08a6b3..8fb39a905f961 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1515,35 +1515,38 @@ func TestUsersFilter(t *testing.T) { // Add users with different creation dates for testing date filters for i := 0; i < 3; i++ { - // nolint:gocritic + // nolint:gocritic // Using system context is necessary to seed data in tests user1, err := api.Database.InsertUser(dbauthz.AsSystemRestricted(ctx), database.InsertUserParams{ ID: uuid.New(), Email: fmt.Sprintf("before%d@coder.com", i), Username: fmt.Sprintf("before%d", i), LoginType: database.LoginTypeNone, + Status: string(codersdk.UserStatusActive), CreatedAt: time.Date(2022, 12, 15+i, 12, 0, 0, 0, time.UTC), UpdatedAt: dbtime.Now(), }) require.NoError(t, err) users = append(users, dbUserToSDKUser(user1)) - // nolint:gocritic + // nolint:gocritic //Using system context is necessary to seed data in tests user2, err := api.Database.InsertUser(dbauthz.AsSystemRestricted(ctx), database.InsertUserParams{ ID: uuid.New(), Email: fmt.Sprintf("during%d@coder.com", i), Username: fmt.Sprintf("during%d", i), LoginType: database.LoginTypeNone, + Status: string(codersdk.UserStatusActive), CreatedAt: time.Date(2023, 1, 15+i, 12, 0, 0, 0, time.UTC), UpdatedAt: dbtime.Now(), }) require.NoError(t, err) users = append(users, dbUserToSDKUser(user2)) - // nolint:gocritic + // nolint:gocritic // Using system context is necessary to seed data in tests user3, err := api.Database.InsertUser(dbauthz.AsSystemRestricted(ctx), database.InsertUserParams{ ID: uuid.New(), Email: fmt.Sprintf("after%d@coder.com", i), Username: fmt.Sprintf("after%d", i), + Status: string(codersdk.UserStatusActive), LoginType: database.LoginTypeNone, CreatedAt: time.Date(2023, 2, 15+i, 12, 0, 0, 0, time.UTC), UpdatedAt: dbtime.Now(), @@ -1560,8 +1563,10 @@ func TestUsersFilter(t *testing.T) { FilterF func(f codersdk.UsersRequest, user codersdk.User) bool }{ { - Name: "All", - Filter: codersdk.UsersRequest{}, + Name: "All", + Filter: codersdk.UsersRequest{ + Status: codersdk.UserStatusSuspended + "," + codersdk.UserStatusActive, + }, FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { return true }, @@ -1639,7 +1644,7 @@ func TestUsersFilter(t *testing.T) { Status: codersdk.UserStatusSuspended + "," + codersdk.UserStatusActive, }, FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool { - return u.Status == codersdk.UserStatusSuspended || u.Status == codersdk.UserStatusActive + return true }, }, { From 81932341a7ce5a0c3bbd5da801840c1071a49fc1 Mon Sep 17 00:00:00 2001 From: joobisb Date: Wed, 11 Dec 2024 15:06:52 +0530 Subject: [PATCH 06/14] use existing db2sdk method --- coderd/users_test.go | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/coderd/users_test.go b/coderd/users_test.go index 8fb39a905f961..8400dbab4e3cc 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -26,6 +26,7 @@ import ( "github.com/coder/coder/v2/coderd/audit" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbfake" "github.com/coder/coder/v2/coderd/database/dbgen" @@ -1526,7 +1527,7 @@ func TestUsersFilter(t *testing.T) { UpdatedAt: dbtime.Now(), }) require.NoError(t, err) - users = append(users, dbUserToSDKUser(user1)) + users = append(users, db2sdk.User(user1, []uuid.UUID{})) // nolint:gocritic //Using system context is necessary to seed data in tests user2, err := api.Database.InsertUser(dbauthz.AsSystemRestricted(ctx), database.InsertUserParams{ @@ -1539,7 +1540,7 @@ func TestUsersFilter(t *testing.T) { UpdatedAt: dbtime.Now(), }) require.NoError(t, err) - users = append(users, dbUserToSDKUser(user2)) + users = append(users, db2sdk.User(user2, []uuid.UUID{})) // nolint:gocritic // Using system context is necessary to seed data in tests user3, err := api.Database.InsertUser(dbauthz.AsSystemRestricted(ctx), database.InsertUserParams{ @@ -1552,7 +1553,7 @@ func TestUsersFilter(t *testing.T) { UpdatedAt: dbtime.Now(), }) require.NoError(t, err) - users = append(users, dbUserToSDKUser(user3)) + users = append(users, db2sdk.User(user3, []uuid.UUID{})) } // --- Setup done --- @@ -1748,6 +1749,8 @@ func TestUsersFilter(t *testing.T) { exp = append(exp, made) } } + fmt.Printf("expexp: %+v\n", exp) + fmt.Printf("matched.Usersmatched.Users: %+v\n", matched.Users) require.ElementsMatch(t, exp, matched.Users, "expected users returned") }) } @@ -2346,28 +2349,6 @@ func onlyUsernames[U codersdk.User | database.User](users []U) []string { return out } -// dbUserToSDKUser converts database.User to codersdk.User -func dbUserToSDKUser(dbUser database.User) codersdk.User { - return codersdk.User{ - ReducedUser: codersdk.ReducedUser{ - MinimalUser: codersdk.MinimalUser{ - ID: dbUser.ID, - Username: dbUser.Username, - AvatarURL: dbUser.AvatarURL, - }, - Email: dbUser.Email, - CreatedAt: dbUser.CreatedAt, - UpdatedAt: dbUser.UpdatedAt, - LastSeenAt: dbUser.LastSeenAt, - Status: codersdk.UserStatus(dbUser.Status), - LoginType: codersdk.LoginType(dbUser.LoginType), - ThemePreference: dbUser.ThemePreference, - }, - OrganizationIDs: make([]uuid.UUID, 0), - Roles: make([]codersdk.SlimRole, 0), - } -} - func BenchmarkUsersMe(b *testing.B) { client := coderdtest.New(b, nil) _ = coderdtest.CreateFirstUser(b, client) From 67a7113874d205a8a45bf433cea893bac94f521c Mon Sep 17 00:00:00 2001 From: joobisb Date: Wed, 11 Dec 2024 16:27:09 +0530 Subject: [PATCH 07/14] remove unnecessary print statements --- coderd/users_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/coderd/users_test.go b/coderd/users_test.go index 8400dbab4e3cc..8a7758f7bfc79 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1749,8 +1749,6 @@ func TestUsersFilter(t *testing.T) { exp = append(exp, made) } } - fmt.Printf("expexp: %+v\n", exp) - fmt.Printf("matched.Usersmatched.Users: %+v\n", matched.Users) require.ElementsMatch(t, exp, matched.Users, "expected users returned") }) } From 20ed862c5114f06b83cf5a1434045b7d52690a66 Mon Sep 17 00:00:00 2001 From: joobisb Date: Wed, 11 Dec 2024 18:21:02 +0530 Subject: [PATCH 08/14] fix tests --- coderd/users_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/coderd/users_test.go b/coderd/users_test.go index 8a7758f7bfc79..a6d7f3c82ebd4 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1523,6 +1523,7 @@ func TestUsersFilter(t *testing.T) { Username: fmt.Sprintf("before%d", i), LoginType: database.LoginTypeNone, Status: string(codersdk.UserStatusActive), + RBACRoles: []string{codersdk.RoleOwner}, CreatedAt: time.Date(2022, 12, 15+i, 12, 0, 0, 0, time.UTC), UpdatedAt: dbtime.Now(), }) @@ -1536,6 +1537,7 @@ func TestUsersFilter(t *testing.T) { Username: fmt.Sprintf("during%d", i), LoginType: database.LoginTypeNone, Status: string(codersdk.UserStatusActive), + RBACRoles: []string{codersdk.RoleOwner}, CreatedAt: time.Date(2023, 1, 15+i, 12, 0, 0, 0, time.UTC), UpdatedAt: dbtime.Now(), }) @@ -1549,6 +1551,7 @@ func TestUsersFilter(t *testing.T) { Username: fmt.Sprintf("after%d", i), Status: string(codersdk.UserStatusActive), LoginType: database.LoginTypeNone, + RBACRoles: []string{codersdk.RoleOwner}, CreatedAt: time.Date(2023, 2, 15+i, 12, 0, 0, 0, time.UTC), UpdatedAt: dbtime.Now(), }) From f2d1a0220294bfb1090e386c0bc96297e715f023 Mon Sep 17 00:00:00 2001 From: joobisb Date: Sat, 14 Dec 2024 13:10:44 +0530 Subject: [PATCH 09/14] fix: test failing on postgres --- coderd/users_test.go | 66 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/coderd/users_test.go b/coderd/users_test.go index a6d7f3c82ebd4..a8de254257145 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -26,7 +26,6 @@ import ( "github.com/coder/coder/v2/coderd/audit" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbfake" "github.com/coder/coder/v2/coderd/database/dbgen" @@ -1522,13 +1521,26 @@ func TestUsersFilter(t *testing.T) { Email: fmt.Sprintf("before%d@coder.com", i), Username: fmt.Sprintf("before%d", i), LoginType: database.LoginTypeNone, - Status: string(codersdk.UserStatusActive), - RBACRoles: []string{codersdk.RoleOwner}, - CreatedAt: time.Date(2022, 12, 15+i, 12, 0, 0, 0, time.UTC), - UpdatedAt: dbtime.Now(), + RBACRoles: []string{codersdk.RoleMember}, + CreatedAt: dbtime.Time(time.Date(2022, 12, 15+i, 12, 0, 0, 0, time.UTC)), }) require.NoError(t, err) - users = append(users, db2sdk.User(user1, []uuid.UUID{})) + + orgMember, err := api.Database.InsertOrganizationMember(dbauthz.AsSystemRestricted(ctx), database.InsertOrganizationMemberParams{ + OrganizationID: firstUser.OrganizationIDs[0], + UserID: user1.ID, + Roles: []string{}, + CreatedAt: dbtime.Now(), + }) + require.NoError(t, err) + + // hack: Call UpdateUserStatus to get API-formatted timestamps (without timezones) + // instead of database-formatted timestamps (with timezones) for comparison + sdkUser1, err := client.UpdateUserStatus(ctx, user1.ID.String(), codersdk.UserStatusActive) + require.NoError(t, err) + + sdkUser1.OrganizationIDs = []uuid.UUID{orgMember.OrganizationID} + users = append(users, sdkUser1) // nolint:gocritic //Using system context is necessary to seed data in tests user2, err := api.Database.InsertUser(dbauthz.AsSystemRestricted(ctx), database.InsertUserParams{ @@ -1536,27 +1548,53 @@ func TestUsersFilter(t *testing.T) { Email: fmt.Sprintf("during%d@coder.com", i), Username: fmt.Sprintf("during%d", i), LoginType: database.LoginTypeNone, - Status: string(codersdk.UserStatusActive), RBACRoles: []string{codersdk.RoleOwner}, - CreatedAt: time.Date(2023, 1, 15+i, 12, 0, 0, 0, time.UTC), - UpdatedAt: dbtime.Now(), + CreatedAt: dbtime.Time(time.Date(2023, 1, 15+i, 12, 0, 0, 0, time.UTC)), }) require.NoError(t, err) - users = append(users, db2sdk.User(user2, []uuid.UUID{})) + + // hack: Call UpdateUserStatus to get API-formatted timestamps (without timezones) + // instead of database-formatted timestamps (with timezones) for comparison + sdkUser2, err := client.UpdateUserStatus(ctx, user2.ID.String(), codersdk.UserStatusActive) + require.NoError(t, err) + + orgMember, err = api.Database.InsertOrganizationMember(dbauthz.AsSystemRestricted(ctx), database.InsertOrganizationMemberParams{ + OrganizationID: firstUser.OrganizationIDs[0], + UserID: user2.ID, + Roles: []string{}, + CreatedAt: dbtime.Now(), + }) + require.NoError(t, err) + + sdkUser2.OrganizationIDs = []uuid.UUID{orgMember.OrganizationID} + users = append(users, sdkUser2) // nolint:gocritic // Using system context is necessary to seed data in tests user3, err := api.Database.InsertUser(dbauthz.AsSystemRestricted(ctx), database.InsertUserParams{ ID: uuid.New(), Email: fmt.Sprintf("after%d@coder.com", i), Username: fmt.Sprintf("after%d", i), - Status: string(codersdk.UserStatusActive), LoginType: database.LoginTypeNone, RBACRoles: []string{codersdk.RoleOwner}, - CreatedAt: time.Date(2023, 2, 15+i, 12, 0, 0, 0, time.UTC), - UpdatedAt: dbtime.Now(), + CreatedAt: dbtime.Time(time.Date(2023, 2, 15+i, 12, 0, 0, 0, time.UTC)), }) require.NoError(t, err) - users = append(users, db2sdk.User(user3, []uuid.UUID{})) + + // hack: Call UpdateUserStatus to get API-formatted timestamps (without timezones) + // instead of database-formatted timestamps (with timezones) for comparison + sdkUser3, err := client.UpdateUserStatus(ctx, user3.ID.String(), codersdk.UserStatusActive) + require.NoError(t, err) + + orgMember, err = api.Database.InsertOrganizationMember(dbauthz.AsSystemRestricted(ctx), database.InsertOrganizationMemberParams{ + OrganizationID: firstUser.OrganizationIDs[0], + UserID: user3.ID, + Roles: []string{}, + CreatedAt: dbtime.Now(), + }) + require.NoError(t, err) + + sdkUser3.OrganizationIDs = []uuid.UUID{orgMember.OrganizationID} + users = append(users, sdkUser3) } // --- Setup done --- From 0f2104d489cadffcf9c3a9bcf14208478d547ada Mon Sep 17 00:00:00 2001 From: joobisb Date: Mon, 16 Dec 2024 13:56:48 +0530 Subject: [PATCH 10/14] fix lint --- coderd/users_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/coderd/users_test.go b/coderd/users_test.go index a8de254257145..bb990ae027f44 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1526,6 +1526,7 @@ func TestUsersFilter(t *testing.T) { }) require.NoError(t, err) + // nolint:gocritic //Using system context is necessary to seed data in tests orgMember, err := api.Database.InsertOrganizationMember(dbauthz.AsSystemRestricted(ctx), database.InsertOrganizationMemberParams{ OrganizationID: firstUser.OrganizationIDs[0], UserID: user1.ID, @@ -1558,6 +1559,7 @@ func TestUsersFilter(t *testing.T) { sdkUser2, err := client.UpdateUserStatus(ctx, user2.ID.String(), codersdk.UserStatusActive) require.NoError(t, err) + // nolint:gocritic //Using system context is necessary to seed data in tests orgMember, err = api.Database.InsertOrganizationMember(dbauthz.AsSystemRestricted(ctx), database.InsertOrganizationMemberParams{ OrganizationID: firstUser.OrganizationIDs[0], UserID: user2.ID, @@ -1585,6 +1587,7 @@ func TestUsersFilter(t *testing.T) { sdkUser3, err := client.UpdateUserStatus(ctx, user3.ID.String(), codersdk.UserStatusActive) require.NoError(t, err) + // nolint:gocritic //Using system context is necessary to seed data in tests orgMember, err = api.Database.InsertOrganizationMember(dbauthz.AsSystemRestricted(ctx), database.InsertOrganizationMemberParams{ OrganizationID: firstUser.OrganizationIDs[0], UserID: user3.ID, From f00b0d568564c0ab3c50b661051056434e23d982 Mon Sep 17 00:00:00 2001 From: joobisb Date: Mon, 16 Dec 2024 23:42:11 +0530 Subject: [PATCH 11/14] fix timezone --- coderd/users_test.go | 58 +++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 39 deletions(-) diff --git a/coderd/users_test.go b/coderd/users_test.go index a28d1b3d9d512..ccfd5c1c8cb31 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -26,6 +26,7 @@ import ( "github.com/coder/coder/v2/coderd/audit" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbfake" "github.com/coder/coder/v2/coderd/database/dbgen" @@ -1523,26 +1524,19 @@ func TestUsersFilter(t *testing.T) { Email: fmt.Sprintf("before%d@coder.com", i), Username: fmt.Sprintf("before%d", i), LoginType: database.LoginTypeNone, + Status: string(codersdk.UserStatusActive), RBACRoles: []string{codersdk.RoleMember}, CreatedAt: dbtime.Time(time.Date(2022, 12, 15+i, 12, 0, 0, 0, time.UTC)), }) require.NoError(t, err) - // nolint:gocritic //Using system context is necessary to seed data in tests - orgMember, err := api.Database.InsertOrganizationMember(dbauthz.AsSystemRestricted(ctx), database.InsertOrganizationMemberParams{ - OrganizationID: firstUser.OrganizationIDs[0], - UserID: user1.ID, - Roles: []string{}, - CreatedAt: dbtime.Now(), - }) + sdkUser1 := db2sdk.User(user1, []uuid.UUID{}) + sdkUser1.CreatedAt, err = time.Parse(time.RFC3339, sdkUser1.CreatedAt.Format(time.RFC3339)) require.NoError(t, err) - - // hack: Call UpdateUserStatus to get API-formatted timestamps (without timezones) - // instead of database-formatted timestamps (with timezones) for comparison - sdkUser1, err := client.UpdateUserStatus(ctx, user1.ID.String(), codersdk.UserStatusActive) + sdkUser1.UpdatedAt, err = time.Parse(time.RFC3339, sdkUser1.UpdatedAt.Format(time.RFC3339)) + require.NoError(t, err) + sdkUser1.LastSeenAt, err = time.Parse(time.RFC3339, sdkUser1.LastSeenAt.Format(time.RFC3339)) require.NoError(t, err) - - sdkUser1.OrganizationIDs = []uuid.UUID{orgMember.OrganizationID} users = append(users, sdkUser1) // nolint:gocritic //Using system context is necessary to seed data in tests @@ -1551,26 +1545,19 @@ func TestUsersFilter(t *testing.T) { Email: fmt.Sprintf("during%d@coder.com", i), Username: fmt.Sprintf("during%d", i), LoginType: database.LoginTypeNone, + Status: string(codersdk.UserStatusActive), RBACRoles: []string{codersdk.RoleOwner}, CreatedAt: dbtime.Time(time.Date(2023, 1, 15+i, 12, 0, 0, 0, time.UTC)), }) require.NoError(t, err) - // hack: Call UpdateUserStatus to get API-formatted timestamps (without timezones) - // instead of database-formatted timestamps (with timezones) for comparison - sdkUser2, err := client.UpdateUserStatus(ctx, user2.ID.String(), codersdk.UserStatusActive) + sdkUser2 := db2sdk.User(user2, []uuid.UUID{}) + sdkUser2.CreatedAt, err = time.Parse(time.RFC3339, sdkUser2.CreatedAt.Format(time.RFC3339)) require.NoError(t, err) - - // nolint:gocritic //Using system context is necessary to seed data in tests - orgMember, err = api.Database.InsertOrganizationMember(dbauthz.AsSystemRestricted(ctx), database.InsertOrganizationMemberParams{ - OrganizationID: firstUser.OrganizationIDs[0], - UserID: user2.ID, - Roles: []string{}, - CreatedAt: dbtime.Now(), - }) + sdkUser2.UpdatedAt, err = time.Parse(time.RFC3339, sdkUser2.UpdatedAt.Format(time.RFC3339)) + require.NoError(t, err) + sdkUser2.LastSeenAt, err = time.Parse(time.RFC3339, sdkUser2.LastSeenAt.Format(time.RFC3339)) require.NoError(t, err) - - sdkUser2.OrganizationIDs = []uuid.UUID{orgMember.OrganizationID} users = append(users, sdkUser2) // nolint:gocritic // Using system context is necessary to seed data in tests @@ -1579,26 +1566,19 @@ func TestUsersFilter(t *testing.T) { Email: fmt.Sprintf("after%d@coder.com", i), Username: fmt.Sprintf("after%d", i), LoginType: database.LoginTypeNone, + Status: string(codersdk.UserStatusActive), RBACRoles: []string{codersdk.RoleOwner}, CreatedAt: dbtime.Time(time.Date(2023, 2, 15+i, 12, 0, 0, 0, time.UTC)), }) require.NoError(t, err) - // hack: Call UpdateUserStatus to get API-formatted timestamps (without timezones) - // instead of database-formatted timestamps (with timezones) for comparison - sdkUser3, err := client.UpdateUserStatus(ctx, user3.ID.String(), codersdk.UserStatusActive) + sdkUser3 := db2sdk.User(user3, []uuid.UUID{}) + sdkUser3.CreatedAt, err = time.Parse(time.RFC3339, sdkUser3.CreatedAt.Format(time.RFC3339)) require.NoError(t, err) - - // nolint:gocritic //Using system context is necessary to seed data in tests - orgMember, err = api.Database.InsertOrganizationMember(dbauthz.AsSystemRestricted(ctx), database.InsertOrganizationMemberParams{ - OrganizationID: firstUser.OrganizationIDs[0], - UserID: user3.ID, - Roles: []string{}, - CreatedAt: dbtime.Now(), - }) + sdkUser3.UpdatedAt, err = time.Parse(time.RFC3339, sdkUser3.UpdatedAt.Format(time.RFC3339)) + require.NoError(t, err) + sdkUser3.LastSeenAt, err = time.Parse(time.RFC3339, sdkUser3.LastSeenAt.Format(time.RFC3339)) require.NoError(t, err) - - sdkUser3.OrganizationIDs = []uuid.UUID{orgMember.OrganizationID} users = append(users, sdkUser3) } From 049aafad8b4fe7023da96b2fdb17f71710c5cb60 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 17 Dec 2024 03:00:31 +0000 Subject: [PATCH 12/14] use nil org ID slice to compare equal --- coderd/users_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/coderd/users_test.go b/coderd/users_test.go index ccfd5c1c8cb31..78563a406e184 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1530,7 +1530,8 @@ func TestUsersFilter(t *testing.T) { }) require.NoError(t, err) - sdkUser1 := db2sdk.User(user1, []uuid.UUID{}) + // The expected timestamps must be parsed from strings to compare equal during `ElementsMatch` + sdkUser1 := db2sdk.User(user1, nil) sdkUser1.CreatedAt, err = time.Parse(time.RFC3339, sdkUser1.CreatedAt.Format(time.RFC3339)) require.NoError(t, err) sdkUser1.UpdatedAt, err = time.Parse(time.RFC3339, sdkUser1.UpdatedAt.Format(time.RFC3339)) @@ -1551,7 +1552,7 @@ func TestUsersFilter(t *testing.T) { }) require.NoError(t, err) - sdkUser2 := db2sdk.User(user2, []uuid.UUID{}) + sdkUser2 := db2sdk.User(user2, nil) sdkUser2.CreatedAt, err = time.Parse(time.RFC3339, sdkUser2.CreatedAt.Format(time.RFC3339)) require.NoError(t, err) sdkUser2.UpdatedAt, err = time.Parse(time.RFC3339, sdkUser2.UpdatedAt.Format(time.RFC3339)) @@ -1572,7 +1573,7 @@ func TestUsersFilter(t *testing.T) { }) require.NoError(t, err) - sdkUser3 := db2sdk.User(user3, []uuid.UUID{}) + sdkUser3 := db2sdk.User(user3, nil) sdkUser3.CreatedAt, err = time.Parse(time.RFC3339, sdkUser3.CreatedAt.Format(time.RFC3339)) require.NoError(t, err) sdkUser3.UpdatedAt, err = time.Parse(time.RFC3339, sdkUser3.UpdatedAt.Format(time.RFC3339)) From 981656f12fe18f90d6ddf7667add9cbfc54061f7 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 17 Dec 2024 03:26:42 +0000 Subject: [PATCH 13/14] use nil org ID slice on dbmem API to compare equal --- coderd/users_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/coderd/users_test.go b/coderd/users_test.go index 78563a406e184..0ccffb4b729bd 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -30,6 +30,7 @@ import ( "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbfake" "github.com/coder/coder/v2/coderd/database/dbgen" + "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/util/ptr" @@ -1776,6 +1777,17 @@ func TestUsersFilter(t *testing.T) { exp = append(exp, made) } } + + // TODO: This can be removed with dbmem + if !dbtestutil.WillUsePostgres() { + for i := range matched.Users { + if len(matched.Users[i].OrganizationIDs) == 0 { + matched.Users[i].OrganizationIDs = nil + } + + } + } + require.ElementsMatch(t, exp, matched.Users, "expected users returned") }) } From de63832c042258e1eb29a3440d5b0dfe023e46d5 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 17 Dec 2024 03:37:33 +0000 Subject: [PATCH 14/14] fmt --- coderd/users_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/coderd/users_test.go b/coderd/users_test.go index 0ccffb4b729bd..1386d76f3e0bf 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1784,7 +1784,6 @@ func TestUsersFilter(t *testing.T) { if len(matched.Users[i].OrganizationIDs) == 0 { matched.Users[i].OrganizationIDs = nil } - } }