Skip to content

feat: support created_at filter for the GET /users endpoint #15633

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Dec 17, 2024

Conversation

joobisb
Copy link
Contributor

@joobisb joobisb commented Nov 22, 2024

Closes #12747

We support these filters currently: https://coder.com/docs/v2/latest/admin/users#user-filtering, adding created_at filter as well.

@cdr-bot cdr-bot bot added the community Pull Requests and issues created by the community. label Nov 22, 2024
@joobisb
Copy link
Contributor Author

joobisb commented Nov 22, 2024

@ericpaulsen @matifali could you please take a look at this?

@ethanndickson
Copy link
Member

ethanndickson commented Nov 25, 2024

Thanks for the PR!

Whilst this technically addresses the linked issue, I'm not sure how useful it is if it doesn't support date ranges. I think it'd make more sense to implement it in terms of created_at_before and created_at_after, as we do for last_seen_*. You could obviously then use a combination of the two to retrieve users created on a specific day.

Although not mentioned in the issue, we'll also want to support this filter on the frontend (a separate PR for that is fine), on the deployment/users page.

@joobisb
Copy link
Contributor Author

joobisb commented Nov 28, 2024

Thanks for the PR!

Whilst this technically addresses the linked issue, I'm not sure how useful it is if it doesn't support date ranges. I think it'd make more sense to implement it in terms of created_at_before and created_at_after, as we do for last_seen_*. You could obviously then use a combination of the two to retrieve users created on a specific day.

Although not mentioned in the issue, we'll also want to support this filter on the frontend (a separate PR for that is fine), on the deployment/users page.

@ethanndickson sure! will update the PR

@joobisb
Copy link
Contributor Author

joobisb commented Dec 4, 2024

Thanks for the PR!

Whilst this technically addresses the linked issue, I'm not sure how useful it is if it doesn't support date ranges. I think it'd make more sense to implement it in terms of created_at_before and created_at_after, as we do for last_seen_*. You could obviously then use a combination of the two to retrieve users created on a specific day.

Although not mentioned in the issue, we'll also want to support this filter on the frontend (a separate PR for that is fine), on the deployment/users page.

@ethanndickson I've updated the PR to support date ranges. Also I don't think we need to make any changes in the frontend as the params are directly passed on to the backend and is working fine as shown below.

Please do have a look at the PR.

image

Copy link
Member

@ethanndickson ethanndickson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just one comment on naming, sorry for misleading you there!

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think created_at_{after|before} reads very well. I didn't really think about the phrasing when I wrote it earlier. Let's just go with created_before and created_after.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ethanndickson

updated the PR!

Name: "CreatedAtRange",
Filter: codersdk.UsersRequest{
SearchQuery: `created_after:"2023-01-01T00:00:00Z" created_before:"2023-01-31T23:59:59Z"`,
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the users created in the setup of this test have the same creation date. We should add some users created within the range, and before the CreatedBefore testcase timestamp. Right now those testcases are just comparing empty slices.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ethanndickson

I've updated the PR with the tests, please do have a look

Status: codersdk.UserStatusSuspended + "," + codersdk.UserStatusActive,
},
Name: "All",
Filter: codersdk.UsersRequest{},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated this since "All" filter should include all the users

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this is done twice, I assume this was intentional. Just a sanity check that all users are either suspended or active. I think we should just keep the original.

@@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the request, status is filtered with UserStatusSuspended and UserStatusActive, handling the same here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, let's keep the original as it was almost certainly purposeful.

Copy link
Member

@ethanndickson ethanndickson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few comments then I'm happy to approve!

Status: codersdk.UserStatusSuspended + "," + codersdk.UserStatusActive,
},
Name: "All",
Filter: codersdk.UsersRequest{},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this is done twice, I assume this was intentional. Just a sanity check that all users are either suspended or active. I think we should just keep the original.

@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, let's keep the original as it was almost certainly purposeful.

@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each of these nolint should mention that the system context is necessary here. (Also to make it clear which specific rule this is nolinting)

@joobisb
Copy link
Contributor Author

joobisb commented Dec 10, 2024

Few comments then I'm happy to approve!

@ethanndickson addressed the comments!

Copy link
Member

@ethanndickson ethanndickson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last comment, thanks for your patience!

@@ -2273,6 +2346,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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We fortunately already have this function, db2sdk.User. It takes a database.User and a slice of organization IDs. In this case that slice should just be []uuid.UUID{first.OrganizationID})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for pointing this out!

@joobisb
Copy link
Contributor Author

joobisb commented Dec 11, 2024

Last comment, thanks for your patience!

@ethanndickson Done!

Comment on lines 1752 to 1753
fmt.Printf("expexp: %+v\n", exp)
fmt.Printf("matched.Usersmatched.Users: %+v\n", matched.Users)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fmt.Printf("expexp: %+v\n", exp)
fmt.Printf("matched.Usersmatched.Users: %+v\n", matched.Users)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my bad, fixed it!

@ethanndickson
Copy link
Member

Looks like the test is failing on Postgres.

The expected list of users have timestamps with time zones, while the actual list of users retrieved via the API has timestamps parsed from strings, which don’t have timezones. I’m not sure of the best way to fix that..

@joobisb
Copy link
Contributor Author

joobisb commented Dec 14, 2024

Looks like the test is failing on Postgres.

The expected list of users have timestamps with time zones, while the actual list of users retrieved via the API has timestamps parsed from strings, which don’t have timezones. I’m not sure of the best way to fix that..

@ethanndickson put together a little hack to fix this. Instead of using the db object, call an API to update the status and used that user object to be added to the list of expected users. Please do have a look.

})
require.NoError(t, err)

// hack: Call UpdateUserStatus to get API-formatted timestamps (without timezones)
Copy link
Member

@ethanndickson ethanndickson Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's avoid all this extra work & extra DB queries if we can.

For each of these, we can round-trip just the timestamps.

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))
require.NoError(t, err)
sdkUser1.LastSeenAt, err = time.Parse(time.RFC3339, sdkUser1.LastSeenAt.Format(time.RFC3339))
require.NoError(t, err)
users = append(users, sdkUser1)

I don't think we'll find a better way. I wish the API just let you manually enter this timezoneless state.

@ethanndickson
Copy link
Member

Yep, definitely unrelated. Will fix.

@ethanndickson
Copy link
Member

Should be fixed after a merge from main!

@@ -1677,6 +1777,17 @@ func TestUsersFilter(t *testing.T) {
exp = append(exp, made)
}
}

// TODO: This can be removed with dbmem
if !dbtestutil.WillUsePostgres() {
Copy link
Member

@ethanndickson ethanndickson Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plan is to eventually get rid of dbmem entirely, so I'm not too worried about cluttering up this test. I've used dbtestutil.WillUsePostgres as it's more likely to found and removed when that happens.

This is to handle a disparity between organization-less members in Postgres and dbmem. For dbmem, the API returns an empty slice. For postgres it returns nil. (FWIW it's not possible to have organization-less members on a real Coder deployment.)

Copy link
Member

@ethanndickson ethanndickson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution and your patience through the review cycle! Greatly appreciated!

@ethanndickson ethanndickson merged commit e191d96 into coder:main Dec 17, 2024
30 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Dec 17, 2024
@joobisb joobisb deleted the issue#12747 branch December 17, 2024 12:02
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
community Pull Requests and issues created by the community.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

support created_at filter for the GET /users endpoint
3 participants