Skip to content

Commit d6e9870

Browse files
authored
feat: add "dormant" user state (#8644)
1 parent d2c7c8e commit d6e9870

40 files changed

+587
-239
lines changed

cli/server.go

+4
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import (
7070
"github.com/coder/coder/coderd/database/migrations"
7171
"github.com/coder/coder/coderd/database/pubsub"
7272
"github.com/coder/coder/coderd/devtunnel"
73+
"github.com/coder/coder/coderd/dormancy"
7374
"github.com/coder/coder/coderd/gitauth"
7475
"github.com/coder/coder/coderd/gitsshkey"
7576
"github.com/coder/coder/coderd/httpapi"
@@ -812,6 +813,9 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
812813
options.SwaggerEndpoint = cfg.Swagger.Enable.Value()
813814
}
814815

816+
closeCheckInactiveUsersFunc := dormancy.CheckInactiveUsers(ctx, logger, options.Database)
817+
defer closeCheckInactiveUsersFunc()
818+
815819
// We use a separate coderAPICloser so the Enterprise API
816820
// can have it's own close functions. This is cleaner
817821
// than abstracting the Coder API itself.

cli/testdata/coder_scaletest_--help.golden

-16
This file was deleted.

cli/testdata/coder_scaletest_cleanup_--help.golden

-19
This file was deleted.

cli/testdata/coder_scaletest_create-workspaces_--help.golden

-114
This file was deleted.

cli/testdata/coder_scaletest_workspace-traffic_--help.golden

-62
This file was deleted.

cli/testdata/coder_users_list_--output_json.golden

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"email": "testuser2@coder.com",
2525
"created_at": "[timestamp]",
2626
"last_seen_at": "[timestamp]",
27-
"status": "active",
27+
"status": "dormant",
2828
"organization_ids": [
2929
"[first org ID]"
3030
],

cli/userstatus_test.go

+11-7
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,13 @@ import (
1414

1515
func TestUserStatus(t *testing.T) {
1616
t.Parallel()
17-
client := coderdtest.New(t, nil)
18-
admin := coderdtest.CreateFirstUser(t, client)
19-
other, _ := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
20-
otherUser, err := other.User(context.Background(), codersdk.Me)
21-
require.NoError(t, err, "fetch user")
2217

2318
t.Run("StatusSelf", func(t *testing.T) {
2419
t.Parallel()
20+
21+
client := coderdtest.New(t, nil)
22+
coderdtest.CreateFirstUser(t, client)
23+
2524
inv, root := clitest.New(t, "users", "suspend", "me")
2625
clitest.SetupConfig(t, client, root)
2726
// Yes to the prompt
@@ -34,13 +33,18 @@ func TestUserStatus(t *testing.T) {
3433

3534
t.Run("StatusOther", func(t *testing.T) {
3635
t.Parallel()
37-
require.Equal(t, codersdk.UserStatusActive, otherUser.Status, "start as active")
36+
37+
client := coderdtest.New(t, nil)
38+
admin := coderdtest.CreateFirstUser(t, client)
39+
other, _ := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
40+
otherUser, err := other.User(context.Background(), codersdk.Me)
41+
require.NoError(t, err, "fetch user")
3842

3943
inv, root := clitest.New(t, "users", "suspend", otherUser.Username)
4044
clitest.SetupConfig(t, client, root)
4145
// Yes to the prompt
4246
inv.Stdin = bytes.NewReader([]byte("yes\n"))
43-
err := inv.Run()
47+
err = inv.Run()
4448
require.NoError(t, err, "suspend user")
4549

4650
// Check the user status

coderd/apidoc/docs.go

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

coderd/apidoc/swagger.json

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

coderd/coderdtest/coderdtest.go

+8
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,14 @@ func createAnotherUserRetry(t *testing.T, client *codersdk.Client, organizationI
587587
sessionToken = token.Key
588588
}
589589

590+
if user.Status == codersdk.UserStatusDormant {
591+
// Use admin client so that user's LastSeenAt is not updated.
592+
// In general we need to refresh the user status, which should
593+
// transition from "dormant" to "active".
594+
user, err = client.User(context.Background(), user.Username)
595+
require.NoError(t, err)
596+
}
597+
590598
other := codersdk.New(client.URL)
591599
other.SetSessionToken(sessionToken)
592600
t.Cleanup(func() {

coderd/database/dbauthz/dbauthz.go

+7
Original file line numberDiff line numberDiff line change
@@ -2099,6 +2099,13 @@ func (q *querier) UpdateGroupByID(ctx context.Context, arg database.UpdateGroupB
20992099
return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateGroupByID)(ctx, arg)
21002100
}
21012101

2102+
func (q *querier) UpdateInactiveUsersToDormant(ctx context.Context, lastSeenAfter database.UpdateInactiveUsersToDormantParams) ([]database.UpdateInactiveUsersToDormantRow, error) {
2103+
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
2104+
return nil, err
2105+
}
2106+
return q.db.UpdateInactiveUsersToDormant(ctx, lastSeenAfter)
2107+
}
2108+
21022109
func (q *querier) UpdateMemberRoles(ctx context.Context, arg database.UpdateMemberRolesParams) (database.OrganizationMember, error) {
21032110
// Authorized fetch will check that the actor has read access to the org member since the org member is returned.
21042111
member, err := q.GetOrganizationMemberByUserID(ctx, database.GetOrganizationMemberByUserIDParams{

coderd/database/dbfake/dbfake.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -3862,7 +3862,7 @@ func (q *FakeQuerier) InsertUser(_ context.Context, arg database.InsertUserParam
38623862
CreatedAt: arg.CreatedAt,
38633863
UpdatedAt: arg.UpdatedAt,
38643864
Username: arg.Username,
3865-
Status: database.UserStatusActive,
3865+
Status: database.UserStatusDormant,
38663866
RBACRoles: arg.RBACRoles,
38673867
LoginType: arg.LoginType,
38683868
}
@@ -4337,6 +4337,29 @@ func (q *FakeQuerier) UpdateGroupByID(_ context.Context, arg database.UpdateGrou
43374337
return database.Group{}, sql.ErrNoRows
43384338
}
43394339

4340+
func (q *FakeQuerier) UpdateInactiveUsersToDormant(_ context.Context, params database.UpdateInactiveUsersToDormantParams) ([]database.UpdateInactiveUsersToDormantRow, error) {
4341+
q.mutex.Lock()
4342+
defer q.mutex.Unlock()
4343+
4344+
var updated []database.UpdateInactiveUsersToDormantRow
4345+
for index, user := range q.users {
4346+
if user.Status == database.UserStatusActive && user.LastSeenAt.Before(params.LastSeenAfter) {
4347+
q.users[index].Status = database.UserStatusDormant
4348+
q.users[index].UpdatedAt = params.UpdatedAt
4349+
updated = append(updated, database.UpdateInactiveUsersToDormantRow{
4350+
ID: user.ID,
4351+
Email: user.Email,
4352+
LastSeenAt: user.LastSeenAt,
4353+
})
4354+
}
4355+
}
4356+
4357+
if len(updated) == 0 {
4358+
return nil, sql.ErrNoRows
4359+
}
4360+
return updated, nil
4361+
}
4362+
43404363
func (q *FakeQuerier) UpdateMemberRoles(_ context.Context, arg database.UpdateMemberRolesParams) (database.OrganizationMember, error) {
43414364
if err := validateDatabaseType(arg); err != nil {
43424365
return database.OrganizationMember{}, err

coderd/database/dbgen/dbgen.go

+7
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,13 @@ func User(t testing.TB, db database.Store, orig database.User) database.User {
224224
})
225225
require.NoError(t, err, "insert user")
226226

227+
user, err = db.UpdateUserStatus(genCtx, database.UpdateUserStatusParams{
228+
ID: user.ID,
229+
Status: database.UserStatusActive,
230+
UpdatedAt: database.Now(),
231+
})
232+
require.NoError(t, err, "insert user")
233+
227234
if !orig.LastSeenAt.IsZero() {
228235
user, err = db.UpdateUserLastSeenAt(genCtx, database.UpdateUserLastSeenAtParams{
229236
ID: user.ID,

0 commit comments

Comments
 (0)