feat!: add sections parameter to template insights (#10010)

This commit is contained in:
Marcin Tojek
2023-10-03 15:44:50 +02:00
committed by GitHub
parent cb60409a8c
commit f62f45a303
9 changed files with 227 additions and 26 deletions
+55 -4
View File
@@ -257,6 +257,7 @@ func (api *API) insightsTemplates(rw http.ResponseWriter, r *http.Request) {
endTimeString = p.String(vals, "", "end_time")
intervalString = p.String(vals, "", "interval")
templateIDs = p.UUIDs(vals, []uuid.UUID{}, "template_ids")
sectionStrings = p.Strings(vals, templateInsightsSectionAsStrings(codersdk.TemplateInsightsSectionIntervalReports, codersdk.TemplateInsightsSectionReport), "sections")
)
p.ErrorExcessParams(vals)
if len(p.Errors) > 0 {
@@ -275,6 +276,10 @@ func (api *API) insightsTemplates(rw http.ResponseWriter, r *http.Request) {
if !ok {
return
}
sections, ok := parseTemplateInsightsSections(ctx, rw, sectionStrings)
if !ok {
return
}
var usage database.GetTemplateInsightsRow
var appUsage []database.GetTemplateAppInsightsRow
@@ -289,7 +294,7 @@ func (api *API) insightsTemplates(rw http.ResponseWriter, r *http.Request) {
// overhead from a transaction is not worth it.
eg.Go(func() error {
var err error
if interval != "" {
if interval != "" && slices.Contains(sections, codersdk.TemplateInsightsSectionIntervalReports) {
dailyUsage, err = api.Database.GetTemplateInsightsByInterval(egCtx, database.GetTemplateInsightsByIntervalParams{
StartTime: startTime,
EndTime: endTime,
@@ -303,6 +308,10 @@ func (api *API) insightsTemplates(rw http.ResponseWriter, r *http.Request) {
return nil
})
eg.Go(func() error {
if !slices.Contains(sections, codersdk.TemplateInsightsSectionReport) {
return nil
}
var err error
usage, err = api.Database.GetTemplateInsights(egCtx, database.GetTemplateInsightsParams{
StartTime: startTime,
@@ -315,6 +324,10 @@ func (api *API) insightsTemplates(rw http.ResponseWriter, r *http.Request) {
return nil
})
eg.Go(func() error {
if !slices.Contains(sections, codersdk.TemplateInsightsSectionReport) {
return nil
}
var err error
appUsage, err = api.Database.GetTemplateAppInsights(egCtx, database.GetTemplateAppInsightsParams{
StartTime: startTime,
@@ -330,6 +343,10 @@ func (api *API) insightsTemplates(rw http.ResponseWriter, r *http.Request) {
// Template parameter insights have no risk of inconsistency with the other
// insights.
eg.Go(func() error {
if !slices.Contains(sections, codersdk.TemplateInsightsSectionReport) {
return nil
}
var err error
parameterRows, err = api.Database.GetTemplateParameterInsights(ctx, database.GetTemplateParameterInsightsParams{
StartTime: startTime,
@@ -365,16 +382,20 @@ func (api *API) insightsTemplates(rw http.ResponseWriter, r *http.Request) {
}
resp := codersdk.TemplateInsightsResponse{
Report: codersdk.TemplateInsightsReport{
IntervalReports: []codersdk.TemplateInsightsIntervalReport{},
}
if slices.Contains(sections, codersdk.TemplateInsightsSectionReport) {
resp.Report = &codersdk.TemplateInsightsReport{
StartTime: startTime,
EndTime: endTime,
TemplateIDs: convertTemplateInsightsTemplateIDs(usage, appUsage),
ActiveUsers: convertTemplateInsightsActiveUsers(usage, appUsage),
AppsUsage: convertTemplateInsightsApps(usage, appUsage),
ParametersUsage: parametersUsage,
},
IntervalReports: []codersdk.TemplateInsightsIntervalReport{},
}
}
for _, row := range dailyUsage {
resp.IntervalReports = append(resp.IntervalReports, codersdk.TemplateInsightsIntervalReport{
// NOTE(mafredri): This might not be accurate over DST since the
@@ -654,3 +675,33 @@ func lastReportIntervalHasAtLeastSixDays(startTime, endTime time.Time) bool {
// 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)
}
func templateInsightsSectionAsStrings(sections ...codersdk.TemplateInsightsSection) []string {
t := make([]string, len(sections))
for i, s := range sections {
t[i] = string(s)
}
return t
}
func parseTemplateInsightsSections(ctx context.Context, rw http.ResponseWriter, sections []string) ([]codersdk.TemplateInsightsSection, bool) {
t := make([]codersdk.TemplateInsightsSection, len(sections))
for i, s := range sections {
switch v := codersdk.TemplateInsightsSection(s); v {
case codersdk.TemplateInsightsSectionIntervalReports, codersdk.TemplateInsightsSectionReport:
t[i] = v
default:
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Query parameter has invalid value.",
Validations: []codersdk.ValidationError{
{
Field: "sections",
Detail: fmt.Sprintf("must be one of %v", []codersdk.TemplateInsightsSection{codersdk.TemplateInsightsSectionIntervalReports, codersdk.TemplateInsightsSectionReport}),
},
},
})
return nil, false
}
}
return t, true
}
+32
View File
@@ -1135,6 +1135,30 @@ func TestTemplateInsights_Golden(t *testing.T) {
}
},
},
{
name: "three weeks second template only report",
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,
Sections: []codersdk.TemplateInsightsSection{codersdk.TemplateInsightsSectionReport},
}
},
},
{
name: "three weeks second template only interval reports",
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,
Sections: []codersdk.TemplateInsightsSection{codersdk.TemplateInsightsSectionIntervalReports},
}
},
},
},
},
{
@@ -2049,6 +2073,14 @@ func TestTemplateInsights_BadRequest(t *testing.T) {
Interval: codersdk.InsightsReportIntervalWeek,
})
assert.Error(t, err, "last report interval must have at least 6 days")
_, err = client.TemplateInsights(ctx, codersdk.TemplateInsightsRequest{
StartTime: today.AddDate(0, 0, -1),
EndTime: today,
Interval: codersdk.InsightsReportIntervalWeek,
Sections: []codersdk.TemplateInsightsSection{"invalid"},
})
assert.Error(t, err, "want error for bad section")
}
func TestTemplateInsights_RBAC(t *testing.T) {
@@ -0,0 +1,29 @@
{
"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
}
]
}
@@ -0,0 +1,63 @@
{
"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": []
}
}
@@ -39,6 +39,5 @@
}
],
"parameters_usage": []
},
"interval_reports": []
}
}
@@ -160,6 +160,5 @@
]
}
]
},
"interval_reports": []
}
}