feat(coderd): support weekly aggregated insights (#9684)

This commit is contained in:
Marcin Tojek 2023-09-19 13:06:19 +02:00 committed by GitHub
parent b358e3d558
commit b0e3daa120
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 680 additions and 24 deletions

13
coderd/apidoc/docs.go generated
View File

@ -8438,10 +8438,12 @@ const docTemplate = `{
"codersdk.InsightsReportInterval": {
"type": "string",
"enum": [
"day"
"day",
"week"
],
"x-enum-varnames": [
"InsightsReportIntervalDay"
"InsightsReportIntervalDay",
"InsightsReportIntervalWeek"
]
},
"codersdk.IssueReconnectingPTYSignedTokenRequest": {
@ -9732,7 +9734,12 @@ const docTemplate = `{
"format": "date-time"
},
"interval": {
"$ref": "#/definitions/codersdk.InsightsReportInterval"
"allOf": [
{
"$ref": "#/definitions/codersdk.InsightsReportInterval"
}
],
"example": "week"
},
"start_time": {
"type": "string",

View File

@ -7578,8 +7578,11 @@
},
"codersdk.InsightsReportInterval": {
"type": "string",
"enum": ["day"],
"x-enum-varnames": ["InsightsReportIntervalDay"]
"enum": ["day", "week"],
"x-enum-varnames": [
"InsightsReportIntervalDay",
"InsightsReportIntervalWeek"
]
},
"codersdk.IssueReconnectingPTYSignedTokenRequest": {
"type": "object",
@ -8794,7 +8797,12 @@
"format": "date-time"
},
"interval": {
"$ref": "#/definitions/codersdk.InsightsReportInterval"
"allOf": [
{
"$ref": "#/definitions/codersdk.InsightsReportInterval"
}
],
"example": "week"
},
"start_time": {
"type": "string",

View File

@ -7518,7 +7518,7 @@ func (q *sqlQuerier) UpdateWorkspaceAgentStartupByID(ctx context.Context, arg Up
}
const deleteOldWorkspaceAgentStats = `-- name: DeleteOldWorkspaceAgentStats :exec
DELETE FROM workspace_agent_stats WHERE created_at < NOW() - INTERVAL '30 days'
DELETE FROM workspace_agent_stats WHERE created_at < NOW() - INTERVAL '6 months'
`
func (q *sqlQuerier) DeleteOldWorkspaceAgentStats(ctx context.Context) error {

View File

@ -90,7 +90,7 @@ ORDER BY
date ASC;
-- name: DeleteOldWorkspaceAgentStats :exec
DELETE FROM workspace_agent_stats WHERE created_at < NOW() - INTERVAL '30 days';
DELETE FROM workspace_agent_stats WHERE created_at < NOW() - INTERVAL '6 months';
-- name: GetDeploymentWorkspaceAgentStats :one
WITH agent_stats AS (

View File

@ -184,7 +184,7 @@ func (api *API) insightsTemplates(rw http.ResponseWriter, r *http.Request) {
if !ok {
return
}
interval, ok := verifyInsightsInterval(ctx, rw, intervalString)
interval, ok := parseInsightsInterval(ctx, rw, intervalString, startTime, endTime)
if !ok {
return
}
@ -198,7 +198,7 @@ func (api *API) insightsTemplates(rw http.ResponseWriter, r *http.Request) {
eg.SetLimit(4)
// The following insights data queries have a theoretical chance to be
// inconsistent between eachother when looking at "today", however, the
// inconsistent between each other when looking at "today", however, the
// overhead from a transaction is not worth it.
eg.Go(func() error {
var err error
@ -207,7 +207,7 @@ func (api *API) insightsTemplates(rw http.ResponseWriter, r *http.Request) {
StartTime: startTime,
EndTime: endTime,
TemplateIDs: templateIDs,
IntervalDays: 1,
IntervalDays: interval.Days(),
})
if err != nil {
return xerrors.Errorf("get template daily insights: %w", err)
@ -531,20 +531,39 @@ func parseInsightsStartAndEndTime(ctx context.Context, rw http.ResponseWriter, s
return startTime, endTime, true
}
func verifyInsightsInterval(ctx context.Context, rw http.ResponseWriter, intervalString string) (codersdk.InsightsReportInterval, bool) {
func parseInsightsInterval(ctx context.Context, rw http.ResponseWriter, intervalString string, startTime, endTime time.Time) (codersdk.InsightsReportInterval, bool) {
switch v := codersdk.InsightsReportInterval(intervalString); v {
case codersdk.InsightsReportIntervalDay, "":
return v, true
case codersdk.InsightsReportIntervalWeek:
if !lastReportIntervalHasAtLeastSixDays(startTime, endTime) {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Query parameter has invalid value.",
Detail: "Last report interval should have at least 6 days.",
})
return "", false
}
return v, true
default:
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Query parameter has invalid value.",
Validations: []codersdk.ValidationError{
{
Field: "interval",
Detail: fmt.Sprintf("must be one of %v", []codersdk.InsightsReportInterval{codersdk.InsightsReportIntervalDay}),
Detail: fmt.Sprintf("must be one of %v", []codersdk.InsightsReportInterval{codersdk.InsightsReportIntervalDay, codersdk.InsightsReportIntervalWeek}),
},
},
})
return "", false
}
}
func lastReportIntervalHasAtLeastSixDays(startTime, endTime time.Time) bool {
lastReportIntervalDays := endTime.Sub(startTime) % (7 * 24 * time.Hour)
if lastReportIntervalDays == 0 {
return true // this is a perfectly full week!
}
// Ensure that the last interval has at least 6 days, or check the special case, forward DST change,
// when the duration can be shorter than 6 days: 5 days 23 hours.
return lastReportIntervalDays >= 6*24*time.Hour || startTime.AddDate(0, 0, 6).Equal(endTime)
}

View File

@ -8,6 +8,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/codersdk"
)
func Test_parseInsightsStartAndEndTime(t *testing.T) {
@ -148,3 +150,156 @@ func Test_parseInsightsStartAndEndTime(t *testing.T) {
})
}
}
func Test_parseInsightsInterval_week(t *testing.T) {
t.Parallel()
layout := insightsTimeLayout
sydneyLoc, err := time.LoadLocation("Australia/Sydney") // Random location
require.NoError(t, err)
now := time.Now()
y, m, d := now.Date()
today := time.Date(y, m, d, 0, 0, 0, 0, sydneyLoc)
thisHour := time.Date(y, m, d, now.Hour(), 0, 0, 0, sydneyLoc)
twoHoursAgo := thisHour.Add(-2 * time.Hour)
thirteenDaysAgo := today.AddDate(0, 0, -13)
sixDaysAgo := today.AddDate(0, 0, -6)
nineDaysAgo := today.AddDate(0, 0, -9)
type args struct {
startTime string
endTime string
}
tests := []struct {
name string
args args
wantOk bool
}{
{
name: "Two full weeks",
args: args{
startTime: "2023-08-10T00:00:00+02:00",
endTime: "2023-08-24T00:00:00+02:00",
},
wantOk: true,
},
{
name: "Two weeks",
args: args{
startTime: thirteenDaysAgo.Format(layout),
endTime: twoHoursAgo.Format(layout),
},
wantOk: true,
},
{
name: "One full week",
args: args{
startTime: "2023-09-06T00:00:00+02:00",
endTime: "2023-09-13T00:00:00+02:00",
},
wantOk: true,
},
{
name: "6 days are acceptable",
args: args{
startTime: sixDaysAgo.Format(layout),
endTime: thisHour.Format(layout),
},
wantOk: true,
},
{
name: "Shorter than a full week",
args: args{
startTime: "2023-09-08T00:00:00+02:00",
endTime: "2023-09-13T00:00:00+02:00",
},
wantOk: false,
},
{
name: "9 days (7 + 2) are not acceptable",
args: args{
startTime: nineDaysAgo.Format(layout),
endTime: thisHour.Format(layout),
},
wantOk: false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
rw := httptest.NewRecorder()
startTime, endTime, ok := parseInsightsStartAndEndTime(context.Background(), rw, tt.args.startTime, tt.args.endTime)
if !ok {
//nolint:bodyclose
t.Log("Status: ", rw.Result().StatusCode)
t.Log("Body: ", rw.Body.String())
}
require.True(t, ok, "start_time and end_time must be valid")
parsedInterval, gotOk := parseInsightsInterval(context.Background(), rw, "week", startTime, endTime)
if !assert.Equal(t, tt.wantOk, gotOk) {
//nolint:bodyclose
t.Log("Status: ", rw.Result().StatusCode)
t.Log("Body: ", rw.Body.String())
}
if tt.wantOk {
assert.Equal(t, codersdk.InsightsReportIntervalWeek, parsedInterval)
}
})
}
}
func TestLastReportIntervalHasAtLeastSixDays(t *testing.T) {
t.Parallel()
loc, err := time.LoadLocation("Europe/Warsaw")
require.NoError(t, err)
testCases := []struct {
name string
startTime time.Time
endTime time.Time
expected bool
}{
{
name: "perfectly full week",
startTime: time.Date(2023, time.September, 11, 12, 0, 0, 0, loc),
endTime: time.Date(2023, time.September, 18, 12, 0, 0, 0, loc),
expected: true,
},
{
name: "exactly 6 days apart",
startTime: time.Date(2023, time.September, 11, 12, 0, 0, 0, loc),
endTime: time.Date(2023, time.September, 17, 12, 0, 0, 0, loc),
expected: true,
},
{
name: "less than 6 days apart",
startTime: time.Date(2023, time.September, 11, 12, 0, 0, 0, time.UTC),
endTime: time.Date(2023, time.September, 17, 11, 0, 0, 0, time.UTC),
expected: false,
},
{
name: "forward DST change, 5 days and 23 hours apart",
startTime: time.Date(2023, time.March, 22, 12, 0, 0, 0, loc), // A day before DST starts
endTime: time.Date(2023, time.March, 28, 12, 0, 0, 0, loc), // Exactly 6 "days" apart
expected: true,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
result := lastReportIntervalHasAtLeastSixDays(tc.startTime, tc.endTime)
if result != tc.expected {
t.Errorf("Expected %v, but got %v for start time %v and end time %v", tc.expected, result, tc.startTime, tc.endTime)
}
})
}
}

