mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat(coderd/database): track user status changes over time (#16019)
RE: https://github.com/coder/coder/issues/15740, https://github.com/coder/coder/issues/15297 In order to add a graph to the coder frontend to show user status over time as an indicator of license usage, this PR adds the following: * a new `api.insightsUserStatusCountsOverTime` endpoint to the API * which calls a new `GetUserStatusCountsOverTime` query from postgres * which relies on two new tables `user_status_changes` and `user_deleted` * which are populated by a new trigger and function that tracks updates to the users table The chart itself will be added in a subsequent PR --------- Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
This commit is contained in:
@ -292,6 +292,69 @@ func (api *API) insightsUserLatency(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(ctx, rw, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// @Summary Get insights about user status counts
|
||||
// @ID get-insights-about-user-status-counts
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Insights
|
||||
// @Param tz_offset query int true "Time-zone offset (e.g. -2)"
|
||||
// @Success 200 {object} codersdk.GetUserStatusCountsResponse
|
||||
// @Router /insights/user-status-counts [get]
|
||||
func (api *API) insightsUserStatusCounts(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
p := httpapi.NewQueryParamParser()
|
||||
vals := r.URL.Query()
|
||||
tzOffset := p.Int(vals, 0, "tz_offset")
|
||||
p.ErrorExcessParams(vals)
|
||||
|
||||
if len(p.Errors) > 0 {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Query parameters have invalid values.",
|
||||
Validations: p.Errors,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
loc := time.FixedZone("", tzOffset*3600)
|
||||
// If the time is 14:01 or 14:31, we still want to include all the
|
||||
// data between 14:00 and 15:00. Our rollups buckets are 30 minutes
|
||||
// so this works nicely. It works just as well for 23:59 as well.
|
||||
nextHourInLoc := time.Now().In(loc).Truncate(time.Hour).Add(time.Hour)
|
||||
// Always return 60 days of data (2 months).
|
||||
sixtyDaysAgo := nextHourInLoc.In(loc).Truncate(24*time.Hour).AddDate(0, 0, -60)
|
||||
|
||||
rows, err := api.Database.GetUserStatusCounts(ctx, database.GetUserStatusCountsParams{
|
||||
StartTime: sixtyDaysAgo,
|
||||
EndTime: nextHourInLoc,
|
||||
})
|
||||
if err != nil {
|
||||
if httpapi.IsUnauthorizedError(err) {
|
||||
httpapi.Forbidden(rw)
|
||||
return
|
||||
}
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching user status counts over time.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
resp := codersdk.GetUserStatusCountsResponse{
|
||||
StatusCounts: make(map[codersdk.UserStatus][]codersdk.UserStatusChangeCount),
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
status := codersdk.UserStatus(row.Status)
|
||||
resp.StatusCounts[status] = append(resp.StatusCounts[status], codersdk.UserStatusChangeCount{
|
||||
Date: row.Date,
|
||||
Count: row.Count,
|
||||
})
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// @Summary Get insights about templates
|
||||
// @ID get-insights-about-templates
|
||||
// @Security CoderSessionToken
|
||||
|
Reference in New Issue
Block a user