Skip to content

feat(site): display user status counts over time as an indicator of license usage #15893

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

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
69e4a09
add user_status_changes table
SasSwart Dec 14, 2024
0913355
add GetUserStatusCountsByDay
SasSwart Dec 14, 2024
d952af0
rename unused variable
SasSwart Dec 14, 2024
34ac634
Test GetUserStatusCountsByDay
SasSwart Dec 17, 2024
d5d3021
make gen
SasSwart Dec 17, 2024
c6b50af
fix dbauthz tests
SasSwart Dec 17, 2024
b2fb346
do the plumbing to get sql, api and frontend talking to one another
SasSwart Dec 23, 2024
ad42c16
rename migration
SasSwart Dec 23, 2024
ed86820
move aggregation logic for GetUserStatusChanges into the SQL
SasSwart Dec 24, 2024
0f17038
use window functions for efficiency
SasSwart Dec 24, 2024
1b3976d
ensure we use the same time zone as the start_time param
SasSwart Dec 24, 2024
e2d0d15
ensure we use the same time zone as the start_time param
SasSwart Dec 24, 2024
7fdd9a5
Merge remote-tracking branch 'origin/main' into jjs/dau-history
SasSwart Dec 24, 2024
aed4132
make gen
SasSwart Dec 24, 2024
9c65856
update field names and fix tests
SasSwart Dec 24, 2024
d6c5a4f
exclude deleted users from the user status graph
SasSwart Dec 27, 2024
d1c5baf
Merge remote-tracking branch 'origin/main' into jjs/dau-history
SasSwart Dec 30, 2024
fa66382
GetUserStatusChanges now passes all querier tests
SasSwart Jan 2, 2025
a8c125c
renumber migrations
SasSwart Jan 2, 2025
726bcba
add partial fixture for CI
SasSwart Jan 3, 2025
8bcbe03
fix migration numbers
SasSwart Jan 3, 2025
494f165
rename and document sql function
SasSwart Jan 3, 2025
dbebf0b
revert backend changes for now. so that we can review and merge the d…
SasSwart Jan 3, 2025
e85bc1f
Merge remote-tracking branch 'origin/main' into jjs/dau-history-backend
SasSwart Jan 3, 2025
e738a0e
make gen
SasSwart Jan 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -2413,6 +2413,13 @@ func (q *querier) GetUserNotificationPreferences(ctx context.Context, userID uui
return q.db.GetUserNotificationPreferences(ctx, userID)
}

func (q *querier) GetUserStatusCountsByDay(ctx context.Context, arg database.GetUserStatusCountsByDayParams) ([]database.GetUserStatusCountsByDayRow, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceUser); err != nil {
return nil, err
}
return q.db.GetUserStatusCountsByDay(ctx, arg)
}

func (q *querier) GetUserWorkspaceBuildParameters(ctx context.Context, params database.GetUserWorkspaceBuildParametersParams) ([]database.GetUserWorkspaceBuildParametersRow, error) {
u, err := q.db.GetUserByID(ctx, params.OwnerID)
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions coderd/database/dbauthz/dbauthz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1490,6 +1490,12 @@ func (s *MethodTestSuite) TestUser() {
rbac.ResourceTemplate.InOrg(orgID), policy.ActionRead,
)
}))
s.Run("GetUserStatusCountsByDay", s.Subtest(func(db database.Store, check *expects) {
check.Args(database.GetUserStatusCountsByDayParams{
StartTime: time.Now().Add(-time.Hour * 24 * 30),
EndTime: time.Now(),
}).Asserts(rbac.ResourceUser, policy.ActionRead)
}))
}

func (s *MethodTestSuite) TestWorkspace() {
Expand Down
46 changes: 46 additions & 0 deletions coderd/database/dbmem/dbmem.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func New() database.Store {
customRoles: make([]database.CustomRole, 0),
locks: map[int64]struct{}{},
runtimeConfig: map[string]string{},
userStatusChanges: make([]database.UserStatusChange, 0),
},
}
// Always start with a default org. Matching migration 198.
Expand Down Expand Up @@ -256,6 +257,7 @@ type data struct {
lastLicenseID int32
defaultProxyDisplayName string
defaultProxyIconURL string
userStatusChanges []database.UserStatusChange
}