View File

@ -810,6 +810,18 @@ func TestTemplateInsights_Golden(t *testing.T) {
sessionCountVSCode: 1,
sessionCountSSH: 1,
},
{ // One hour of usage.
startedAt: frozenWeekAgo.AddDate(0, 0, -12),
endedAt: frozenWeekAgo.AddDate(0, 0, -12).Add(time.Hour),
sessionCountSSH: 1,
sessionCountReconnectingPTY: 1,
},
{ // Another one hour of usage, but "active users" shouldn't be increased twice.
startedAt: frozenWeekAgo.AddDate(0, 0, -10),
endedAt: frozenWeekAgo.AddDate(0, 0, -10).Add(time.Hour),
sessionCountSSH: 1,
sessionCountReconnectingPTY: 1,
},
},
appUsage: []appUsage{
// TODO(mafredri): This doesn't behave correctly right now
@ -922,6 +934,16 @@ func TestTemplateInsights_Golden(t *testing.T) {
}
},
},
{
name: "weekly aggregated deployment wide",
makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest {
return codersdk.TemplateInsightsRequest{
StartTime: frozenWeekAgo.AddDate(0, 0, -3),
EndTime: frozenWeekAgo.AddDate(0, 0, 4),
Interval: codersdk.InsightsReportIntervalWeek,
}
},
},
{
name: "week all templates",
makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest {
@ -933,6 +955,17 @@ func TestTemplateInsights_Golden(t *testing.T) {
}
},
},
{
name: "weekly aggregated templates",
makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest {
return codersdk.TemplateInsightsRequest{
TemplateIDs: []uuid.UUID{templates[0].id, templates[1].id, templates[2].id},
StartTime: frozenWeekAgo.AddDate(0, 0, -1),
EndTime: frozenWeekAgo.AddDate(0, 0, 6),
Interval: codersdk.InsightsReportIntervalWeek,
}
},
},
{
name: "week first template",
makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest {
@ -944,6 +977,17 @@ func TestTemplateInsights_Golden(t *testing.T) {
}
},
},
{
name: "weekly aggregated first template",
makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest {
return codersdk.TemplateInsightsRequest{
TemplateIDs: []uuid.UUID{templates[0].id},
StartTime: frozenWeekAgo,
EndTime: frozenWeekAgo.AddDate(0, 0, 7),
Interval: codersdk.InsightsReportIntervalWeek,
}
},
},
{
name: "week second template",
makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest {
@ -955,6 +999,17 @@ func TestTemplateInsights_Golden(t *testing.T) {
}
},
},
{
name: "three weeks second template",
makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest {
return codersdk.TemplateInsightsRequest{
TemplateIDs: []uuid.UUID{templates[1].id},
StartTime: frozenWeekAgo.AddDate(0, 0, -14),
EndTime: frozenWeekAgo.AddDate(0, 0, 7),
Interval: codersdk.InsightsReportIntervalWeek,
}
},
},
{
name: "week third template",
makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest {
@ -1115,6 +1170,13 @@ func TestTemplateInsights_BadRequest(t *testing.T) {
Interval: "invalid",
})
assert.Error(t, err, "want error for bad interval")
_, err = client.TemplateInsights(ctx, codersdk.TemplateInsightsRequest{
StartTime: today.AddDate(0, 0, -5),
EndTime: today,
Interval: codersdk.InsightsReportIntervalWeek,
})
assert.Error(t, err, "last report interval must have at least 6 days")
}
func TestTemplateInsights_RBAC(t *testing.T) {

View File

@ -0,0 +1,90 @@
{
"report": {
"start_time": "2023-08-01T00:00:00Z",
"end_time": "2023-08-22T00:00:00Z",
"template_ids": [
"00000000-0000-0000-0000-000000000002"
],
"active_users": 1,
"apps_usage": [
{
"template_ids": [
"00000000-0000-0000-0000-000000000002"
],
"type": "builtin",
"display_name": "Visual Studio Code",
"slug": "vscode",
"icon": "/icon/code.svg",
"seconds": 3600
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000002"
],
"type": "builtin",
"display_name": "JetBrains",
"slug": "jetbrains",
"icon": "/icon/intellij.svg",
"seconds": 0
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000002"
],
"type": "builtin",
"display_name": "Web Terminal",
"slug": "reconnecting-pty",
"icon": "/icon/terminal.svg",
"seconds": 7200
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000002"
],
"type": "builtin",
"display_name": "SSH",
"slug": "ssh",
"icon": "/icon/terminal.svg",
"seconds": 10800
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000002"
],
"type": "app",
"display_name": "app1",
"slug": "app1",
"icon": "/icon1.png",
"seconds": 21600
}
],
"parameters_usage": []
},
"interval_reports": [
{
"start_time": "2023-08-01T00:00:00Z",
"end_time": "2023-08-08T00:00:00Z",
"template_ids": [
"00000000-0000-0000-0000-000000000002"
],
"interval": "week",
"active_users": 1
},
{
"start_time": "2023-08-08T00:00:00Z",
"end_time": "2023-08-15T00:00:00Z",
"template_ids": [],
"interval": "week",
"active_users": 0
},
{
"start_time": "2023-08-15T00:00:00Z",
"end_time": "2023-08-22T00:00:00Z",
"template_ids": [
"00000000-0000-0000-0000-000000000002"
],
"interval": "week",
"active_users": 1
}
]
}

View File

@ -0,0 +1,107 @@
{
"report": {
"start_time": "2023-08-12T00:00:00Z",
"end_time": "2023-08-19T00:00:00Z",
"template_ids": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"active_users": 3,
"apps_usage": [
{
"template_ids": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"type": "builtin",
"display_name": "Visual Studio Code",
"slug": "vscode",
"icon": "/icon/code.svg",
"seconds": 7200
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"type": "builtin",
"display_name": "JetBrains",
"slug": "jetbrains",
"icon": "/icon/intellij.svg",
"seconds": 120
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"type": "builtin",
"display_name": "Web Terminal",
"slug": "reconnecting-pty",
"icon": "/icon/terminal.svg",
"seconds": 3600
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"type": "builtin",
"display_name": "SSH",
"slug": "ssh",
"icon": "/icon/terminal.svg",
"seconds": 15120
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002"
],
"type": "app",
"display_name": "app1",
"slug": "app1",
"icon": "/icon1.png",
"seconds": 25380
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001"
],
"type": "app",
"display_name": "app3",
"slug": "app3",
"icon": "/icon2.png",
"seconds": 3600
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000003"
],
"type": "app",
"display_name": "otherapp1",
"slug": "otherapp1",
"icon": "/icon1.png",
"seconds": 300
}
],
"parameters_usage": []
},
"interval_reports": [
{
"start_time": "2023-08-12T00:00:00Z",
"end_time": "2023-08-19T00:00:00Z",
"template_ids": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"interval": "week",
"active_users": 3
}
]
}

View File

@ -0,0 +1,84 @@
{
"report": {
"start_time": "2023-08-15T00:00:00Z",
"end_time": "2023-08-22T00:00:00Z",
"template_ids": [
"00000000-0000-0000-0000-000000000001"
],
"active_users": 2,
"apps_usage": [
{
"template_ids": [
"00000000-0000-0000-0000-000000000001"
],
"type": "builtin",
"display_name": "Visual Studio Code",
"slug": "vscode",
"icon": "/icon/code.svg",
"seconds": 3600
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001"
],
"type": "builtin",
"display_name": "JetBrains",
"slug": "jetbrains",
"icon": "/icon/intellij.svg",
"seconds": 120
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001"
],
"type": "builtin",
"display_name": "Web Terminal",
"slug": "reconnecting-pty",
"icon": "/icon/terminal.svg",
"seconds": 0
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001"
],
"type": "builtin",
"display_name": "SSH",
"slug": "ssh",
"icon": "/icon/terminal.svg",
"seconds": 7920
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001"
],
"type": "app",
"display_name": "app1",
"slug": "app1",
"icon": "/icon1.png",
"seconds": 3780
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001"
],
"type": "app",
"display_name": "app3",
"slug": "app3",
"icon": "/icon2.png",
"seconds": 720
}
],
"parameters_usage": []
},
"interval_reports": [
{
"start_time": "2023-08-15T00:00:00Z",
"end_time": "2023-08-22T00:00:00Z",
"template_ids": [
"00000000-0000-0000-0000-000000000001"
],
"interval": "week",
"active_users": 2
}
]
}

View File

@ -0,0 +1,107 @@
{
"report": {
"start_time": "2023-08-14T00:00:00Z",
"end_time": "2023-08-21T00:00:00Z",
"template_ids": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"active_users": 3,
"apps_usage": [
{
"template_ids": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"type": "builtin",
"display_name": "Visual Studio Code",
"slug": "vscode",
"icon": "/icon/code.svg",
"seconds": 7200
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"type": "builtin",
"display_name": "JetBrains",
"slug": "jetbrains",
"icon": "/icon/intellij.svg",
"seconds": 120
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"type": "builtin",
"display_name": "Web Terminal",
"slug": "reconnecting-pty",
"icon": "/icon/terminal.svg",
"seconds": 3600
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"type": "builtin",
"display_name": "SSH",
"slug": "ssh",
"icon": "/icon/terminal.svg",
"seconds": 15120
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002"
],
"type": "app",
"display_name": "app1",
"slug": "app1",
"icon": "/icon1.png",
"seconds": 25380
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000001"
],
"type": "app",
"display_name": "app3",
"slug": "app3",
"icon": "/icon2.png",
"seconds": 3600
},
{
"template_ids": [
"00000000-0000-0000-0000-000000000003"
],
"type": "app",
"display_name": "otherapp1",
"slug": "otherapp1",
"icon": "/icon1.png",
"seconds": 300
}
],
"parameters_usage": []
},
"interval_reports": [
{
"start_time": "2023-08-14T00:00:00Z",
"end_time": "2023-08-21T00:00:00Z",
"template_ids": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"interval": "week",
"active_users": 3
}
]
}

View File

@ -20,9 +20,22 @@ const insightsTimeLayout = time.RFC3339
// smaller insights report within a time range.
type InsightsReportInterval string
// Days returns the duration of the interval in days.
func (interval InsightsReportInterval) Days() int32 {
switch interval {
case InsightsReportIntervalDay:
return 1
case InsightsReportIntervalWeek:
return 7
default:
panic("developer error: unsupported report interval")
}
}
// InsightsReportInterval enums.
const (
InsightsReportIntervalDay InsightsReportInterval = "day"
InsightsReportIntervalDay InsightsReportInterval = "day"
InsightsReportIntervalWeek InsightsReportInterval = "week"
)
// UserLatencyInsightsResponse is the response from the user latency insights
@ -109,7 +122,7 @@ type TemplateInsightsIntervalReport struct {
StartTime time.Time `json:"start_time" format:"date-time"`
EndTime time.Time `json:"end_time" format:"date-time"`
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
Interval InsightsReportInterval `json:"interval"`
Interval InsightsReportInterval `json:"interval" example:"week"`
ActiveUsers int64 `json:"active_users" example:"14"`
}
@ -155,7 +168,7 @@ type TemplateInsightsRequest struct {
StartTime time.Time `json:"start_time" format:"date-time"`
EndTime time.Time `json:"end_time" format:"date-time"`
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
Interval InsightsReportInterval `json:"interval"`
Interval InsightsReportInterval `json:"interval" example:"day"`
}
func (c *Client) TemplateInsights(ctx context.Context, req TemplateInsightsRequest) (TemplateInsightsResponse, error) {

2
docs/api/insights.md generated
View File

@ -60,7 +60,7 @@ curl -X GET http://coder-server:8080/api/v2/insights/templates \
{
"active_users": 14,
"end_time": "2019-08-24T14:15:22Z",
"interval": "day",
"interval": "week",
"start_time": "2019-08-24T14:15:22Z",
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"]
}

11
docs/api/schemas.md generated
View File

@ -3085,9 +3085,10 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
#### Enumerated Values
| Value |
| ----- |
| `day` |
| Value |
| ------ |
| `day` |
| `week` |
## codersdk.IssueReconnectingPTYSignedTokenRequest
@ -4421,7 +4422,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
{
"active_users": 14,
"end_time": "2019-08-24T14:15:22Z",
"interval": "day",
"interval": "week",
"start_time": "2019-08-24T14:15:22Z",
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"]
}
@ -4500,7 +4501,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
{
"active_users": 14,
"end_time": "2019-08-24T14:15:22Z",
"interval": "day",
"interval": "week",
"start_time": "2019-08-24T14:15:22Z",
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"]
}

View File

@ -1654,8 +1654,11 @@ export type GroupSource = "oidc" | "user";
export const GroupSources: GroupSource[] = ["oidc", "user"];
// From codersdk/insights.go
export type InsightsReportInterval = "day";
export const InsightsReportIntervals: InsightsReportInterval[] = ["day"];
export type InsightsReportInterval = "day" | "week";
export const InsightsReportIntervals: InsightsReportInterval[] = [
"day",
"week",
];
// From codersdk/provisionerdaemons.go
export type JobErrorCode = "REQUIRED_TEMPLATE_VARIABLES";