feat: add "dormant" user state (#8644)

This commit is contained in:
Marcin Tojek
2023-08-02 16:31:25 +02:00
committed by GitHub
parent d2c7c8e1d8
commit d6e9870209
40 changed files with 587 additions and 239 deletions
+7
View File
@@ -2099,6 +2099,13 @@ func (q *querier) UpdateGroupByID(ctx context.Context, arg database.UpdateGroupB
return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateGroupByID)(ctx, arg)
}
func (q *querier) UpdateInactiveUsersToDormant(ctx context.Context, lastSeenAfter database.UpdateInactiveUsersToDormantParams) ([]database.UpdateInactiveUsersToDormantRow, error) {
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
return nil, err
}
return q.db.UpdateInactiveUsersToDormant(ctx, lastSeenAfter)
}
func (q *querier) UpdateMemberRoles(ctx context.Context, arg database.UpdateMemberRolesParams) (database.OrganizationMember, error) {
// Authorized fetch will check that the actor has read access to the org member since the org member is returned.
member, err := q.GetOrganizationMemberByUserID(ctx, database.GetOrganizationMemberByUserIDParams{
+24 -1
View File
@@ -3862,7 +3862,7 @@ func (q *FakeQuerier) InsertUser(_ context.Context, arg database.InsertUserParam
CreatedAt: arg.CreatedAt,
UpdatedAt: arg.UpdatedAt,
Username: arg.Username,
Status: database.UserStatusActive,
Status: database.UserStatusDormant,
RBACRoles: arg.RBACRoles,
LoginType: arg.LoginType,
}
@@ -4337,6 +4337,29 @@ func (q *FakeQuerier) UpdateGroupByID(_ context.Context, arg database.UpdateGrou
return database.Group{}, sql.ErrNoRows
}
func (q *FakeQuerier) UpdateInactiveUsersToDormant(_ context.Context, params database.UpdateInactiveUsersToDormantParams) ([]database.UpdateInactiveUsersToDormantRow, error) {
q.mutex.Lock()
defer q.mutex.Unlock()
var updated []database.UpdateInactiveUsersToDormantRow
for index, user := range q.users {
if user.Status == database.UserStatusActive && user.LastSeenAt.Before(params.LastSeenAfter) {
q.users[index].Status = database.UserStatusDormant
q.users[index].UpdatedAt = params.UpdatedAt
updated = append(updated, database.UpdateInactiveUsersToDormantRow{
ID: user.ID,
Email: user.Email,
LastSeenAt: user.LastSeenAt,
})
}
}
if len(updated) == 0 {
return nil, sql.ErrNoRows
}
return updated, nil
}
func (q *FakeQuerier) UpdateMemberRoles(_ context.Context, arg database.UpdateMemberRolesParams) (database.OrganizationMember, error) {
if err := validateDatabaseType(arg); err != nil {
return database.OrganizationMember{}, err
+7
View File
@@ -224,6 +224,13 @@ func User(t testing.TB, db database.Store, orig database.User) database.User {
})
require.NoError(t, err, "insert user")
user, err = db.UpdateUserStatus(genCtx, database.UpdateUserStatusParams{
ID: user.ID,
Status: database.UserStatusActive,
UpdatedAt: database.Now(),
})
require.NoError(t, err, "insert user")
if !orig.LastSeenAt.IsZero() {
user, err = db.UpdateUserLastSeenAt(genCtx, database.UpdateUserLastSeenAtParams{
ID: user.ID,
+7
View File
@@ -1313,6 +1313,13 @@ func (m metricsStore) UpdateGroupByID(ctx context.Context, arg database.UpdateGr
return group, err
}
func (m metricsStore) UpdateInactiveUsersToDormant(ctx context.Context, lastSeenAfter database.UpdateInactiveUsersToDormantParams) ([]database.UpdateInactiveUsersToDormantRow, error) {
start := time.Now()
r0, r1 := m.s.UpdateInactiveUsersToDormant(ctx, lastSeenAfter)
m.queryLatencies.WithLabelValues("UpdateInactiveUsersToDormant").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m metricsStore) UpdateMemberRoles(ctx context.Context, arg database.UpdateMemberRolesParams) (database.OrganizationMember, error) {
start := time.Now()
member, err := m.s.UpdateMemberRoles(ctx, arg)
+15
View File
@@ -2775,6 +2775,21 @@ func (mr *MockStoreMockRecorder) UpdateGroupByID(arg0, arg1 interface{}) *gomock
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGroupByID", reflect.TypeOf((*MockStore)(nil).UpdateGroupByID), arg0, arg1)
}
// UpdateInactiveUsersToDormant mocks base method.
func (m *MockStore) UpdateInactiveUsersToDormant(arg0 context.Context, arg1 database.UpdateInactiveUsersToDormantParams) ([]database.UpdateInactiveUsersToDormantRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateInactiveUsersToDormant", arg0, arg1)
ret0, _ := ret[0].([]database.UpdateInactiveUsersToDormantRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateInactiveUsersToDormant indicates an expected call of UpdateInactiveUsersToDormant.
func (mr *MockStoreMockRecorder) UpdateInactiveUsersToDormant(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateInactiveUsersToDormant", reflect.TypeOf((*MockStore)(nil).UpdateInactiveUsersToDormant), arg0, arg1)
}
// UpdateMemberRoles mocks base method.
func (m *MockStore) UpdateMemberRoles(arg0 context.Context, arg1 database.UpdateMemberRolesParams) (database.OrganizationMember, error) {
m.ctrl.T.Helper()
+5 -2
View File
@@ -113,9 +113,12 @@ CREATE TYPE startup_script_behavior AS ENUM (
CREATE TYPE user_status AS ENUM (
'active',
'suspended'
'suspended',
'dormant'
);
COMMENT ON TYPE user_status IS 'Defines the user status: active, dormant, or suspended.';
CREATE TYPE workspace_agent_lifecycle_state AS ENUM (
'created',
'starting',
@@ -561,7 +564,7 @@ CREATE TABLE users (
hashed_password bytea NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
status user_status DEFAULT 'active'::user_status NOT NULL,
status user_status DEFAULT 'dormant'::user_status NOT NULL,
rbac_roles text[] DEFAULT '{}'::text[] NOT NULL,
login_type login_type DEFAULT 'password'::login_type NOT NULL,
avatar_url text,
@@ -0,0 +1,3 @@
-- It's not possible to drop enum values from enum types, so the UP has "IF NOT EXISTS"
UPDATE users SET status = 'active'::user_status WHERE status = 'dormant'::user_status;
@@ -0,0 +1,2 @@
ALTER TYPE user_status ADD VALUE IF NOT EXISTS 'dormant';
COMMENT ON TYPE user_status IS 'Defines the user status: active, dormant, or suspended.';
@@ -0,0 +1 @@
ALTER TABLE users ALTER COLUMN status SET DEFAULT 'active'::user_status;
@@ -0,0 +1 @@
ALTER TABLE users ALTER COLUMN status SET DEFAULT 'dormant'::user_status;
+5 -1
View File
@@ -1032,11 +1032,13 @@ func AllStartupScriptBehaviorValues() []StartupScriptBehavior {
}
}
// Defines the user status: active, dormant, or suspended.
type UserStatus string
const (
UserStatusActive UserStatus = "active"
UserStatusSuspended UserStatus = "suspended"
UserStatusDormant UserStatus = "dormant"
)
func (e *UserStatus) Scan(src interface{}) error {
@@ -1077,7 +1079,8 @@ func (ns NullUserStatus) Value() (driver.Value, error) {
func (e UserStatus) Valid() bool {
switch e {
case UserStatusActive,
UserStatusSuspended:
UserStatusSuspended,
UserStatusDormant:
return true
}
return false
@@ -1087,6 +1090,7 @@ func AllUserStatusValues() []UserStatus {
return []UserStatus{
UserStatusActive,
UserStatusSuspended,
UserStatusDormant,
}
}
+1
View File
@@ -236,6 +236,7 @@ type sqlcQuerier interface {
UpdateGitAuthLink(ctx context.Context, arg UpdateGitAuthLinkParams) (GitAuthLink, error)
UpdateGitSSHKey(ctx context.Context, arg UpdateGitSSHKeyParams) (GitSSHKey, error)
UpdateGroupByID(ctx context.Context, arg UpdateGroupByIDParams) (Group, error)
UpdateInactiveUsersToDormant(ctx context.Context, arg UpdateInactiveUsersToDormantParams) ([]UpdateInactiveUsersToDormantRow, error)
UpdateMemberRoles(ctx context.Context, arg UpdateMemberRolesParams) (OrganizationMember, error)
UpdateProvisionerJobByID(ctx context.Context, arg UpdateProvisionerJobByIDParams) error
UpdateProvisionerJobWithCancelByID(ctx context.Context, arg UpdateProvisionerJobWithCancelByIDParams) error
+46
View File
@@ -5708,6 +5708,52 @@ func (q *sqlQuerier) InsertUser(ctx context.Context, arg InsertUserParams) (User
return i, err
}
const updateInactiveUsersToDormant = `-- name: UpdateInactiveUsersToDormant :many
UPDATE
users
SET
status = 'dormant'::user_status,
updated_at = $1
WHERE
last_seen_at < $2 :: timestamp
AND status = 'active'::user_status
RETURNING id, email, last_seen_at
`
type UpdateInactiveUsersToDormantParams struct {
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
LastSeenAfter time.Time `db:"last_seen_after" json:"last_seen_after"`
}
type UpdateInactiveUsersToDormantRow struct {
ID uuid.UUID `db:"id" json:"id"`
Email string `db:"email" json:"email"`
LastSeenAt time.Time `db:"last_seen_at" json:"last_seen_at"`
}
func (q *sqlQuerier) UpdateInactiveUsersToDormant(ctx context.Context, arg UpdateInactiveUsersToDormantParams) ([]UpdateInactiveUsersToDormantRow, error) {
rows, err := q.db.QueryContext(ctx, updateInactiveUsersToDormant, arg.UpdatedAt, arg.LastSeenAfter)
if err != nil {
return nil, err
}
defer rows.Close()
var items []UpdateInactiveUsersToDormantRow
for rows.Next() {
var i UpdateInactiveUsersToDormantRow
if err := rows.Scan(&i.ID, &i.Email, &i.LastSeenAt); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateUserDeletedByID = `-- name: UpdateUserDeletedByID :exec
UPDATE
users
+12
View File
@@ -250,3 +250,15 @@ SET
WHERE
id = $1
RETURNING *;
-- name: UpdateInactiveUsersToDormant :many
UPDATE
users
SET
status = 'dormant'::user_status,
updated_at = @updated_at
WHERE
last_seen_at < @last_seen_after :: timestamp
AND status = 'active'::user_status
RETURNING id, email, last_seen_at;