func tryPercentile(fs []float64, p float64) float64 {
Expand Down Expand Up @@ -5664,6 +5666,32 @@ func (q *FakeQuerier) GetUserNotificationPreferences(_ context.Context, userID u
return out, nil
}

func (q *FakeQuerier) GetUserStatusCountsByDay(_ context.Context, arg database.GetUserStatusCountsByDayParams) ([]database.GetUserStatusCountsByDayRow, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()

err := validateDatabaseType(arg)
if err != nil {
return nil, err
}

result := make([]database.GetUserStatusCountsByDayRow, 0)
for _, change := range q.userStatusChanges {
if change.ChangedAt.Before(arg.StartTime) || change.ChangedAt.After(arg.EndTime) {
continue
}
result = append(result, database.GetUserStatusCountsByDayRow{
Status: database.NullUserStatus{
UserStatus: change.NewStatus,
Valid: true,
},
Count: 1,
})
}

return result, nil
}

func (q *FakeQuerier) GetUserWorkspaceBuildParameters(_ context.Context, params database.GetUserWorkspaceBuildParametersParams) ([]database.GetUserWorkspaceBuildParametersRow, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
Expand Down Expand Up @@ -7996,6 +8024,12 @@ func (q *FakeQuerier) InsertUser(_ context.Context, arg database.InsertUserParam
sort.Slice(q.users, func(i, j int) bool {
return q.users[i].CreatedAt.Before(q.users[j].CreatedAt)
})

q.userStatusChanges = append(q.userStatusChanges, database.UserStatusChange{
UserID: user.ID,
NewStatus: user.Status,
ChangedAt: user.UpdatedAt,
})
return user, nil
}

Expand Down Expand Up @@ -9032,12 +9066,18 @@ func (q *FakeQuerier) UpdateInactiveUsersToDormant(_ context.Context, params dat
Username: user.Username,
LastSeenAt: user.LastSeenAt,
})
q.userStatusChanges = append(q.userStatusChanges, database.UserStatusChange{
UserID: user.ID,
NewStatus: database.UserStatusDormant,
ChangedAt: params.UpdatedAt,
})
}
}

if len(updated) == 0 {
return nil, sql.ErrNoRows
}

return updated, nil
}

Expand Down Expand Up @@ -9838,6 +9878,12 @@ func (q *FakeQuerier) UpdateUserStatus(_ context.Context, arg database.UpdateUse
user.Status = arg.Status
user.UpdatedAt = arg.UpdatedAt
q.users[index] = user

q.userStatusChanges = append(q.userStatusChanges, database.UserStatusChange{
UserID: user.ID,
NewStatus: user.Status,
ChangedAt: user.UpdatedAt,
})
return user, nil
}
return database.User{}, sql.ErrNoRows
Expand Down
7 changes: 7 additions & 0 deletions coderd/database/dbmetrics/querymetrics.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions coderd/database/dbmock/dbmock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions coderd/database/dump.sql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions coderd/database/foreign_key_constraint.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions coderd/database/migrations/000279_user_status_changes.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- Drop the trigger first
DROP TRIGGER IF EXISTS user_status_change_trigger ON users;

-- Drop the trigger function
DROP FUNCTION IF EXISTS record_user_status_change();

-- Drop the indexes
DROP INDEX IF EXISTS idx_user_status_changes_changed_at;

-- Drop the table
DROP TABLE IF EXISTS user_status_changes;
44 changes: 44 additions & 0 deletions coderd/database/migrations/000279_user_status_changes.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
CREATE TABLE user_status_changes (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id),
new_status user_status NOT NULL,
changed_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);

COMMENT ON TABLE user_status_changes IS 'Tracks the history of user status changes';

CREATE INDEX idx_user_status_changes_changed_at ON user_status_changes(changed_at);

INSERT INTO user_status_changes (
user_id,
new_status,
changed_at
)
SELECT
id,
status,
created_at
FROM users
WHERE NOT deleted;

CREATE OR REPLACE FUNCTION record_user_status_change() RETURNS trigger AS $$
BEGIN
IF TG_OP = 'INSERT' OR OLD.status IS DISTINCT FROM NEW.status THEN
INSERT INTO user_status_changes (
user_id,
new_status,
changed_at
) VALUES (
NEW.id,
NEW.status,
NEW.updated_at
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER user_status_change_trigger
AFTER INSERT OR UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION record_user_status_change();
8 changes: 8 additions & 0 deletions coderd/database/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions coderd/database/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading