From ffc7391a786fc23e69465d98ab509be7b742b78c Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 22 Aug 2023 12:45:54 +0000 Subject: [PATCH 01/33] test(coderd): add extended template insights test suite Fixes #9213 --- Makefile | 6 +- coderd/coderd_test.go | 4 + coderd/insights_test.go | 599 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 608 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c249fbd8c70ce..62528464cb419 100644 --- a/Makefile +++ b/Makefile @@ -564,7 +564,7 @@ coderd/apidoc/swagger.json: $(shell find ./scripts/apidocgen $(FIND_EXCLUSIONS) ./scripts/apidocgen/generate.sh pnpm run format:write:only ./docs/api ./docs/manifest.json ./coderd/apidoc/swagger.json -update-golden-files: cli/testdata/.gen-golden helm/coder/tests/testdata/.gen-golden helm/provisioner/tests/testdata/.gen-golden scripts/ci-report/testdata/.gen-golden enterprise/cli/testdata/.gen-golden +update-golden-files: cli/testdata/.gen-golden helm/coder/tests/testdata/.gen-golden helm/provisioner/tests/testdata/.gen-golden scripts/ci-report/testdata/.gen-golden enterprise/cli/testdata/.gen-golden coderd/.gen-golden .PHONY: update-golden-files cli/testdata/.gen-golden: $(wildcard cli/testdata/*.golden) $(wildcard cli/*.tpl) $(GO_SRC_FILES) $(wildcard cli/*_test.go) @@ -583,6 +583,10 @@ helm/provisioner/tests/testdata/.gen-golden: $(wildcard helm/provisioner/tests/t go test ./helm/provisioner/tests -run=TestUpdateGoldenFiles -update touch "$@" +coderd/.gen-golden: $(wildcard coderd/testdata/*/*.golden) $(GO_SRC_FILES) $(wildcard coderd/*_test.go) + go test ./coderd -run="Test.*Golden$$" -update + touch "$@" + scripts/ci-report/testdata/.gen-golden: $(wildcard scripts/ci-report/testdata/*) $(wildcard scripts/ci-report/*.go) go test ./scripts/ci-report -run=TestOutputMatchesGoldenFile -update touch "$@" diff --git a/coderd/coderd_test.go b/coderd/coderd_test.go index ba8f5311d60a2..1805b15c959ce 100644 --- a/coderd/coderd_test.go +++ b/coderd/coderd_test.go @@ -2,6 +2,7 @@ package coderd_test import ( "context" + "flag" "io" "net/http" "net/netip" @@ -23,6 +24,9 @@ import ( "github.com/coder/coder/v2/testutil" ) +// updateGoldenFiles is a flag that can be set to update golden files. +var updateGoldenFiles = flag.Bool("update", false, "Update golden files") + func TestMain(m *testing.M) { goleak.VerifyTestMain(m) } diff --git a/coderd/insights_test.go b/coderd/insights_test.go index bb28f113bd332..6131a49c1eee5 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -2,9 +2,13 @@ package coderd_test import ( "context" + "encoding/json" "fmt" "io" "net/http" + "os" + "path/filepath" + "strings" "testing" "time" @@ -16,6 +20,7 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" "github.com/coder/coder/v2/agent" + "github.com/coder/coder/v2/coderd/batchstats" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/rbac" @@ -268,6 +273,7 @@ func TestTemplateInsights(t *testing.T) { logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) opts := &coderdtest.Options{ + Logger: &logger, IncludeProvisionerDaemon: true, AgentStatsRefreshInterval: time.Millisecond * 100, } @@ -570,6 +576,599 @@ func TestTemplateInsights(t *testing.T) { }, resp.Report.ParametersUsage[2].Options) } +func TestTemplateInsights_Golden(t *testing.T) { + t.Parallel() + + stabilizeReportForGoldenComparison := func(report *codersdk.TemplateInsightsResponse) { + var stableTemplateIDs []uuid.UUID + stableTemplateIDMap := make(map[uuid.UUID]uuid.UUID) + toStableTemplateID := func(id uuid.UUID) uuid.UUID { + if stableID, ok := stableTemplateIDMap[id]; ok { + return stableID + } + stableTemplateIDs = append(stableTemplateIDs, uuid.MustParse(fmt.Sprintf("00000000-0000-0000-0000-%012d", len(stableTemplateIDs)+1))) + stableID := stableTemplateIDs[len(stableTemplateIDs)-1] + stableTemplateIDMap[id] = stableID + return stableID + } + + for i, id := range report.Report.TemplateIDs { + report.Report.TemplateIDs[i] = toStableTemplateID(id) + } + for _, param := range report.Report.ParametersUsage { + for i, id := range param.TemplateIDs { + param.TemplateIDs[i] = toStableTemplateID(id) + } + } + for _, app := range report.Report.AppsUsage { + for i, id := range app.TemplateIDs { + app.TemplateIDs[i] = toStableTemplateID(id) + } + } + for _, intervalReport := range report.IntervalReports { + for i, id := range intervalReport.TemplateIDs { + intervalReport.TemplateIDs[i] = toStableTemplateID(id) + } + } + } + + // Prepare test data types. + type templateParameterOption struct { + name string + value string + } + type templateParameter struct { + name string + description string + options []templateParameterOption + } + type templateApp struct { + name string + icon string + } + type testTemplate struct { + name string + parameters []*templateParameter + apps []templateApp + + // Filled later. + id uuid.UUID // Set to the created template ID. + } + type buildParameter struct { + templateParameter *templateParameter + value string + } + type workspaceApp templateApp + type testWorkspace struct { + name string + template *testTemplate + buildParameters []buildParameter + + // Filled later. + id uuid.UUID + user any // *testUser, but it's not available yet, defined below. + agentID uuid.UUID + apps []*workspaceApp + agentClient *agentsdk.Client + } + type testUser struct { + name string + workspaces []*testWorkspace + + client *codersdk.Client + sdk codersdk.User + } + + // Represent agent stats, to be inserted via stats batcher. + type agentStat struct { + // Set a range via start/end, multiple stats will be generated + // within the range. + startedAt time.Time + endedAt time.Time + + sessionCountVSCode int64 + sessionCountJetBrains int64 + sessionCountReconnectingPTY int64 + sessionCountSSH int64 + noConnections bool + } + // Represent app usage stats, to be inserted via stats reporter. + type appUsage struct { + app *workspaceApp + startedAt time.Time + endedAt time.Time + requests int + } + + // Represent actual data being generated on a per-workspace basis. + type testDataGen struct { + agentStats []agentStat + appUsage []appUsage + } + + createFixture := func() ([]*testTemplate, []*testUser) { + // Test templates and configuration to generate. + templates := []*testTemplate{ + // Create two templates with near-identical apps and parameters + // to allow testing for grouping similar data. + { + name: "template1", + parameters: []*templateParameter{ + {name: "param1", description: "This is first parameter"}, + {name: "param2", description: "This is second parameter"}, + {name: "param3", description: "This is third parameter"}, + { + name: "param4", + description: "This is fourth parameter", + options: []templateParameterOption{ + {name: "option1", value: "option1"}, + {name: "option2", value: "option2"}, + }, + }, + }, + apps: []templateApp{ + {name: "app1", icon: "/icon1.png"}, + {name: "app2", icon: "/icon2.png"}, + {name: "app3", icon: "/icon2.png"}, + }, + }, + { + name: "template2", + parameters: []*templateParameter{ + {name: "param1", description: "This is first parameter"}, + {name: "param2", description: "This is second parameter"}, + {name: "param3", description: "This is third parameter"}, + }, + apps: []templateApp{ + {name: "app1", icon: "/icon1.png"}, + {name: "app2", icon: "/icon2.png"}, + {name: "app3", icon: "/icon2.png"}, + }, + }, + // Create another template with different parameters and apps. + { + name: "othertemplate", + parameters: []*templateParameter{ + {name: "otherparam1", description: "This is another parameter"}, + }, + apps: []templateApp{ + {name: "otherapp1", icon: "/icon1.png"}, + }, + }, + } + + // Users and workspaces to generate. + users := []*testUser{ + { + name: "user1", + workspaces: []*testWorkspace{ + { + name: "workspace1", + template: templates[0], + buildParameters: []buildParameter{ + {templateParameter: templates[0].parameters[0], value: "abc"}, + {templateParameter: templates[0].parameters[1], value: "123"}, + {templateParameter: templates[0].parameters[2], value: "bbb"}, + {templateParameter: templates[0].parameters[3], value: "option1"}, + }, + }, + { + name: "workspace2", + template: templates[1], + buildParameters: []buildParameter{ + {templateParameter: templates[0].parameters[0], value: "ABC"}, + {templateParameter: templates[0].parameters[1], value: "123"}, + {templateParameter: templates[0].parameters[2], value: "BBB"}, + {templateParameter: templates[0].parameters[3], value: "option2"}, + }, + }, + { + name: "otherworkspace1", + template: templates[2], + }, + }, + }, + { + name: "user2", + workspaces: []*testWorkspace{ + { + name: "workspace1", + template: templates[0], + buildParameters: []buildParameter{ + {templateParameter: templates[0].parameters[0], value: "abc"}, + {templateParameter: templates[0].parameters[1], value: "123"}, + {templateParameter: templates[0].parameters[2], value: "BBB"}, + {templateParameter: templates[0].parameters[3], value: "option1"}, + }, + }, + }, + }, + { + name: "user3", + workspaces: []*testWorkspace{ + { + name: "otherworkspace1", + template: templates[2], + buildParameters: []buildParameter{ + {templateParameter: templates[2].parameters[0], value: "xyz"}, + }, + }, + }, + }, + } + + // Post-process workspaces. + for _, user := range users { + for _, workspace := range user.workspaces { + workspace.user = user + workspace.agentID = uuid.New() + for _, app := range workspace.template.apps { + app := workspaceApp(app) + workspace.apps = append(workspace.apps, &app) + } + } + } + + return templates, users + } + + prepare := func(t *testing.T, templates []*testTemplate, users []*testUser, testData map[*testWorkspace]testDataGen) *codersdk.Client { + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + opts := &coderdtest.Options{ + Logger: &logger, + IncludeProvisionerDaemon: true, + AgentStatsRefreshInterval: time.Millisecond * 100, + } + client, _, coderdAPI := coderdtest.NewWithAPI(t, opts) + firstUser := coderdtest.CreateFirstUser(t, client) + + // Prepare all test users. + for _, u := range users { + u.client, u.sdk = coderdtest.CreateAnotherUserMutators(t, client, firstUser.OrganizationID, nil, func(r *codersdk.CreateUserRequest) { + r.Username = u.name + }) + } + + // Prepare all the templates. + for _, template := range templates { + var parameters []*proto.RichParameter + for _, parameter := range template.parameters { + var options []*proto.RichParameterOption + var defaultValue string + for _, option := range parameter.options { + if defaultValue == "" { + defaultValue = option.value + } + options = append(options, &proto.RichParameterOption{ + Name: option.name, + Value: option.value, + }) + } + parameters = append(parameters, &proto.RichParameter{ + Name: parameter.name, + DisplayName: parameter.name, + Type: "string", + Description: parameter.description, + Options: options, + DefaultValue: defaultValue, + }) + } + + // Prepare all workspace resources (agents and apps). + var ( + createWorkspaces []func(uuid.UUID) + waitWorkspaces []func() + ) + var resources []*proto.Resource + for _, user := range users { + for _, workspace := range user.workspaces { + if workspace.template != template { + continue + } + authToken := uuid.New() + agentClient := agentsdk.New(client.URL) + agentClient.SetSessionToken(authToken.String()) + workspace.agentClient = agentClient + + var apps []*proto.App + for _, app := range workspace.apps { + apps = append(apps, &proto.App{ + Slug: app.name, + DisplayName: app.name, + Icon: app.icon, + SharingLevel: proto.AppSharingLevel_OWNER, + Url: "http://", + }) + } + + resources = append(resources, &proto.Resource{ + Name: "example", + Type: "aws_instance", + Agents: []*proto.Agent{{ + Id: workspace.agentID.String(), + Name: "dev", + Auth: &proto.Agent_Token{ + Token: authToken.String(), + }, + Apps: apps, + }}, + }) + + var buildParameters []codersdk.WorkspaceBuildParameter + for _, buildParameter := range workspace.buildParameters { + buildParameters = append(buildParameters, codersdk.WorkspaceBuildParameter{ + Name: buildParameter.templateParameter.name, + Value: buildParameter.value, + }) + } + createWorkspaces = append(createWorkspaces, func(templateID uuid.UUID) { + // Create workspace using the users client. + createdWorkspace := coderdtest.CreateWorkspace(t, user.client, firstUser.OrganizationID, templateID, func(cwr *codersdk.CreateWorkspaceRequest) { + cwr.RichParameterValues = buildParameters + }) + workspace.id = createdWorkspace.ID + waitWorkspaces = append(waitWorkspaces, func() { + coderdtest.AwaitWorkspaceBuildJob(t, user.client, createdWorkspace.LatestBuild.ID) + }) + }) + } + } + + // Create the template version and template. + version := coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: []*proto.Provision_Response{ + { + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Parameters: parameters, + }, + }, + }, + }, + ProvisionApply: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: resources, + }, + }, + }}, + }) + createdTemplate := coderdtest.CreateTemplate(t, client, firstUser.OrganizationID, version.ID) + require.Empty(t, createdTemplate.BuildTimeStats[codersdk.WorkspaceTransitionStart]) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + + template.id = createdTemplate.ID + + // Create all workspaces and wait for them. + for _, createWorkspace := range createWorkspaces { + createWorkspace(template.id) + } + for _, waitWorkspace := range waitWorkspaces { + waitWorkspace() + } + } + + ctx := testutil.Context(t, testutil.WaitSuperLong) + + // Use agent stats batcher to insert agent stats, similar to live system. + // NOTE(mafredri): Ideally we would pass batcher as a coderd option and + // insert using the agentClient, but we have a circular dependency on + // the database. + batcher, batcherCloser, err := batchstats.New( + ctx, + batchstats.WithStore(coderdAPI.Database), + batchstats.WithLogger(logger.Named("batchstats")), + batchstats.WithInterval(time.Hour), + ) + require.NoError(t, err) + defer batcherCloser() // Flushes the stats, this is to ensure they're written. + + for workspace, data := range testData { + for _, stat := range data.agentStats { + createdAt := stat.startedAt + connectionCount := int64(1) + if stat.noConnections { + connectionCount = 0 + } + for createdAt.Before(stat.endedAt) { + err = batcher.Add(createdAt, workspace.agentID, workspace.template.id, workspace.user.(*testUser).sdk.ID, workspace.id, agentsdk.Stats{ + ConnectionCount: connectionCount, + SessionCountVSCode: stat.sessionCountVSCode, + SessionCountJetBrains: stat.sessionCountJetBrains, + SessionCountReconnectingPTY: stat.sessionCountReconnectingPTY, + SessionCountSSH: stat.sessionCountSSH, + }) + require.NoError(t, err, "want no error inserting agent stats") + createdAt = createdAt.Add(30 * time.Second) + } + } + } + + // Insert app usage. + var stats []workspaceapps.StatsReport + for workspace, data := range testData { + for _, usage := range data.appUsage { + appName := usage.app.name + accessMethod := workspaceapps.AccessMethodPath + if usage.app.name == "terminal" { + appName = "" + accessMethod = workspaceapps.AccessMethodTerminal + } + stats = append(stats, workspaceapps.StatsReport{ + UserID: workspace.user.(*testUser).sdk.ID, + WorkspaceID: workspace.id, + AgentID: workspace.agentID, + AccessMethod: accessMethod, + SlugOrPort: appName, + SessionID: uuid.New(), + SessionStartedAt: usage.startedAt, + SessionEndedAt: usage.endedAt, + Requests: usage.requests, + }) + } + } + reporter := workspaceapps.NewStatsDBReporter(coderdAPI.Database, workspaceapps.DefaultStatsDBReporterBatchSize) + //nolint:gocritic // This is a test. + err = reporter.Report(dbauthz.AsSystemRestricted(ctx), stats) + require.NoError(t, err, "want no error inserting app stats") + + return client + } + + // Time range for report, test data will be generated within and + // outside this range, but only data within the range should be + // included in the report. + lastNight := time.Now().UTC().Truncate(24 * time.Hour) + weekAgo := lastNight.AddDate(0, 0, -7) + + makeBaseTestData := func(templates []*testTemplate, users []*testUser) map[*testWorkspace]testDataGen { + return map[*testWorkspace]testDataGen{ + users[0].workspaces[0]: { + agentStats: []agentStat{ + { + startedAt: weekAgo, + endedAt: weekAgo.Add(time.Hour), + sessionCountVSCode: 1, + sessionCountSSH: 1, + }, + }, + appUsage: []appUsage{ + { + app: users[0].workspaces[0].apps[0], + startedAt: time.Now().UTC().Add(-time.Hour), + endedAt: time.Now().UTC(), + requests: 1, + }, + }, + }, + } + } + type testRequest struct { + name string + makeRequest func([]*testTemplate) codersdk.TemplateInsightsRequest + } + tests := []struct { + name string + makeTestData func([]*testTemplate, []*testUser) map[*testWorkspace]testDataGen + requests []testRequest + }{ + { + name: "multiple users and workspaces week", + makeTestData: makeBaseTestData, + requests: []testRequest{ + { + name: "deployment wide", + makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { + return codersdk.TemplateInsightsRequest{ + StartTime: weekAgo, + EndTime: lastNight, + Interval: codersdk.InsightsReportIntervalDay, + } + }, + }, + { + name: "all templates", + makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { + return codersdk.TemplateInsightsRequest{ + TemplateIDs: []uuid.UUID{templates[0].id, templates[1].id, templates[2].id}, + StartTime: weekAgo, + EndTime: lastNight, + Interval: codersdk.InsightsReportIntervalDay, + } + }, + }, + { + name: "first template", + makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { + return codersdk.TemplateInsightsRequest{ + TemplateIDs: []uuid.UUID{templates[0].id}, + StartTime: weekAgo, + EndTime: lastNight, + Interval: codersdk.InsightsReportIntervalDay, + } + }, + }, + { + name: "second template", + makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { + return codersdk.TemplateInsightsRequest{ + TemplateIDs: []uuid.UUID{templates[1].id}, + StartTime: weekAgo, + EndTime: lastNight, + Interval: codersdk.InsightsReportIntervalDay, + } + }, + }, + { + name: "third template", + makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { + return codersdk.TemplateInsightsRequest{ + TemplateIDs: []uuid.UUID{templates[2].id}, + StartTime: weekAgo, + EndTime: lastNight, + Interval: codersdk.InsightsReportIntervalDay, + } + }, + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + templates, users := createFixture() + testData := tt.makeTestData(templates, users) + // Sanity check. + for ws, data := range testData { + for _, usage := range data.appUsage { + require.Contains(t, ws.apps, usage.app, "test bug: app %q not in workspace %q", usage.app.name, ws.name) + } + } + + client := prepare(t, templates, users, testData) + + for _, req := range tt.requests { + req := req + t.Run(req.name, func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + report, err := client.TemplateInsights(ctx, req.makeRequest(templates)) + require.NoError(t, err, "want no error getting template insights") + + stabilizeReportForGoldenComparison(&report) + + partialName := strings.Join(strings.Split(t.Name(), "/")[1:], "_") + goldenFile := filepath.Join("testdata", "insights", partialName+".json.golden") + if *updateGoldenFiles { + err = os.MkdirAll(filepath.Dir(goldenFile), 0o755) + require.NoError(t, err, "want no error creating golden file directory") + f, err := os.Create(goldenFile) + require.NoError(t, err, "want no error creating golden file") + defer f.Close() + enc := json.NewEncoder(f) + enc.SetIndent("", " ") + enc.Encode(report) + return + } + + f, err := os.Open(goldenFile) + require.NoError(t, err, "open golden file, run \"make update-golden-files\" and commit the changes") + defer f.Close() + var want codersdk.TemplateInsightsResponse + err = json.NewDecoder(f).Decode(&want) + require.NoError(t, err, "want no error decoding golden file") + + assert.Equal(t, want, report, "golden file mismatch: %s, run \"make update-golden-files\", verify and commit the changes", goldenFile) + }) + } + }) + } +} + func TestTemplateInsights_BadRequest(t *testing.T) { t.Parallel() From 95ad848bcd500c22ced764afb82255571af2fbd7 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 22 Aug 2023 12:47:53 +0000 Subject: [PATCH 02/33] add initial golden files --- ..._workspaces_week_all_templates.json.golden | 106 ++++++++++++++++++ ...orkspaces_week_deployment_wide.json.golden | 106 ++++++++++++++++++ ...workspaces_week_first_template.json.golden | 106 ++++++++++++++++++ ...orkspaces_week_second_template.json.golden | 94 ++++++++++++++++ ...workspaces_week_third_template.json.golden | 94 ++++++++++++++++ 5 files changed, 506 insertions(+) create mode 100644 coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden create mode 100644 coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden create mode 100644 coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden create mode 100644 coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden create mode 100644 coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden new file mode 100644 index 0000000000000..4d3032a559427 --- /dev/null +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden @@ -0,0 +1,106 @@ +{ + "report": { + "start_time": "2023-08-15T00:00:00Z", + "end_time": "2023-08-22T00:00:00Z", + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "active_users": 1, + "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": 0 + }, + { + "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": 3600 + } + ], + "parameters_usage": [] + }, + "interval_reports": [ + { + "start_time": "2023-08-15T00:00:00Z", + "end_time": "2023-08-16T00:00:00Z", + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "interval": "day", + "active_users": 1 + }, + { + "start_time": "2023-08-16T00:00:00Z", + "end_time": "2023-08-17T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-17T00:00:00Z", + "end_time": "2023-08-18T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-18T00:00:00Z", + "end_time": "2023-08-19T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-19T00:00:00Z", + "end_time": "2023-08-20T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-20T00:00:00Z", + "end_time": "2023-08-21T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-21T00:00:00Z", + "end_time": "2023-08-22T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + } + ] +} diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden new file mode 100644 index 0000000000000..4d3032a559427 --- /dev/null +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden @@ -0,0 +1,106 @@ +{ + "report": { + "start_time": "2023-08-15T00:00:00Z", + "end_time": "2023-08-22T00:00:00Z", + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "active_users": 1, + "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": 0 + }, + { + "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": 3600 + } + ], + "parameters_usage": [] + }, + "interval_reports": [ + { + "start_time": "2023-08-15T00:00:00Z", + "end_time": "2023-08-16T00:00:00Z", + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "interval": "day", + "active_users": 1 + }, + { + "start_time": "2023-08-16T00:00:00Z", + "end_time": "2023-08-17T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-17T00:00:00Z", + "end_time": "2023-08-18T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-18T00:00:00Z", + "end_time": "2023-08-19T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-19T00:00:00Z", + "end_time": "2023-08-20T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-20T00:00:00Z", + "end_time": "2023-08-21T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-21T00:00:00Z", + "end_time": "2023-08-22T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + } + ] +} diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden new file mode 100644 index 0000000000000..4d3032a559427 --- /dev/null +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden @@ -0,0 +1,106 @@ +{ + "report": { + "start_time": "2023-08-15T00:00:00Z", + "end_time": "2023-08-22T00:00:00Z", + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "active_users": 1, + "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": 0 + }, + { + "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": 3600 + } + ], + "parameters_usage": [] + }, + "interval_reports": [ + { + "start_time": "2023-08-15T00:00:00Z", + "end_time": "2023-08-16T00:00:00Z", + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "interval": "day", + "active_users": 1 + }, + { + "start_time": "2023-08-16T00:00:00Z", + "end_time": "2023-08-17T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-17T00:00:00Z", + "end_time": "2023-08-18T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-18T00:00:00Z", + "end_time": "2023-08-19T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-19T00:00:00Z", + "end_time": "2023-08-20T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-20T00:00:00Z", + "end_time": "2023-08-21T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-21T00:00:00Z", + "end_time": "2023-08-22T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + } + ] +} diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden new file mode 100644 index 0000000000000..2926f200d8ae5 --- /dev/null +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden @@ -0,0 +1,94 @@ +{ + "report": { + "start_time": "2023-08-15T00:00:00Z", + "end_time": "2023-08-22T00:00:00Z", + "template_ids": [], + "active_users": 0, + "apps_usage": [ + { + "template_ids": [], + "type": "builtin", + "display_name": "Visual Studio Code", + "slug": "vscode", + "icon": "/icon/code.svg", + "seconds": 0 + }, + { + "template_ids": [], + "type": "builtin", + "display_name": "JetBrains", + "slug": "jetbrains", + "icon": "/icon/intellij.svg", + "seconds": 0 + }, + { + "template_ids": [], + "type": "builtin", + "display_name": "Web Terminal", + "slug": "reconnecting-pty", + "icon": "/icon/terminal.svg", + "seconds": 0 + }, + { + "template_ids": [], + "type": "builtin", + "display_name": "SSH", + "slug": "ssh", + "icon": "/icon/terminal.svg", + "seconds": 0 + } + ], + "parameters_usage": [] + }, + "interval_reports": [ + { + "start_time": "2023-08-15T00:00:00Z", + "end_time": "2023-08-16T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-16T00:00:00Z", + "end_time": "2023-08-17T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-17T00:00:00Z", + "end_time": "2023-08-18T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-18T00:00:00Z", + "end_time": "2023-08-19T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-19T00:00:00Z", + "end_time": "2023-08-20T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-20T00:00:00Z", + "end_time": "2023-08-21T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-21T00:00:00Z", + "end_time": "2023-08-22T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + } + ] +} diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden new file mode 100644 index 0000000000000..2926f200d8ae5 --- /dev/null +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden @@ -0,0 +1,94 @@ +{ + "report": { + "start_time": "2023-08-15T00:00:00Z", + "end_time": "2023-08-22T00:00:00Z", + "template_ids": [], + "active_users": 0, + "apps_usage": [ + { + "template_ids": [], + "type": "builtin", + "display_name": "Visual Studio Code", + "slug": "vscode", + "icon": "/icon/code.svg", + "seconds": 0 + }, + { + "template_ids": [], + "type": "builtin", + "display_name": "JetBrains", + "slug": "jetbrains", + "icon": "/icon/intellij.svg", + "seconds": 0 + }, + { + "template_ids": [], + "type": "builtin", + "display_name": "Web Terminal", + "slug": "reconnecting-pty", + "icon": "/icon/terminal.svg", + "seconds": 0 + }, + { + "template_ids": [], + "type": "builtin", + "display_name": "SSH", + "slug": "ssh", + "icon": "/icon/terminal.svg", + "seconds": 0 + } + ], + "parameters_usage": [] + }, + "interval_reports": [ + { + "start_time": "2023-08-15T00:00:00Z", + "end_time": "2023-08-16T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-16T00:00:00Z", + "end_time": "2023-08-17T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-17T00:00:00Z", + "end_time": "2023-08-18T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-18T00:00:00Z", + "end_time": "2023-08-19T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-19T00:00:00Z", + "end_time": "2023-08-20T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-20T00:00:00Z", + "end_time": "2023-08-21T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-21T00:00:00Z", + "end_time": "2023-08-22T00:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + } + ] +} From d4d58cf46c9402a681bb791e262828bb863b9d2f Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 22 Aug 2023 13:03:12 +0000 Subject: [PATCH 03/33] add some more usage and verify golden --- coderd/insights_test.go | 12 +++++++++++- ...and_workspaces_week_all_templates.json.golden | 16 ++++++++++------ ...d_workspaces_week_deployment_wide.json.golden | 16 ++++++++++------ ...nd_workspaces_week_first_template.json.golden | 16 ++++++++++------ 4 files changed, 41 insertions(+), 19 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 6131a49c1eee5..a5f80cbbef9ad 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1026,12 +1026,22 @@ func TestTemplateInsights_Golden(t *testing.T) { return map[*testWorkspace]testDataGen{ users[0].workspaces[0]: { agentStats: []agentStat{ - { + { // One hour of usage. startedAt: weekAgo, endedAt: weekAgo.Add(time.Hour), sessionCountVSCode: 1, sessionCountSSH: 1, }, + { // 12 minutes of usage -> 15 minutes. + startedAt: weekAgo.AddDate(0, 0, 1), + endedAt: weekAgo.AddDate(0, 0, 1).Add(12 * time.Minute), + sessionCountSSH: 1, + }, + { // 2 minutes of usage -> 10 minutes because it crosses the 5 minute interval boundary. + startedAt: weekAgo.AddDate(0, 0, 2).Add(4 * time.Minute), + endedAt: weekAgo.AddDate(0, 0, 2).Add(6 * time.Minute), + sessionCountJetBrains: 1, + }, }, appUsage: []appUsage{ { diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden index 4d3032a559427..469c0b1f0984c 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden @@ -25,7 +25,7 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 0 + "seconds": 600 }, { "template_ids": [ @@ -45,7 +45,7 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 3600 + "seconds": 4500 } ], "parameters_usage": [] @@ -63,16 +63,20 @@ { "start_time": "2023-08-16T00:00:00Z", "end_time": "2023-08-17T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "interval": "day", - "active_users": 0 + "active_users": 1 }, { "start_time": "2023-08-17T00:00:00Z", "end_time": "2023-08-18T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "interval": "day", - "active_users": 0 + "active_users": 1 }, { "start_time": "2023-08-18T00:00:00Z", diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden index 4d3032a559427..469c0b1f0984c 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden @@ -25,7 +25,7 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 0 + "seconds": 600 }, { "template_ids": [ @@ -45,7 +45,7 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 3600 + "seconds": 4500 } ], "parameters_usage": [] @@ -63,16 +63,20 @@ { "start_time": "2023-08-16T00:00:00Z", "end_time": "2023-08-17T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "interval": "day", - "active_users": 0 + "active_users": 1 }, { "start_time": "2023-08-17T00:00:00Z", "end_time": "2023-08-18T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "interval": "day", - "active_users": 0 + "active_users": 1 }, { "start_time": "2023-08-18T00:00:00Z", diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden index 4d3032a559427..469c0b1f0984c 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden @@ -25,7 +25,7 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 0 + "seconds": 600 }, { "template_ids": [ @@ -45,7 +45,7 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 3600 + "seconds": 4500 } ], "parameters_usage": [] @@ -63,16 +63,20 @@ { "start_time": "2023-08-16T00:00:00Z", "end_time": "2023-08-17T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "interval": "day", - "active_users": 0 + "active_users": 1 }, { "start_time": "2023-08-17T00:00:00Z", "end_time": "2023-08-18T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "interval": "day", - "active_users": 0 + "active_users": 1 }, { "start_time": "2023-08-18T00:00:00Z", From e4f901a574e93a4712c344c7aa93c3aef803c5e8 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 22 Aug 2023 14:47:01 +0000 Subject: [PATCH 04/33] fixes --- coderd/insights_test.go | 68 ++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index a5f80cbbef9ad..ac8bf040d8cb4 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -801,7 +801,6 @@ func TestTemplateInsights_Golden(t *testing.T) { for _, user := range users { for _, workspace := range user.workspaces { workspace.user = user - workspace.agentID = uuid.New() for _, app := range workspace.template.apps { app := workspaceApp(app) workspace.apps = append(workspace.apps, &app) @@ -813,24 +812,27 @@ func TestTemplateInsights_Golden(t *testing.T) { } prepare := func(t *testing.T, templates []*testTemplate, users []*testUser, testData map[*testWorkspace]testDataGen) *codersdk.Client { - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: false}).Leveled(slog.LevelDebug) opts := &coderdtest.Options{ Logger: &logger, IncludeProvisionerDaemon: true, - AgentStatsRefreshInterval: time.Millisecond * 100, + AgentStatsRefreshInterval: time.Hour, // Not relevant for this test. } client, _, coderdAPI := coderdtest.NewWithAPI(t, opts) firstUser := coderdtest.CreateFirstUser(t, client) // Prepare all test users. - for _, u := range users { - u.client, u.sdk = coderdtest.CreateAnotherUserMutators(t, client, firstUser.OrganizationID, nil, func(r *codersdk.CreateUserRequest) { - r.Username = u.name + for _, user := range users { + user.client, user.sdk = coderdtest.CreateAnotherUserMutators(t, client, firstUser.OrganizationID, nil, func(r *codersdk.CreateUserRequest) { + r.Username = user.name }) + user.client.SetLogger(logger.Named("user").With(slog.Field{Name: "name", Value: user.name})) } // Prepare all the templates. for _, template := range templates { + template := template + var parameters []*proto.RichParameter for _, parameter := range template.parameters { var options []*proto.RichParameterOption @@ -861,7 +863,10 @@ func TestTemplateInsights_Golden(t *testing.T) { ) var resources []*proto.Resource for _, user := range users { + user := user for _, workspace := range user.workspaces { + workspace := workspace + if workspace.template != template { continue } @@ -885,7 +890,7 @@ func TestTemplateInsights_Golden(t *testing.T) { Name: "example", Type: "aws_instance", Agents: []*proto.Agent{{ - Id: workspace.agentID.String(), + Id: uuid.NewString(), // Doesn't matter, not used in DB. Name: "dev", Auth: &proto.Agent_Token{ Token: authToken.String(), @@ -901,6 +906,7 @@ func TestTemplateInsights_Golden(t *testing.T) { Value: buildParameter.value, }) } + createWorkspaces = append(createWorkspaces, func(templateID uuid.UUID) { // Create workspace using the users client. createdWorkspace := coderdtest.CreateWorkspace(t, user.client, firstUser.OrganizationID, templateID, func(cwr *codersdk.CreateWorkspaceRequest) { @@ -909,6 +915,11 @@ func TestTemplateInsights_Golden(t *testing.T) { workspace.id = createdWorkspace.ID waitWorkspaces = append(waitWorkspaces, func() { coderdtest.AwaitWorkspaceBuildJob(t, user.client, createdWorkspace.LatestBuild.ID) + ctx := testutil.Context(t, testutil.WaitShort) + ws, err := user.client.Workspace(ctx, workspace.id) + require.NoError(t, err, "want no error getting workspace") + + workspace.agentID = ws.LatestBuild.Resources[0].Agents[0].ID }) }) } @@ -1044,14 +1055,30 @@ func TestTemplateInsights_Golden(t *testing.T) { }, }, appUsage: []appUsage{ - { - app: users[0].workspaces[0].apps[0], - startedAt: time.Now().UTC().Add(-time.Hour), - endedAt: time.Now().UTC(), - requests: 1, - }, + // { // One hour of usage. + // app: users[0].workspaces[0].apps[0], + // startedAt: weekAgo, + // endedAt: weekAgo.Add(time.Hour), + // requests: 1, + // }, + // { // used an app on the last day, counts as active user. + // app: users[0].workspaces[0].apps[2], + // startedAt: weekAgo.AddDate(0, 0, 6), + // endedAt: weekAgo.AddDate(0, 0, 6).Add(12 * time.Minute), + // requests: 1, + // }, }, }, + // users[0].workspaces[1]: { + // appUsage: []appUsage{ + // { // One hour of usage, but same user and same template app, only count once. + // app: users[0].workspaces[1].apps[0], + // startedAt: weekAgo, + // endedAt: weekAgo.Add(time.Hour), + // requests: 1, + // }, + // }, + // }, } } type testRequest struct { @@ -1135,7 +1162,18 @@ func TestTemplateInsights_Golden(t *testing.T) { // Sanity check. for ws, data := range testData { for _, usage := range data.appUsage { - require.Contains(t, ws.apps, usage.app, "test bug: app %q not in workspace %q", usage.app.name, ws.name) + found := false + wrongWorkspace := false + for _, app := range ws.apps { + if usage.app == app { // Pointer equality + found = true + break + } + if *usage.app == *app { + wrongWorkspace = true + } + } + require.True(t, found, "test bug: app %q not in workspace %q [wrongWorkspace=%v]", usage.app.name, ws.name, wrongWorkspace) } } @@ -1145,7 +1183,9 @@ func TestTemplateInsights_Golden(t *testing.T) { req := req t.Run(req.name, func(t *testing.T) { t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + report, err := client.TemplateInsights(ctx, req.makeRequest(templates)) require.NoError(t, err, "want no error getting template insights") From f333170c9d39b66651acbd49903e00a93c70ce64 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 22 Aug 2023 14:53:54 +0000 Subject: [PATCH 05/33] add an hour of app usage --- coderd/insights_test.go | 12 ++++++------ ...ers_and_workspaces_week_all_templates.json.golden | 10 ++++++++++ ...s_and_workspaces_week_deployment_wide.json.golden | 10 ++++++++++ ...rs_and_workspaces_week_first_template.json.golden | 10 ++++++++++ 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index ac8bf040d8cb4..a2f5e3536240b 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1055,12 +1055,12 @@ func TestTemplateInsights_Golden(t *testing.T) { }, }, appUsage: []appUsage{ - // { // One hour of usage. - // app: users[0].workspaces[0].apps[0], - // startedAt: weekAgo, - // endedAt: weekAgo.Add(time.Hour), - // requests: 1, - // }, + { // One hour of usage. + app: users[0].workspaces[0].apps[0], + startedAt: weekAgo, + endedAt: weekAgo.Add(time.Hour), + requests: 1, + }, // { // used an app on the last day, counts as active user. // app: users[0].workspaces[0].apps[2], // startedAt: weekAgo.AddDate(0, 0, 6), diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden index 469c0b1f0984c..ef80c44e0e205 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden @@ -46,6 +46,16 @@ "slug": "ssh", "icon": "/icon/terminal.svg", "seconds": 4500 + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "type": "app", + "display_name": "app1", + "slug": "app1", + "icon": "/icon1.png", + "seconds": 3600 } ], "parameters_usage": [] diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden index 469c0b1f0984c..ef80c44e0e205 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden @@ -46,6 +46,16 @@ "slug": "ssh", "icon": "/icon/terminal.svg", "seconds": 4500 + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "type": "app", + "display_name": "app1", + "slug": "app1", + "icon": "/icon1.png", + "seconds": 3600 } ], "parameters_usage": [] diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden index 469c0b1f0984c..ef80c44e0e205 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden @@ -46,6 +46,16 @@ "slug": "ssh", "icon": "/icon/terminal.svg", "seconds": 4500 + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "type": "app", + "display_name": "app1", + "slug": "app1", + "icon": "/icon1.png", + "seconds": 3600 } ], "parameters_usage": [] From 6fb49a934f204e5ef8aebf688d87c20de070c4c9 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 22 Aug 2023 15:01:49 +0000 Subject: [PATCH 06/33] add 15 min of app usage -> active user on last day --- coderd/insights_test.go | 31 ++++++++++--------- ..._workspaces_week_all_templates.json.golden | 16 ++++++++-- ...orkspaces_week_deployment_wide.json.golden | 16 ++++++++-- ...workspaces_week_first_template.json.golden | 16 ++++++++-- 4 files changed, 59 insertions(+), 20 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index a2f5e3536240b..81c136c207a8a 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1061,24 +1061,27 @@ func TestTemplateInsights_Golden(t *testing.T) { endedAt: weekAgo.Add(time.Hour), requests: 1, }, - // { // used an app on the last day, counts as active user. - // app: users[0].workspaces[0].apps[2], - // startedAt: weekAgo.AddDate(0, 0, 6), - // endedAt: weekAgo.AddDate(0, 0, 6).Add(12 * time.Minute), + { // used an app on the last day, counts as active user, 12m -> 15m rounded. + app: users[0].workspaces[0].apps[2], + startedAt: weekAgo.AddDate(0, 0, 6), + endedAt: weekAgo.AddDate(0, 0, 6).Add(12 * time.Minute), + requests: 1, + }, + }, + }, + users[0].workspaces[1]: { + appUsage: []appUsage{ + // TODO(mafredri): This doesn't behave correctly right now + // and will add more usage to the app. This could be + // considered both correct and incorrect behavior. + // { // One hour of usage, but same user and same template app, only count once. + // app: users[0].workspaces[1].apps[0], + // startedAt: weekAgo, + // endedAt: weekAgo.Add(time.Hour), // requests: 1, // }, }, }, - // users[0].workspaces[1]: { - // appUsage: []appUsage{ - // { // One hour of usage, but same user and same template app, only count once. - // app: users[0].workspaces[1].apps[0], - // startedAt: weekAgo, - // endedAt: weekAgo.Add(time.Hour), - // requests: 1, - // }, - // }, - // }, } } type testRequest struct { diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden index ef80c44e0e205..405f1a3dd1750 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden @@ -56,6 +56,16 @@ "slug": "app1", "icon": "/icon1.png", "seconds": 3600 + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "type": "app", + "display_name": "app3", + "slug": "app3", + "icon": "/icon2.png", + "seconds": 900 } ], "parameters_usage": [] @@ -112,9 +122,11 @@ { "start_time": "2023-08-21T00:00:00Z", "end_time": "2023-08-22T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "interval": "day", - "active_users": 0 + "active_users": 1 } ] } diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden index ef80c44e0e205..405f1a3dd1750 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden @@ -56,6 +56,16 @@ "slug": "app1", "icon": "/icon1.png", "seconds": 3600 + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "type": "app", + "display_name": "app3", + "slug": "app3", + "icon": "/icon2.png", + "seconds": 900 } ], "parameters_usage": [] @@ -112,9 +122,11 @@ { "start_time": "2023-08-21T00:00:00Z", "end_time": "2023-08-22T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "interval": "day", - "active_users": 0 + "active_users": 1 } ] } diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden index ef80c44e0e205..405f1a3dd1750 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden @@ -56,6 +56,16 @@ "slug": "app1", "icon": "/icon1.png", "seconds": 3600 + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "type": "app", + "display_name": "app3", + "slug": "app3", + "icon": "/icon2.png", + "seconds": 900 } ], "parameters_usage": [] @@ -112,9 +122,11 @@ { "start_time": "2023-08-21T00:00:00Z", "end_time": "2023-08-22T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "interval": "day", - "active_users": 0 + "active_users": 1 } ] } From e9a967ea8181251c83ad6af970cc22b6333cc7c3 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 22 Aug 2023 15:02:55 +0000 Subject: [PATCH 07/33] add placeholder data --- coderd/insights_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 81c136c207a8a..579f3a253a413 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1082,6 +1082,18 @@ func TestTemplateInsights_Golden(t *testing.T) { // }, }, }, + users[0].workspaces[2]: { + agentStats: []agentStat{}, + appUsage: []appUsage{}, + }, + users[1].workspaces[0]: { + agentStats: []agentStat{}, + appUsage: []appUsage{}, + }, + users[2].workspaces[0]: { + agentStats: []agentStat{}, + appUsage: []appUsage{}, + }, } } type testRequest struct { From 9879462b989c6bf206e274ce74884d4ec096308b Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 22 Aug 2023 15:08:17 +0000 Subject: [PATCH 08/33] add identical usage in two templates for one user --- coderd/insights_test.go | 12 +++++++ ..._workspaces_week_all_templates.json.golden | 28 +++++++++------- ...orkspaces_week_deployment_wide.json.golden | 28 +++++++++------- ...orkspaces_week_second_template.json.golden | 32 +++++++++++++------ 4 files changed, 68 insertions(+), 32 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 579f3a253a413..684502bdac9e3 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1070,6 +1070,18 @@ func TestTemplateInsights_Golden(t *testing.T) { }, }, users[0].workspaces[1]: { + agentStats: []agentStat{ + { + // One hour of usage in second template at the same time + // as in first template. When selecting both templates + // this user and their app usage will only be counted + // once but the template ID will show up in the data. + startedAt: weekAgo, + endedAt: weekAgo.Add(time.Hour), + sessionCountVSCode: 1, + sessionCountSSH: 1, + }, + }, appUsage: []appUsage{ // TODO(mafredri): This doesn't behave correctly right now // and will add more usage to the app. This could be diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden index 405f1a3dd1750..2203065b97bfa 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden @@ -3,13 +3,15 @@ "start_time": "2023-08-15T00:00:00Z", "end_time": "2023-08-22T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" ], "active_users": 1, "apps_usage": [ { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" ], "type": "builtin", "display_name": "Visual Studio Code", @@ -19,7 +21,8 @@ }, { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" ], "type": "builtin", "display_name": "JetBrains", @@ -29,7 +32,8 @@ }, { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" ], "type": "builtin", "display_name": "Web Terminal", @@ -39,7 +43,8 @@ }, { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" ], "type": "builtin", "display_name": "SSH", @@ -49,7 +54,7 @@ }, { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "type": "app", "display_name": "app1", @@ -59,7 +64,7 @@ }, { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "type": "app", "display_name": "app3", @@ -75,7 +80,8 @@ "start_time": "2023-08-15T00:00:00Z", "end_time": "2023-08-16T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" ], "interval": "day", "active_users": 1 @@ -84,7 +90,7 @@ "start_time": "2023-08-16T00:00:00Z", "end_time": "2023-08-17T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "interval": "day", "active_users": 1 @@ -93,7 +99,7 @@ "start_time": "2023-08-17T00:00:00Z", "end_time": "2023-08-18T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "interval": "day", "active_users": 1 @@ -123,7 +129,7 @@ "start_time": "2023-08-21T00:00:00Z", "end_time": "2023-08-22T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "interval": "day", "active_users": 1 diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden index 405f1a3dd1750..2203065b97bfa 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden @@ -3,13 +3,15 @@ "start_time": "2023-08-15T00:00:00Z", "end_time": "2023-08-22T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" ], "active_users": 1, "apps_usage": [ { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" ], "type": "builtin", "display_name": "Visual Studio Code", @@ -19,7 +21,8 @@ }, { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" ], "type": "builtin", "display_name": "JetBrains", @@ -29,7 +32,8 @@ }, { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" ], "type": "builtin", "display_name": "Web Terminal", @@ -39,7 +43,8 @@ }, { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" ], "type": "builtin", "display_name": "SSH", @@ -49,7 +54,7 @@ }, { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "type": "app", "display_name": "app1", @@ -59,7 +64,7 @@ }, { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "type": "app", "display_name": "app3", @@ -75,7 +80,8 @@ "start_time": "2023-08-15T00:00:00Z", "end_time": "2023-08-16T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" ], "interval": "day", "active_users": 1 @@ -84,7 +90,7 @@ "start_time": "2023-08-16T00:00:00Z", "end_time": "2023-08-17T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "interval": "day", "active_users": 1 @@ -93,7 +99,7 @@ "start_time": "2023-08-17T00:00:00Z", "end_time": "2023-08-18T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "interval": "day", "active_users": 1 @@ -123,7 +129,7 @@ "start_time": "2023-08-21T00:00:00Z", "end_time": "2023-08-22T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "interval": "day", "active_users": 1 diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden index 2926f200d8ae5..4d3032a559427 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden @@ -2,19 +2,25 @@ "report": { "start_time": "2023-08-15T00:00:00Z", "end_time": "2023-08-22T00:00:00Z", - "template_ids": [], - "active_users": 0, + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "active_users": 1, "apps_usage": [ { - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "type": "builtin", "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 0 + "seconds": 3600 }, { - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "type": "builtin", "display_name": "JetBrains", "slug": "jetbrains", @@ -22,7 +28,9 @@ "seconds": 0 }, { - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "type": "builtin", "display_name": "Web Terminal", "slug": "reconnecting-pty", @@ -30,12 +38,14 @@ "seconds": 0 }, { - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "type": "builtin", "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 3600 } ], "parameters_usage": [] @@ -44,9 +54,11 @@ { "start_time": "2023-08-15T00:00:00Z", "end_time": "2023-08-16T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "interval": "day", - "active_users": 0 + "active_users": 1 }, { "start_time": "2023-08-16T00:00:00Z", From fc1a98da1a15e82f3afe6ed592dca09bdf8eef2d Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 22 Aug 2023 15:14:06 +0000 Subject: [PATCH 09/33] test app merging --- coderd/insights_test.go | 8 ++++++++ ...and_workspaces_week_all_templates.json.golden | 10 ++++++---- ...d_workspaces_week_deployment_wide.json.golden | 10 ++++++---- ...d_workspaces_week_second_template.json.golden | 16 ++++++++++++++-- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 684502bdac9e3..0f092d35ee495 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1092,6 +1092,14 @@ func TestTemplateInsights_Golden(t *testing.T) { // endedAt: weekAgo.Add(time.Hour), // requests: 1, // }, + { + // Different templates but identical apps, apps will be + // combined and usage will be summed. + app: users[0].workspaces[1].apps[0], + startedAt: weekAgo.AddDate(0, 0, 2), + endedAt: weekAgo.AddDate(0, 0, 2).Add(6 * time.Hour), + requests: 1, + }, }, }, users[0].workspaces[2]: { diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden index 2203065b97bfa..760195158c558 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden @@ -54,17 +54,18 @@ }, { "template_ids": [ + "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002" ], "type": "app", "display_name": "app1", "slug": "app1", "icon": "/icon1.png", - "seconds": 3600 + "seconds": 25200 }, { "template_ids": [ - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000001" ], "type": "app", "display_name": "app3", @@ -90,7 +91,7 @@ "start_time": "2023-08-16T00:00:00Z", "end_time": "2023-08-17T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000001" ], "interval": "day", "active_users": 1 @@ -99,6 +100,7 @@ "start_time": "2023-08-17T00:00:00Z", "end_time": "2023-08-18T00:00:00Z", "template_ids": [ + "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002" ], "interval": "day", @@ -129,7 +131,7 @@ "start_time": "2023-08-21T00:00:00Z", "end_time": "2023-08-22T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000001" ], "interval": "day", "active_users": 1 diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden index 2203065b97bfa..760195158c558 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden @@ -54,17 +54,18 @@ }, { "template_ids": [ + "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002" ], "type": "app", "display_name": "app1", "slug": "app1", "icon": "/icon1.png", - "seconds": 3600 + "seconds": 25200 }, { "template_ids": [ - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000001" ], "type": "app", "display_name": "app3", @@ -90,7 +91,7 @@ "start_time": "2023-08-16T00:00:00Z", "end_time": "2023-08-17T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000001" ], "interval": "day", "active_users": 1 @@ -99,6 +100,7 @@ "start_time": "2023-08-17T00:00:00Z", "end_time": "2023-08-18T00:00:00Z", "template_ids": [ + "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002" ], "interval": "day", @@ -129,7 +131,7 @@ "start_time": "2023-08-21T00:00:00Z", "end_time": "2023-08-22T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000001" ], "interval": "day", "active_users": 1 diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden index 4d3032a559427..6d26839386aec 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden @@ -46,6 +46,16 @@ "slug": "ssh", "icon": "/icon/terminal.svg", "seconds": 3600 + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "type": "app", + "display_name": "app1", + "slug": "app1", + "icon": "/icon1.png", + "seconds": 21600 } ], "parameters_usage": [] @@ -70,9 +80,11 @@ { "start_time": "2023-08-17T00:00:00Z", "end_time": "2023-08-18T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "interval": "day", - "active_users": 0 + "active_users": 1 }, { "start_time": "2023-08-18T00:00:00Z", From f14ddf6beeb509979839cc95322a2166fabafdd7 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 22 Aug 2023 16:01:52 +0000 Subject: [PATCH 10/33] fix(codersdk): use url values for query params --- codersdk/insights.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/codersdk/insights.go b/codersdk/insights.go index f4739f3abf5e6..6780b82d45d43 100644 --- a/codersdk/insights.go +++ b/codersdk/insights.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strings" "time" @@ -61,18 +62,18 @@ type UserLatencyInsightsRequest struct { } func (c *Client) UserLatencyInsights(ctx context.Context, req UserLatencyInsightsRequest) (UserLatencyInsightsResponse, error) { - var qp []string - qp = append(qp, fmt.Sprintf("start_time=%s", req.StartTime.Format(insightsTimeLayout))) - qp = append(qp, fmt.Sprintf("end_time=%s", req.EndTime.Format(insightsTimeLayout))) + qp := url.Values{} + qp.Add("start_time", req.StartTime.Format(insightsTimeLayout)) + qp.Add("end_time", req.EndTime.Format(insightsTimeLayout)) if len(req.TemplateIDs) > 0 { var templateIDs []string for _, id := range req.TemplateIDs { templateIDs = append(templateIDs, id.String()) } - qp = append(qp, fmt.Sprintf("template_ids=%s", strings.Join(templateIDs, ","))) + qp.Add("template_ids", strings.Join(templateIDs, ",")) } - reqURL := fmt.Sprintf("/api/v2/insights/user-latency?%s", strings.Join(qp, "&")) + reqURL := fmt.Sprintf("/api/v2/insights/user-latency?%s", qp.Encode()) resp, err := c.Request(ctx, http.MethodGet, reqURL, nil) if err != nil { return UserLatencyInsightsResponse{}, xerrors.Errorf("make request: %w", err) @@ -158,21 +159,21 @@ type TemplateInsightsRequest struct { } func (c *Client) TemplateInsights(ctx context.Context, req TemplateInsightsRequest) (TemplateInsightsResponse, error) { - var qp []string - qp = append(qp, fmt.Sprintf("start_time=%s", req.StartTime.Format(insightsTimeLayout))) - qp = append(qp, fmt.Sprintf("end_time=%s", req.EndTime.Format(insightsTimeLayout))) + qp := url.Values{} + qp.Add("start_time", req.StartTime.Format(insightsTimeLayout)) + qp.Add("end_time", req.EndTime.Format(insightsTimeLayout)) if len(req.TemplateIDs) > 0 { var templateIDs []string for _, id := range req.TemplateIDs { templateIDs = append(templateIDs, id.String()) } - qp = append(qp, fmt.Sprintf("template_ids=%s", strings.Join(templateIDs, ","))) + qp.Add("template_ids", strings.Join(templateIDs, ",")) } if req.Interval != "" { - qp = append(qp, fmt.Sprintf("interval=%s", req.Interval)) + qp.Add("interval", string(req.Interval)) } - reqURL := fmt.Sprintf("/api/v2/insights/templates?%s", strings.Join(qp, "&")) + reqURL := fmt.Sprintf("/api/v2/insights/templates?%s", qp.Encode()) resp, err := c.Request(ctx, http.MethodGet, reqURL, nil) if err != nil { return TemplateInsightsResponse{}, xerrors.Errorf("make request: %w", err) From 8a6056897085e4a5d87f8723ab138d23eadd66db Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 22 Aug 2023 16:56:36 +0000 Subject: [PATCH 11/33] improve stability of template id --- coderd/insights_test.go | 74 ++++++++++--------- ...orkspaces_week_second_template.json.golden | 16 ++-- 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 0f092d35ee495..ea74556ae8dc9 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -24,6 +24,7 @@ import ( "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/rbac" + "github.com/coder/coder/v2/coderd/util/slice" "github.com/coder/coder/v2/coderd/workspaceapps" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" @@ -579,36 +580,16 @@ func TestTemplateInsights(t *testing.T) { func TestTemplateInsights_Golden(t *testing.T) { t.Parallel() - stabilizeReportForGoldenComparison := func(report *codersdk.TemplateInsightsResponse) { - var stableTemplateIDs []uuid.UUID - stableTemplateIDMap := make(map[uuid.UUID]uuid.UUID) - toStableTemplateID := func(id uuid.UUID) uuid.UUID { - if stableID, ok := stableTemplateIDMap[id]; ok { - return stableID - } - stableTemplateIDs = append(stableTemplateIDs, uuid.MustParse(fmt.Sprintf("00000000-0000-0000-0000-%012d", len(stableTemplateIDs)+1))) - stableID := stableTemplateIDs[len(stableTemplateIDs)-1] - stableTemplateIDMap[id] = stableID - return stableID - } - - for i, id := range report.Report.TemplateIDs { - report.Report.TemplateIDs[i] = toStableTemplateID(id) - } + stabilizeReportForGoldenComparison := func(report *codersdk.TemplateInsightsResponse, toStableTemplateIDs func([]uuid.UUID)) { + toStableTemplateIDs(report.Report.TemplateIDs) for _, param := range report.Report.ParametersUsage { - for i, id := range param.TemplateIDs { - param.TemplateIDs[i] = toStableTemplateID(id) - } + toStableTemplateIDs(param.TemplateIDs) } for _, app := range report.Report.AppsUsage { - for i, id := range app.TemplateIDs { - app.TemplateIDs[i] = toStableTemplateID(id) - } + toStableTemplateIDs(app.TemplateIDs) } for _, intervalReport := range report.IntervalReports { - for i, id := range intervalReport.TemplateIDs { - intervalReport.TemplateIDs[i] = toStableTemplateID(id) - } + toStableTemplateIDs(intervalReport.TemplateIDs) } } @@ -811,7 +792,7 @@ func TestTemplateInsights_Golden(t *testing.T) { return templates, users } - prepare := func(t *testing.T, templates []*testTemplate, users []*testUser, testData map[*testWorkspace]testDataGen) *codersdk.Client { + prepare := func(t *testing.T, templates []*testTemplate, users []*testUser, testData map[*testWorkspace]testDataGen) (*codersdk.Client, func([]uuid.UUID)) { logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: false}).Leveled(slog.LevelDebug) opts := &coderdtest.Options{ Logger: &logger, @@ -1024,7 +1005,30 @@ func TestTemplateInsights_Golden(t *testing.T) { err = reporter.Report(dbauthz.AsSystemRestricted(ctx), stats) require.NoError(t, err, "want no error inserting app stats") - return client + var stableTemplateIDs []uuid.UUID + stableTemplateIDMap := make(map[uuid.UUID]uuid.UUID) + toStableTemplateID := func(id uuid.UUID) uuid.UUID { + if stableID, ok := stableTemplateIDMap[id]; ok { + return stableID + } + stableTemplateIDs = append(stableTemplateIDs, uuid.MustParse(fmt.Sprintf("00000000-0000-0000-0000-%012d", len(stableTemplateIDs)+1))) + stableID := stableTemplateIDs[len(stableTemplateIDs)-1] + stableTemplateIDMap[id] = stableID + return stableID + } + // Prime the map. + for _, template := range templates { + _ = toStableTemplateID(template.id) + } + + return client, func(ids []uuid.UUID) { + for i, id := range ids { + ids[i] = toStableTemplateID(id) + } + slices.SortFunc(ids, func(a, b uuid.UUID) int { + return slice.Ascending(a.String(), b.String()) + }) + } } // Time range for report, test data will be generated within and @@ -1126,11 +1130,11 @@ func TestTemplateInsights_Golden(t *testing.T) { requests []testRequest }{ { - name: "multiple users and workspaces week", + name: "multiple users and workspaces", makeTestData: makeBaseTestData, requests: []testRequest{ { - name: "deployment wide", + name: "week deployment wide", makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { return codersdk.TemplateInsightsRequest{ StartTime: weekAgo, @@ -1140,7 +1144,7 @@ func TestTemplateInsights_Golden(t *testing.T) { }, }, { - name: "all templates", + name: "week all templates", makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { return codersdk.TemplateInsightsRequest{ TemplateIDs: []uuid.UUID{templates[0].id, templates[1].id, templates[2].id}, @@ -1151,7 +1155,7 @@ func TestTemplateInsights_Golden(t *testing.T) { }, }, { - name: "first template", + name: "week first template", makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { return codersdk.TemplateInsightsRequest{ TemplateIDs: []uuid.UUID{templates[0].id}, @@ -1162,7 +1166,7 @@ func TestTemplateInsights_Golden(t *testing.T) { }, }, { - name: "second template", + name: "week second template", makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { return codersdk.TemplateInsightsRequest{ TemplateIDs: []uuid.UUID{templates[1].id}, @@ -1173,7 +1177,7 @@ func TestTemplateInsights_Golden(t *testing.T) { }, }, { - name: "third template", + name: "week third template", makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { return codersdk.TemplateInsightsRequest{ TemplateIDs: []uuid.UUID{templates[2].id}, @@ -1212,7 +1216,7 @@ func TestTemplateInsights_Golden(t *testing.T) { } } - client := prepare(t, templates, users, testData) + client, toStableTemplateID := prepare(t, templates, users, testData) for _, req := range tt.requests { req := req @@ -1224,7 +1228,7 @@ func TestTemplateInsights_Golden(t *testing.T) { report, err := client.TemplateInsights(ctx, req.makeRequest(templates)) require.NoError(t, err, "want no error getting template insights") - stabilizeReportForGoldenComparison(&report) + stabilizeReportForGoldenComparison(&report, toStableTemplateID) partialName := strings.Join(strings.Split(t.Name(), "/")[1:], "_") goldenFile := filepath.Join("testdata", "insights", partialName+".json.golden") diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden index 6d26839386aec..b15cba10a8520 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_second_template.json.golden @@ -3,13 +3,13 @@ "start_time": "2023-08-15T00:00:00Z", "end_time": "2023-08-22T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "active_users": 1, "apps_usage": [ { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "type": "builtin", "display_name": "Visual Studio Code", @@ -19,7 +19,7 @@ }, { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "type": "builtin", "display_name": "JetBrains", @@ -29,7 +29,7 @@ }, { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "type": "builtin", "display_name": "Web Terminal", @@ -39,7 +39,7 @@ }, { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "type": "builtin", "display_name": "SSH", @@ -49,7 +49,7 @@ }, { "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "type": "app", "display_name": "app1", @@ -65,7 +65,7 @@ "start_time": "2023-08-15T00:00:00Z", "end_time": "2023-08-16T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "interval": "day", "active_users": 1 @@ -81,7 +81,7 @@ "start_time": "2023-08-17T00:00:00Z", "end_time": "2023-08-18T00:00:00Z", "template_ids": [ - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000002" ], "interval": "day", "active_users": 1 From 7d93d1303f859275f0237d67f17f91848687569c Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 22 Aug 2023 17:13:35 +0000 Subject: [PATCH 12/33] add sao paulo tz test (invalid interval times) --- coderd/insights_test.go | 17 +++ ...r_timezone_(S\303\243o_Paulo).json.golden" | 134 ++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 "coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" diff --git a/coderd/insights_test.go b/coderd/insights_test.go index ea74556ae8dc9..a1595517ddbea 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1037,6 +1037,11 @@ func TestTemplateInsights_Golden(t *testing.T) { lastNight := time.Now().UTC().Truncate(24 * time.Hour) weekAgo := lastNight.AddDate(0, 0, -7) + saoPaulo, err := time.LoadLocation("America/Sao_Paulo") + require.NoError(t, err) + weekAgoSaoPaulo, err := time.ParseInLocation(time.DateTime, weekAgo.Format(time.DateTime), saoPaulo) + require.NoError(t, err) + makeBaseTestData := func(templates []*testTemplate, users []*testUser) map[*testWorkspace]testDataGen { return map[*testWorkspace]testDataGen{ users[0].workspaces[0]: { @@ -1187,6 +1192,18 @@ func TestTemplateInsights_Golden(t *testing.T) { } }, }, + { + // São Paulo is three hours behind UTC, so we should not see + // any data between weekAgo and weekAgo.Add(3 * time.Hour). + name: "week other timezone (São Paulo)", + makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { + return codersdk.TemplateInsightsRequest{ + StartTime: weekAgoSaoPaulo, + EndTime: weekAgoSaoPaulo.AddDate(0, 0, 7), + Interval: codersdk.InsightsReportIntervalDay, + } + }, + }, }, }, } diff --git "a/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" "b/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" new file mode 100644 index 0000000000000..66eccf17bedad --- /dev/null +++ "b/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" @@ -0,0 +1,134 @@ +{ + "report": { + "start_time": "2023-08-15T00:00:00-03:00", + "end_time": "2023-08-22T00:00:00-03:00", + "template_ids": [ + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" + ], + "active_users": 1, + "apps_usage": [ + { + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "type": "builtin", + "display_name": "Visual Studio Code", + "slug": "vscode", + "icon": "/icon/code.svg", + "seconds": 0 + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "type": "builtin", + "display_name": "JetBrains", + "slug": "jetbrains", + "icon": "/icon/intellij.svg", + "seconds": 600 + }, + { + "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": 900 + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000002" + ], + "type": "app", + "display_name": "app1", + "slug": "app1", + "icon": "/icon1.png", + "seconds": 21600 + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "type": "app", + "display_name": "app3", + "slug": "app3", + "icon": "/icon2.png", + "seconds": 900 + } + ], + "parameters_usage": [] + }, + "interval_reports": [ + { + "start_time": "2023-08-15T03:00:00Z", + "end_time": "2023-08-16T03:00:00Z", + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "interval": "day", + "active_users": 1 + }, + { + "start_time": "2023-08-16T03:00:00Z", + "end_time": "2023-08-17T03:00:00Z", + "template_ids": [ + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" + ], + "interval": "day", + "active_users": 1 + }, + { + "start_time": "2023-08-17T03:00:00Z", + "end_time": "2023-08-18T03:00:00Z", + "template_ids": [ + "00000000-0000-0000-0000-000000000002" + ], + "interval": "day", + "active_users": 1 + }, + { + "start_time": "2023-08-18T03:00:00Z", + "end_time": "2023-08-19T03:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-19T03:00:00Z", + "end_time": "2023-08-20T03:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + }, + { + "start_time": "2023-08-20T03:00:00Z", + "end_time": "2023-08-21T03:00:00Z", + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "interval": "day", + "active_users": 1 + }, + { + "start_time": "2023-08-21T03:00:00Z", + "end_time": "2023-08-22T03:00:00Z", + "template_ids": [], + "interval": "day", + "active_users": 0 + } + ] +} From f9fdb47d689255f2b7cc803ff2fe021623a7c5a0 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 22 Aug 2023 17:14:46 +0000 Subject: [PATCH 13/33] fix start/end time tz returned in interval report --- coderd/insights.go | 6 ++-- ...r_timezone_(S\303\243o_Paulo).json.golden" | 28 +++++++++---------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/coderd/insights.go b/coderd/insights.go index 80865b9287ec6..dc8be8291af13 100644 --- a/coderd/insights.go +++ b/coderd/insights.go @@ -288,8 +288,10 @@ func (api *API) insightsTemplates(rw http.ResponseWriter, r *http.Request) { } for _, row := range dailyUsage { resp.IntervalReports = append(resp.IntervalReports, codersdk.TemplateInsightsIntervalReport{ - StartTime: row.StartTime, - EndTime: row.EndTime, + // NOTE(mafredri): This might not be accurate over DST since the + // parsed location only contains the offset. + StartTime: row.StartTime.In(startTime.Location()), + EndTime: row.EndTime.In(startTime.Location()), Interval: interval, TemplateIDs: row.TemplateIDs, ActiveUsers: row.ActiveUsers, diff --git "a/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" "b/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" index 66eccf17bedad..e6c72500b6b92 100644 --- "a/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" +++ "b/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" @@ -73,8 +73,8 @@ }, "interval_reports": [ { - "start_time": "2023-08-15T03:00:00Z", - "end_time": "2023-08-16T03:00:00Z", + "start_time": "2023-08-15T00:00:00-03:00", + "end_time": "2023-08-16T00:00:00-03:00", "template_ids": [ "00000000-0000-0000-0000-000000000001" ], @@ -82,8 +82,8 @@ "active_users": 1 }, { - "start_time": "2023-08-16T03:00:00Z", - "end_time": "2023-08-17T03:00:00Z", + "start_time": "2023-08-16T00:00:00-03:00", + "end_time": "2023-08-17T00:00:00-03:00", "template_ids": [ "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002" @@ -92,8 +92,8 @@ "active_users": 1 }, { - "start_time": "2023-08-17T03:00:00Z", - "end_time": "2023-08-18T03:00:00Z", + "start_time": "2023-08-17T00:00:00-03:00", + "end_time": "2023-08-18T00:00:00-03:00", "template_ids": [ "00000000-0000-0000-0000-000000000002" ], @@ -101,22 +101,22 @@ "active_users": 1 }, { - "start_time": "2023-08-18T03:00:00Z", - "end_time": "2023-08-19T03:00:00Z", + "start_time": "2023-08-18T00:00:00-03:00", + "end_time": "2023-08-19T00:00:00-03:00", "template_ids": [], "interval": "day", "active_users": 0 }, { - "start_time": "2023-08-19T03:00:00Z", - "end_time": "2023-08-20T03:00:00Z", + "start_time": "2023-08-19T00:00:00-03:00", + "end_time": "2023-08-20T00:00:00-03:00", "template_ids": [], "interval": "day", "active_users": 0 }, { - "start_time": "2023-08-20T03:00:00Z", - "end_time": "2023-08-21T03:00:00Z", + "start_time": "2023-08-20T00:00:00-03:00", + "end_time": "2023-08-21T00:00:00-03:00", "template_ids": [ "00000000-0000-0000-0000-000000000001" ], @@ -124,8 +124,8 @@ "active_users": 1 }, { - "start_time": "2023-08-21T03:00:00Z", - "end_time": "2023-08-22T03:00:00Z", + "start_time": "2023-08-21T00:00:00-03:00", + "end_time": "2023-08-22T00:00:00-03:00", "template_ids": [], "interval": "day", "active_users": 0 From 54638b03fbb31a761b401f29a69803ab4ea45b61 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 22 Aug 2023 17:41:55 +0000 Subject: [PATCH 14/33] cleanup --- coderd/insights_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index a1595517ddbea..8d08a56a6fbdf 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1143,7 +1143,7 @@ func TestTemplateInsights_Golden(t *testing.T) { makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { return codersdk.TemplateInsightsRequest{ StartTime: weekAgo, - EndTime: lastNight, + EndTime: weekAgo.AddDate(0, 0, 7), Interval: codersdk.InsightsReportIntervalDay, } }, @@ -1154,7 +1154,7 @@ func TestTemplateInsights_Golden(t *testing.T) { return codersdk.TemplateInsightsRequest{ TemplateIDs: []uuid.UUID{templates[0].id, templates[1].id, templates[2].id}, StartTime: weekAgo, - EndTime: lastNight, + EndTime: weekAgo.AddDate(0, 0, 7), Interval: codersdk.InsightsReportIntervalDay, } }, @@ -1165,7 +1165,7 @@ func TestTemplateInsights_Golden(t *testing.T) { return codersdk.TemplateInsightsRequest{ TemplateIDs: []uuid.UUID{templates[0].id}, StartTime: weekAgo, - EndTime: lastNight, + EndTime: weekAgo.AddDate(0, 0, 7), Interval: codersdk.InsightsReportIntervalDay, } }, @@ -1176,7 +1176,7 @@ func TestTemplateInsights_Golden(t *testing.T) { return codersdk.TemplateInsightsRequest{ TemplateIDs: []uuid.UUID{templates[1].id}, StartTime: weekAgo, - EndTime: lastNight, + EndTime: weekAgo.AddDate(0, 0, 7), Interval: codersdk.InsightsReportIntervalDay, } }, @@ -1187,7 +1187,7 @@ func TestTemplateInsights_Golden(t *testing.T) { return codersdk.TemplateInsightsRequest{ TemplateIDs: []uuid.UUID{templates[2].id}, StartTime: weekAgo, - EndTime: lastNight, + EndTime: weekAgo.AddDate(0, 0, 7), Interval: codersdk.InsightsReportIntervalDay, } }, @@ -1233,7 +1233,7 @@ func TestTemplateInsights_Golden(t *testing.T) { } } - client, toStableTemplateID := prepare(t, templates, users, testData) + client, toStableTemplateIDs := prepare(t, templates, users, testData) for _, req := range tt.requests { req := req @@ -1245,7 +1245,7 @@ func TestTemplateInsights_Golden(t *testing.T) { report, err := client.TemplateInsights(ctx, req.makeRequest(templates)) require.NoError(t, err, "want no error getting template insights") - stabilizeReportForGoldenComparison(&report, toStableTemplateID) + stabilizeReportForGoldenComparison(&report, toStableTemplateIDs) partialName := strings.Join(strings.Split(t.Name(), "/")[1:], "_") goldenFile := filepath.Join("testdata", "insights", partialName+".json.golden") From 7b0ac4cc85786d6ec2db0aa15db070da723487d9 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 12:30:06 +0000 Subject: [PATCH 15/33] refactor template creation with stable IDs --- coderd/database/queries.sql.go | 1 + coderd/database/queries/insights.sql | 3 +- coderd/insights_test.go | 164 ++++++++++++++------------- 3 files changed, 87 insertions(+), 81 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index f1bb80a6e0afd..60d1a8d9f1ac7 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1520,6 +1520,7 @@ SELECT SUM(seconds) AS usage_seconds FROM app_stats_by_user_and_agent GROUP BY access_method, slug_or_port, display_name, icon, is_app +ORDER BY access_method, slug_or_port, display_name, icon, is_app ` type GetTemplateAppInsightsParams struct { diff --git a/coderd/database/queries/insights.sql b/coderd/database/queries/insights.sql index 8f1f3e3bb0b60..20190a4717c49 100644 --- a/coderd/database/queries/insights.sql +++ b/coderd/database/queries/insights.sql @@ -133,7 +133,8 @@ SELECT is_app, SUM(seconds) AS usage_seconds FROM app_stats_by_user_and_agent -GROUP BY access_method, slug_or_port, display_name, icon, is_app; +GROUP BY access_method, slug_or_port, display_name, icon, is_app +ORDER BY access_method, slug_or_port, display_name, icon, is_app; -- name: GetTemplateDailyInsights :many -- GetTemplateDailyInsights returns all daily intervals between start and end diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 8d08a56a6fbdf..6a2497865f1dd 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -22,9 +22,11 @@ import ( "github.com/coder/coder/v2/agent" "github.com/coder/coder/v2/coderd/batchstats" "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" + "github.com/coder/coder/v2/coderd/database/dbgen" + "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/coderd/rbac" - "github.com/coder/coder/v2/coderd/util/slice" "github.com/coder/coder/v2/coderd/workspaceapps" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" @@ -580,19 +582,6 @@ func TestTemplateInsights(t *testing.T) { func TestTemplateInsights_Golden(t *testing.T) { t.Parallel() - stabilizeReportForGoldenComparison := func(report *codersdk.TemplateInsightsResponse, toStableTemplateIDs func([]uuid.UUID)) { - toStableTemplateIDs(report.Report.TemplateIDs) - for _, param := range report.Report.ParametersUsage { - toStableTemplateIDs(param.TemplateIDs) - } - for _, app := range report.Report.AppsUsage { - toStableTemplateIDs(app.TemplateIDs) - } - for _, intervalReport := range report.IntervalReports { - toStableTemplateIDs(intervalReport.TemplateIDs) - } - } - // Prepare test data types. type templateParameterOption struct { name string @@ -613,7 +602,7 @@ func TestTemplateInsights_Golden(t *testing.T) { apps []templateApp // Filled later. - id uuid.UUID // Set to the created template ID. + id uuid.UUID } type buildParameter struct { templateParameter *templateParameter @@ -778,7 +767,17 @@ func TestTemplateInsights_Golden(t *testing.T) { }, } - // Post-process workspaces. + // Post-process. + var stableIDs []uuid.UUID + newStableUUID := func() uuid.UUID { + stableIDs = append(stableIDs, uuid.MustParse(fmt.Sprintf("00000000-0000-0000-0000-%012d", len(stableIDs)+1))) + stableID := stableIDs[len(stableIDs)-1] + return stableID + } + + for _, template := range templates { + template.id = newStableUUID() + } for _, user := range users { for _, workspace := range user.workspaces { workspace.user = user @@ -792,14 +791,16 @@ func TestTemplateInsights_Golden(t *testing.T) { return templates, users } - prepare := func(t *testing.T, templates []*testTemplate, users []*testUser, testData map[*testWorkspace]testDataGen) (*codersdk.Client, func([]uuid.UUID)) { + prepare := func(t *testing.T, templates []*testTemplate, users []*testUser, testData map[*testWorkspace]testDataGen) *codersdk.Client { logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: false}).Leveled(slog.LevelDebug) - opts := &coderdtest.Options{ + db, pubsub := dbtestutil.NewDB(t) + client := coderdtest.New(t, &coderdtest.Options{ + Database: db, + Pubsub: pubsub, Logger: &logger, IncludeProvisionerDaemon: true, AgentStatsRefreshInterval: time.Hour, // Not relevant for this test. - } - client, _, coderdAPI := coderdtest.NewWithAPI(t, opts) + }) firstUser := coderdtest.CreateFirstUser(t, client) // Prepare all test users. @@ -926,11 +927,28 @@ func TestTemplateInsights_Golden(t *testing.T) { }, }}, }) - createdTemplate := coderdtest.CreateTemplate(t, client, firstUser.OrganizationID, version.ID) - require.Empty(t, createdTemplate.BuildTimeStats[codersdk.WorkspaceTransitionStart]) coderdtest.AwaitTemplateVersionJob(t, client, version.ID) - template.id = createdTemplate.ID + // Create template, essentially a modified version of CreateTemplate + // where we can control the template ID. + // createdTemplate := coderdtest.CreateTemplate(t, client, firstUser.OrganizationID, version.ID) + createdTemplate := dbgen.Template(t, db, database.Template{ + ID: template.id, + ActiveVersionID: version.ID, + OrganizationID: firstUser.OrganizationID, + CreatedBy: firstUser.UserID, + GroupACL: database.TemplateACL{ + firstUser.OrganizationID.String(): []rbac.Action{rbac.ActionRead}, + }, + }) + err := db.UpdateTemplateVersionByID(context.Background(), database.UpdateTemplateVersionByIDParams{ + ID: version.ID, + TemplateID: uuid.NullUUID{ + UUID: createdTemplate.ID, + Valid: true, + }, + }) + require.NoError(t, err, "want no error updating template version") // Create all workspaces and wait for them. for _, createWorkspace := range createWorkspaces { @@ -949,7 +967,7 @@ func TestTemplateInsights_Golden(t *testing.T) { // the database. batcher, batcherCloser, err := batchstats.New( ctx, - batchstats.WithStore(coderdAPI.Database), + batchstats.WithStore(db), batchstats.WithLogger(logger.Named("batchstats")), batchstats.WithInterval(time.Hour), ) @@ -1000,46 +1018,23 @@ func TestTemplateInsights_Golden(t *testing.T) { }) } } - reporter := workspaceapps.NewStatsDBReporter(coderdAPI.Database, workspaceapps.DefaultStatsDBReporterBatchSize) + reporter := workspaceapps.NewStatsDBReporter(db, workspaceapps.DefaultStatsDBReporterBatchSize) //nolint:gocritic // This is a test. err = reporter.Report(dbauthz.AsSystemRestricted(ctx), stats) require.NoError(t, err, "want no error inserting app stats") - var stableTemplateIDs []uuid.UUID - stableTemplateIDMap := make(map[uuid.UUID]uuid.UUID) - toStableTemplateID := func(id uuid.UUID) uuid.UUID { - if stableID, ok := stableTemplateIDMap[id]; ok { - return stableID - } - stableTemplateIDs = append(stableTemplateIDs, uuid.MustParse(fmt.Sprintf("00000000-0000-0000-0000-%012d", len(stableTemplateIDs)+1))) - stableID := stableTemplateIDs[len(stableTemplateIDs)-1] - stableTemplateIDMap[id] = stableID - return stableID - } - // Prime the map. - for _, template := range templates { - _ = toStableTemplateID(template.id) - } - - return client, func(ids []uuid.UUID) { - for i, id := range ids { - ids[i] = toStableTemplateID(id) - } - slices.SortFunc(ids, func(a, b uuid.UUID) int { - return slice.Ascending(a.String(), b.String()) - }) - } + return client } // Time range for report, test data will be generated within and // outside this range, but only data within the range should be // included in the report. - lastNight := time.Now().UTC().Truncate(24 * time.Hour) - weekAgo := lastNight.AddDate(0, 0, -7) + frozenLastNight := time.Date(2023, 8, 22, 0, 0, 0, 0, time.UTC) + frozenWeekAgo := frozenLastNight.AddDate(0, 0, -7) saoPaulo, err := time.LoadLocation("America/Sao_Paulo") require.NoError(t, err) - weekAgoSaoPaulo, err := time.ParseInLocation(time.DateTime, weekAgo.Format(time.DateTime), saoPaulo) + frozenWeekAgoSaoPaulo, err := time.ParseInLocation(time.DateTime, frozenWeekAgo.Format(time.DateTime), saoPaulo) require.NoError(t, err) makeBaseTestData := func(templates []*testTemplate, users []*testUser) map[*testWorkspace]testDataGen { @@ -1047,33 +1042,33 @@ func TestTemplateInsights_Golden(t *testing.T) { users[0].workspaces[0]: { agentStats: []agentStat{ { // One hour of usage. - startedAt: weekAgo, - endedAt: weekAgo.Add(time.Hour), + startedAt: frozenWeekAgo, + endedAt: frozenWeekAgo.Add(time.Hour), sessionCountVSCode: 1, sessionCountSSH: 1, }, { // 12 minutes of usage -> 15 minutes. - startedAt: weekAgo.AddDate(0, 0, 1), - endedAt: weekAgo.AddDate(0, 0, 1).Add(12 * time.Minute), + startedAt: frozenWeekAgo.AddDate(0, 0, 1), + endedAt: frozenWeekAgo.AddDate(0, 0, 1).Add(12 * time.Minute), sessionCountSSH: 1, }, { // 2 minutes of usage -> 10 minutes because it crosses the 5 minute interval boundary. - startedAt: weekAgo.AddDate(0, 0, 2).Add(4 * time.Minute), - endedAt: weekAgo.AddDate(0, 0, 2).Add(6 * time.Minute), + startedAt: frozenWeekAgo.AddDate(0, 0, 2).Add(4 * time.Minute), + endedAt: frozenWeekAgo.AddDate(0, 0, 2).Add(6 * time.Minute), sessionCountJetBrains: 1, }, }, appUsage: []appUsage{ { // One hour of usage. app: users[0].workspaces[0].apps[0], - startedAt: weekAgo, - endedAt: weekAgo.Add(time.Hour), + startedAt: frozenWeekAgo, + endedAt: frozenWeekAgo.Add(time.Hour), requests: 1, }, { // used an app on the last day, counts as active user, 12m -> 15m rounded. app: users[0].workspaces[0].apps[2], - startedAt: weekAgo.AddDate(0, 0, 6), - endedAt: weekAgo.AddDate(0, 0, 6).Add(12 * time.Minute), + startedAt: frozenWeekAgo.AddDate(0, 0, 6), + endedAt: frozenWeekAgo.AddDate(0, 0, 6).Add(12 * time.Minute), requests: 1, }, }, @@ -1085,8 +1080,8 @@ func TestTemplateInsights_Golden(t *testing.T) { // as in first template. When selecting both templates // this user and their app usage will only be counted // once but the template ID will show up in the data. - startedAt: weekAgo, - endedAt: weekAgo.Add(time.Hour), + startedAt: frozenWeekAgo, + endedAt: frozenWeekAgo.Add(time.Hour), sessionCountVSCode: 1, sessionCountSSH: 1, }, @@ -1105,8 +1100,8 @@ func TestTemplateInsights_Golden(t *testing.T) { // Different templates but identical apps, apps will be // combined and usage will be summed. app: users[0].workspaces[1].apps[0], - startedAt: weekAgo.AddDate(0, 0, 2), - endedAt: weekAgo.AddDate(0, 0, 2).Add(6 * time.Hour), + startedAt: frozenWeekAgo.AddDate(0, 0, 2), + endedAt: frozenWeekAgo.AddDate(0, 0, 2).Add(6 * time.Hour), requests: 1, }, }, @@ -1128,6 +1123,7 @@ func TestTemplateInsights_Golden(t *testing.T) { type testRequest struct { name string makeRequest func([]*testTemplate) codersdk.TemplateInsightsRequest + ignoreTimes bool } tests := []struct { name string @@ -1142,8 +1138,8 @@ func TestTemplateInsights_Golden(t *testing.T) { name: "week deployment wide", makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { return codersdk.TemplateInsightsRequest{ - StartTime: weekAgo, - EndTime: weekAgo.AddDate(0, 0, 7), + StartTime: frozenWeekAgo, + EndTime: frozenWeekAgo.AddDate(0, 0, 7), Interval: codersdk.InsightsReportIntervalDay, } }, @@ -1153,8 +1149,8 @@ func TestTemplateInsights_Golden(t *testing.T) { makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { return codersdk.TemplateInsightsRequest{ TemplateIDs: []uuid.UUID{templates[0].id, templates[1].id, templates[2].id}, - StartTime: weekAgo, - EndTime: weekAgo.AddDate(0, 0, 7), + StartTime: frozenWeekAgo, + EndTime: frozenWeekAgo.AddDate(0, 0, 7), Interval: codersdk.InsightsReportIntervalDay, } }, @@ -1164,8 +1160,8 @@ func TestTemplateInsights_Golden(t *testing.T) { makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { return codersdk.TemplateInsightsRequest{ TemplateIDs: []uuid.UUID{templates[0].id}, - StartTime: weekAgo, - EndTime: weekAgo.AddDate(0, 0, 7), + StartTime: frozenWeekAgo, + EndTime: frozenWeekAgo.AddDate(0, 0, 7), Interval: codersdk.InsightsReportIntervalDay, } }, @@ -1175,8 +1171,8 @@ func TestTemplateInsights_Golden(t *testing.T) { makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { return codersdk.TemplateInsightsRequest{ TemplateIDs: []uuid.UUID{templates[1].id}, - StartTime: weekAgo, - EndTime: weekAgo.AddDate(0, 0, 7), + StartTime: frozenWeekAgo, + EndTime: frozenWeekAgo.AddDate(0, 0, 7), Interval: codersdk.InsightsReportIntervalDay, } }, @@ -1186,8 +1182,8 @@ func TestTemplateInsights_Golden(t *testing.T) { makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { return codersdk.TemplateInsightsRequest{ TemplateIDs: []uuid.UUID{templates[2].id}, - StartTime: weekAgo, - EndTime: weekAgo.AddDate(0, 0, 7), + StartTime: frozenWeekAgo, + EndTime: frozenWeekAgo.AddDate(0, 0, 7), Interval: codersdk.InsightsReportIntervalDay, } }, @@ -1198,8 +1194,8 @@ func TestTemplateInsights_Golden(t *testing.T) { name: "week other timezone (São Paulo)", makeRequest: func(templates []*testTemplate) codersdk.TemplateInsightsRequest { return codersdk.TemplateInsightsRequest{ - StartTime: weekAgoSaoPaulo, - EndTime: weekAgoSaoPaulo.AddDate(0, 0, 7), + StartTime: frozenWeekAgoSaoPaulo, + EndTime: frozenWeekAgoSaoPaulo.AddDate(0, 0, 7), Interval: codersdk.InsightsReportIntervalDay, } }, @@ -1233,7 +1229,7 @@ func TestTemplateInsights_Golden(t *testing.T) { } } - client, toStableTemplateIDs := prepare(t, templates, users, testData) + client := prepare(t, templates, users, testData) for _, req := range tt.requests { req := req @@ -1245,7 +1241,15 @@ func TestTemplateInsights_Golden(t *testing.T) { report, err := client.TemplateInsights(ctx, req.makeRequest(templates)) require.NoError(t, err, "want no error getting template insights") - stabilizeReportForGoldenComparison(&report, toStableTemplateIDs) + if req.ignoreTimes { + // Ignore times, we're only interested in the data. + report.Report.StartTime = time.Time{} + report.Report.EndTime = time.Time{} + for i := range report.IntervalReports { + report.IntervalReports[i].StartTime = time.Time{} + report.IntervalReports[i].EndTime = time.Time{} + } + } partialName := strings.Join(strings.Split(t.Name(), "/")[1:], "_") goldenFile := filepath.Join("testdata", "insights", partialName+".json.golden") From 1e7d8607b12f0f3e3dce7c41a66a3a4cee29bf24 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 12:31:25 +0000 Subject: [PATCH 16/33] add parameter test --- coderd/insights_test.go | 23 +++ ...rday_and_today_deployment_wide.json.golden | 149 ++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 coderd/testdata/insights/parameters_yesterday_and_today_deployment_wide.json.golden diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 6a2497865f1dd..ca54a41866fd9 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1202,6 +1202,29 @@ func TestTemplateInsights_Golden(t *testing.T) { }, }, }, + { + name: "parameters", + makeTestData: func(templates []*testTemplate, users []*testUser) map[*testWorkspace]testDataGen { + return map[*testWorkspace]testDataGen{} + }, + requests: []testRequest{ + { + // Since workspaces are created "now", we can only get + // parameters using a time range that includes "now". + // We check yesterday and today for stability just in case + // the test runs at UTC midnight. + name: "yesterday and today deployment wide", + ignoreTimes: true, + makeRequest: func(_ []*testTemplate) codersdk.TemplateInsightsRequest { + now := time.Now().UTC() + return codersdk.TemplateInsightsRequest{ + StartTime: now.Truncate(24*time.Hour).AddDate(0, 0, -1), + EndTime: now.Truncate(time.Hour).Add(time.Hour), + } + }, + }, + }, + }, } for _, tt := range tests { diff --git a/coderd/testdata/insights/parameters_yesterday_and_today_deployment_wide.json.golden b/coderd/testdata/insights/parameters_yesterday_and_today_deployment_wide.json.golden new file mode 100644 index 0000000000000..d0ab4fcc84049 --- /dev/null +++ b/coderd/testdata/insights/parameters_yesterday_and_today_deployment_wide.json.golden @@ -0,0 +1,149 @@ +{ + "report": { + "start_time": "0001-01-01T00:00:00Z", + "end_time": "0001-01-01T00:00:00Z", + "template_ids": [], + "active_users": 0, + "apps_usage": [ + { + "template_ids": [], + "type": "builtin", + "display_name": "Visual Studio Code", + "slug": "vscode", + "icon": "/icon/code.svg", + "seconds": 0 + }, + { + "template_ids": [], + "type": "builtin", + "display_name": "JetBrains", + "slug": "jetbrains", + "icon": "/icon/intellij.svg", + "seconds": 0 + }, + { + "template_ids": [], + "type": "builtin", + "display_name": "Web Terminal", + "slug": "reconnecting-pty", + "icon": "/icon/terminal.svg", + "seconds": 0 + }, + { + "template_ids": [], + "type": "builtin", + "display_name": "SSH", + "slug": "ssh", + "icon": "/icon/terminal.svg", + "seconds": 0 + } + ], + "parameters_usage": [ + { + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], + "display_name": "otherparam1", + "name": "otherparam1", + "type": "string", + "description": "This is another parameter", + "values": [ + { + "value": "", + "count": 1 + }, + { + "value": "xyz", + "count": 1 + } + ] + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" + ], + "display_name": "param1", + "name": "param1", + "type": "string", + "description": "This is first parameter", + "values": [ + { + "value": "abc", + "count": 2 + }, + { + "value": "ABC", + "count": 1 + } + ] + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" + ], + "display_name": "param2", + "name": "param2", + "type": "string", + "description": "This is second parameter", + "values": [ + { + "value": "123", + "count": 3 + } + ] + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002" + ], + "display_name": "param3", + "name": "param3", + "type": "string", + "description": "This is third parameter", + "values": [ + { + "value": "bbb", + "count": 1 + }, + { + "value": "BBB", + "count": 2 + } + ] + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], + "display_name": "param4", + "name": "param4", + "type": "string", + "description": "This is fourth parameter", + "options": [ + { + "name": "option1", + "description": "", + "value": "option1", + "icon": "" + }, + { + "name": "option2", + "description": "", + "value": "option2", + "icon": "" + } + ], + "values": [ + { + "value": "option1", + "count": 2 + } + ] + } + ] + }, + "interval_reports": [] +} From 9d96052fffcad233036198e74670757e94b2ba4b Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 15:02:01 +0000 Subject: [PATCH 17/33] refactor --- coderd/insights_test.go | 321 +++++++++++++++++++++------------------- 1 file changed, 165 insertions(+), 156 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index ca54a41866fd9..95f20dca334f9 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -656,141 +656,6 @@ func TestTemplateInsights_Golden(t *testing.T) { appUsage []appUsage } - createFixture := func() ([]*testTemplate, []*testUser) { - // Test templates and configuration to generate. - templates := []*testTemplate{ - // Create two templates with near-identical apps and parameters - // to allow testing for grouping similar data. - { - name: "template1", - parameters: []*templateParameter{ - {name: "param1", description: "This is first parameter"}, - {name: "param2", description: "This is second parameter"}, - {name: "param3", description: "This is third parameter"}, - { - name: "param4", - description: "This is fourth parameter", - options: []templateParameterOption{ - {name: "option1", value: "option1"}, - {name: "option2", value: "option2"}, - }, - }, - }, - apps: []templateApp{ - {name: "app1", icon: "/icon1.png"}, - {name: "app2", icon: "/icon2.png"}, - {name: "app3", icon: "/icon2.png"}, - }, - }, - { - name: "template2", - parameters: []*templateParameter{ - {name: "param1", description: "This is first parameter"}, - {name: "param2", description: "This is second parameter"}, - {name: "param3", description: "This is third parameter"}, - }, - apps: []templateApp{ - {name: "app1", icon: "/icon1.png"}, - {name: "app2", icon: "/icon2.png"}, - {name: "app3", icon: "/icon2.png"}, - }, - }, - // Create another template with different parameters and apps. - { - name: "othertemplate", - parameters: []*templateParameter{ - {name: "otherparam1", description: "This is another parameter"}, - }, - apps: []templateApp{ - {name: "otherapp1", icon: "/icon1.png"}, - }, - }, - } - - // Users and workspaces to generate. - users := []*testUser{ - { - name: "user1", - workspaces: []*testWorkspace{ - { - name: "workspace1", - template: templates[0], - buildParameters: []buildParameter{ - {templateParameter: templates[0].parameters[0], value: "abc"}, - {templateParameter: templates[0].parameters[1], value: "123"}, - {templateParameter: templates[0].parameters[2], value: "bbb"}, - {templateParameter: templates[0].parameters[3], value: "option1"}, - }, - }, - { - name: "workspace2", - template: templates[1], - buildParameters: []buildParameter{ - {templateParameter: templates[0].parameters[0], value: "ABC"}, - {templateParameter: templates[0].parameters[1], value: "123"}, - {templateParameter: templates[0].parameters[2], value: "BBB"}, - {templateParameter: templates[0].parameters[3], value: "option2"}, - }, - }, - { - name: "otherworkspace1", - template: templates[2], - }, - }, - }, - { - name: "user2", - workspaces: []*testWorkspace{ - { - name: "workspace1", - template: templates[0], - buildParameters: []buildParameter{ - {templateParameter: templates[0].parameters[0], value: "abc"}, - {templateParameter: templates[0].parameters[1], value: "123"}, - {templateParameter: templates[0].parameters[2], value: "BBB"}, - {templateParameter: templates[0].parameters[3], value: "option1"}, - }, - }, - }, - }, - { - name: "user3", - workspaces: []*testWorkspace{ - { - name: "otherworkspace1", - template: templates[2], - buildParameters: []buildParameter{ - {templateParameter: templates[2].parameters[0], value: "xyz"}, - }, - }, - }, - }, - } - - // Post-process. - var stableIDs []uuid.UUID - newStableUUID := func() uuid.UUID { - stableIDs = append(stableIDs, uuid.MustParse(fmt.Sprintf("00000000-0000-0000-0000-%012d", len(stableIDs)+1))) - stableID := stableIDs[len(stableIDs)-1] - return stableID - } - - for _, template := range templates { - template.id = newStableUUID() - } - for _, user := range users { - for _, workspace := range user.workspaces { - workspace.user = user - for _, app := range workspace.template.apps { - app := workspaceApp(app) - workspace.apps = append(workspace.apps, &app) - } - } - } - - return templates, users - } - prepare := func(t *testing.T, templates []*testTemplate, users []*testUser, testData map[*testWorkspace]testDataGen) *codersdk.Client { logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: false}).Leveled(slog.LevelDebug) db, pubsub := dbtestutil.NewDB(t) @@ -1026,6 +891,164 @@ func TestTemplateInsights_Golden(t *testing.T) { return client } + prepareFixtureAndTestData := func(t *testing.T, makeFixture func() ([]*testTemplate, []*testUser), makeData func([]*testTemplate, []*testUser) map[*testWorkspace]testDataGen) ([]*testTemplate, []*testUser, map[*testWorkspace]testDataGen) { + var stableIDs []uuid.UUID + newStableUUID := func() uuid.UUID { + stableIDs = append(stableIDs, uuid.MustParse(fmt.Sprintf("00000000-0000-0000-0000-%012d", len(stableIDs)+1))) + stableID := stableIDs[len(stableIDs)-1] + return stableID + } + + templates, users := makeFixture() + for _, template := range templates { + template.id = newStableUUID() + } + for _, user := range users { + for _, workspace := range user.workspaces { + workspace.user = user + for _, app := range workspace.template.apps { + app := workspaceApp(app) + workspace.apps = append(workspace.apps, &app) + } + } + } + + testData := makeData(templates, users) + // Sanity check. + for ws, data := range testData { + for _, usage := range data.appUsage { + found := false + wrongWorkspace := false + for _, app := range ws.apps { + if usage.app == app { // Pointer equality + found = true + break + } + if *usage.app == *app { + wrongWorkspace = true + } + } + require.True(t, found, "test bug: app %q not in workspace %q [wrongWorkspace=%v]", usage.app.name, ws.name, wrongWorkspace) + } + } + + return templates, users, testData + } + + baseTemplateAndUserFixture := func() ([]*testTemplate, []*testUser) { + // Test templates and configuration to generate. + templates := []*testTemplate{ + // Create two templates with near-identical apps and parameters + // to allow testing for grouping similar data. + { + name: "template1", + parameters: []*templateParameter{ + {name: "param1", description: "This is first parameter"}, + {name: "param2", description: "This is second parameter"}, + {name: "param3", description: "This is third parameter"}, + { + name: "param4", + description: "This is fourth parameter", + options: []templateParameterOption{ + {name: "option1", value: "option1"}, + {name: "option2", value: "option2"}, + }, + }, + }, + apps: []templateApp{ + {name: "app1", icon: "/icon1.png"}, + {name: "app2", icon: "/icon2.png"}, + {name: "app3", icon: "/icon2.png"}, + }, + }, + { + name: "template2", + parameters: []*templateParameter{ + {name: "param1", description: "This is first parameter"}, + {name: "param2", description: "This is second parameter"}, + {name: "param3", description: "This is third parameter"}, + }, + apps: []templateApp{ + {name: "app1", icon: "/icon1.png"}, + {name: "app2", icon: "/icon2.png"}, + {name: "app3", icon: "/icon2.png"}, + }, + }, + // Create another template with different parameters and apps. + { + name: "othertemplate", + parameters: []*templateParameter{ + {name: "otherparam1", description: "This is another parameter"}, + }, + apps: []templateApp{ + {name: "otherapp1", icon: "/icon1.png"}, + }, + }, + } + + // Users and workspaces to generate. + users := []*testUser{ + { + name: "user1", + workspaces: []*testWorkspace{ + { + name: "workspace1", + template: templates[0], + buildParameters: []buildParameter{ + {templateParameter: templates[0].parameters[0], value: "abc"}, + {templateParameter: templates[0].parameters[1], value: "123"}, + {templateParameter: templates[0].parameters[2], value: "bbb"}, + {templateParameter: templates[0].parameters[3], value: "option1"}, + }, + }, + { + name: "workspace2", + template: templates[1], + buildParameters: []buildParameter{ + {templateParameter: templates[0].parameters[0], value: "ABC"}, + {templateParameter: templates[0].parameters[1], value: "123"}, + {templateParameter: templates[0].parameters[2], value: "BBB"}, + {templateParameter: templates[0].parameters[3], value: "option2"}, + }, + }, + { + name: "otherworkspace1", + template: templates[2], + }, + }, + }, + { + name: "user2", + workspaces: []*testWorkspace{ + { + name: "workspace1", + template: templates[0], + buildParameters: []buildParameter{ + {templateParameter: templates[0].parameters[0], value: "abc"}, + {templateParameter: templates[0].parameters[1], value: "123"}, + {templateParameter: templates[0].parameters[2], value: "BBB"}, + {templateParameter: templates[0].parameters[3], value: "option1"}, + }, + }, + }, + }, + { + name: "user3", + workspaces: []*testWorkspace{ + { + name: "otherworkspace1", + template: templates[2], + buildParameters: []buildParameter{ + {templateParameter: templates[2].parameters[0], value: "xyz"}, + }, + }, + }, + }, + } + + return templates, users + } + // Time range for report, test data will be generated within and // outside this range, but only data within the range should be // included in the report. @@ -1127,11 +1150,13 @@ func TestTemplateInsights_Golden(t *testing.T) { } tests := []struct { name string + makeFixture func() ([]*testTemplate, []*testUser) makeTestData func([]*testTemplate, []*testUser) map[*testWorkspace]testDataGen requests []testRequest }{ { name: "multiple users and workspaces", + makeFixture: baseTemplateAndUserFixture, makeTestData: makeBaseTestData, requests: []testRequest{ { @@ -1203,7 +1228,8 @@ func TestTemplateInsights_Golden(t *testing.T) { }, }, { - name: "parameters", + name: "parameters", + makeFixture: baseTemplateAndUserFixture, makeTestData: func(templates []*testTemplate, users []*testUser) map[*testWorkspace]testDataGen { return map[*testWorkspace]testDataGen{} }, @@ -1232,26 +1258,9 @@ func TestTemplateInsights_Golden(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - templates, users := createFixture() - testData := tt.makeTestData(templates, users) - // Sanity check. - for ws, data := range testData { - for _, usage := range data.appUsage { - found := false - wrongWorkspace := false - for _, app := range ws.apps { - if usage.app == app { // Pointer equality - found = true - break - } - if *usage.app == *app { - wrongWorkspace = true - } - } - require.True(t, found, "test bug: app %q not in workspace %q [wrongWorkspace=%v]", usage.app.name, ws.name, wrongWorkspace) - } - } - + require.NotNil(t, tt.makeFixture, "test bug: makeFixture must be set") + require.NotNil(t, tt.makeTestData, "test bug: makeTestData must be set") + templates, users, testData := prepareFixtureAndTestData(t, tt.makeFixture, tt.makeTestData) client := prepare(t, templates, users, testData) for _, req := range tt.requests { From 930d7ef132ca471358fa7249dd2c5a4023662f67 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 15:42:27 +0000 Subject: [PATCH 18/33] add parameter no data test --- coderd/insights_test.go | 11 +++++ ...rameters_two_days_ago,_no_data.json.golden | 44 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 coderd/testdata/insights/parameters_two_days_ago,_no_data.json.golden diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 95f20dca334f9..5e0fc0a81631b 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1249,6 +1249,17 @@ func TestTemplateInsights_Golden(t *testing.T) { } }, }, + { + name: "two days ago, no data", + ignoreTimes: true, + makeRequest: func(_ []*testTemplate) codersdk.TemplateInsightsRequest { + twoDaysAgo := time.Now().UTC().Truncate(24*time.Hour).AddDate(0, 0, -2) + return codersdk.TemplateInsightsRequest{ + StartTime: twoDaysAgo, + EndTime: twoDaysAgo.AddDate(0, 0, 1), + } + }, + }, }, }, } diff --git a/coderd/testdata/insights/parameters_two_days_ago,_no_data.json.golden b/coderd/testdata/insights/parameters_two_days_ago,_no_data.json.golden new file mode 100644 index 0000000000000..e3875b2a34b38 --- /dev/null +++ b/coderd/testdata/insights/parameters_two_days_ago,_no_data.json.golden @@ -0,0 +1,44 @@ +{ + "report": { + "start_time": "0001-01-01T00:00:00Z", + "end_time": "0001-01-01T00:00:00Z", + "template_ids": [], + "active_users": 0, + "apps_usage": [ + { + "template_ids": [], + "type": "builtin", + "display_name": "Visual Studio Code", + "slug": "vscode", + "icon": "/icon/code.svg", + "seconds": 0 + }, + { + "template_ids": [], + "type": "builtin", + "display_name": "JetBrains", + "slug": "jetbrains", + "icon": "/icon/intellij.svg", + "seconds": 0 + }, + { + "template_ids": [], + "type": "builtin", + "display_name": "Web Terminal", + "slug": "reconnecting-pty", + "icon": "/icon/terminal.svg", + "seconds": 0 + }, + { + "template_ids": [], + "type": "builtin", + "display_name": "SSH", + "slug": "ssh", + "icon": "/icon/terminal.svg", + "seconds": 0 + } + ], + "parameters_usage": [] + }, + "interval_reports": [] +} From 07cdc0b145f65535f96228e469ae37bd2aa75f4e Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 15:48:34 +0000 Subject: [PATCH 19/33] add 1h usage to other user --- coderd/insights_test.go | 10 ++++++++-- ...users_and_workspaces_week_all_templates.json.golden | 6 +++--- ...ers_and_workspaces_week_deployment_wide.json.golden | 6 +++--- ...sers_and_workspaces_week_first_template.json.golden | 6 +++--- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 5e0fc0a81631b..c2097c1dc78e9 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1134,8 +1134,14 @@ func TestTemplateInsights_Golden(t *testing.T) { appUsage: []appUsage{}, }, users[1].workspaces[0]: { - agentStats: []agentStat{}, - appUsage: []appUsage{}, + agentStats: []agentStat{ + { // One hour of usage. + startedAt: frozenWeekAgo, + endedAt: frozenWeekAgo.Add(time.Hour), + sessionCountSSH: 1, + }, + }, + appUsage: []appUsage{}, }, users[2].workspaces[0]: { agentStats: []agentStat{}, diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden index 760195158c558..d6aa666ba2076 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden @@ -6,7 +6,7 @@ "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002" ], - "active_users": 1, + "active_users": 2, "apps_usage": [ { "template_ids": [ @@ -50,7 +50,7 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 4500 + "seconds": 8100 }, { "template_ids": [ @@ -85,7 +85,7 @@ "00000000-0000-0000-0000-000000000002" ], "interval": "day", - "active_users": 1 + "active_users": 2 }, { "start_time": "2023-08-16T00:00:00Z", diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden index 760195158c558..d6aa666ba2076 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden @@ -6,7 +6,7 @@ "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002" ], - "active_users": 1, + "active_users": 2, "apps_usage": [ { "template_ids": [ @@ -50,7 +50,7 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 4500 + "seconds": 8100 }, { "template_ids": [ @@ -85,7 +85,7 @@ "00000000-0000-0000-0000-000000000002" ], "interval": "day", - "active_users": 1 + "active_users": 2 }, { "start_time": "2023-08-16T00:00:00Z", diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden index 405f1a3dd1750..c9bc1953b0702 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_first_template.json.golden @@ -5,7 +5,7 @@ "template_ids": [ "00000000-0000-0000-0000-000000000001" ], - "active_users": 1, + "active_users": 2, "apps_usage": [ { "template_ids": [ @@ -45,7 +45,7 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 4500 + "seconds": 8100 }, { "template_ids": [ @@ -78,7 +78,7 @@ "00000000-0000-0000-0000-000000000001" ], "interval": "day", - "active_users": 1 + "active_users": 2 }, { "start_time": "2023-08-16T00:00:00Z", From 73367d1767774f9dc3e708d205dde49754f22d90 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 15:51:18 +0000 Subject: [PATCH 20/33] add usage outside of utc timeframe --- coderd/insights_test.go | 12 ++++++++++++ ...ek_other_timezone_(S\303\243o_Paulo).json.golden" | 12 +++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index c2097c1dc78e9..d7755c2cd2cc3 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1135,11 +1135,23 @@ func TestTemplateInsights_Golden(t *testing.T) { }, users[1].workspaces[0]: { agentStats: []agentStat{ + { // One hour of usage before timeframe (exclude). + startedAt: frozenWeekAgo.Add(-time.Hour), + endedAt: frozenWeekAgo, + sessionCountVSCode: 1, + sessionCountSSH: 1, + }, { // One hour of usage. startedAt: frozenWeekAgo, endedAt: frozenWeekAgo.Add(time.Hour), sessionCountSSH: 1, }, + { // One hour of usage after timeframe (exclude in UTC, include in São Paulo). + startedAt: frozenWeekAgo.AddDate(0, 0, 7), + endedAt: frozenWeekAgo.AddDate(0, 0, 7).Add(time.Hour), + sessionCountVSCode: 1, + sessionCountSSH: 1, + }, }, appUsage: []appUsage{}, }, diff --git "a/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" "b/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" index e6c72500b6b92..82da0b932f442 100644 --- "a/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" +++ "b/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" @@ -6,7 +6,7 @@ "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002" ], - "active_users": 1, + "active_users": 2, "apps_usage": [ { "template_ids": [ @@ -16,7 +16,7 @@ "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 0 + "seconds": 3600 }, { "template_ids": [ @@ -46,7 +46,7 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 900 + "seconds": 4500 }, { "template_ids": [ @@ -126,9 +126,11 @@ { "start_time": "2023-08-21T00:00:00-03:00", "end_time": "2023-08-22T00:00:00-03:00", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000001" + ], "interval": "day", - "active_users": 0 + "active_users": 1 } ] } From c2a0496758eb323871a4d740bd46630c5bac287f Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 15:55:05 +0000 Subject: [PATCH 21/33] add third user with ssh and web term usage --- coderd/insights_test.go | 11 +++++-- ..._workspaces_week_all_templates.json.golden | 26 +++++++++------ ...orkspaces_week_deployment_wide.json.golden | 26 +++++++++------ ...workspaces_week_third_template.json.golden | 32 +++++++++++++------ 4 files changed, 63 insertions(+), 32 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index d7755c2cd2cc3..8989da0da2c2f 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1156,8 +1156,15 @@ func TestTemplateInsights_Golden(t *testing.T) { appUsage: []appUsage{}, }, users[2].workspaces[0]: { - agentStats: []agentStat{}, - appUsage: []appUsage{}, + agentStats: []agentStat{ + { // One hour of usage. + startedAt: frozenWeekAgo, + endedAt: frozenWeekAgo.Add(time.Hour), + sessionCountSSH: 1, + sessionCountReconnectingPTY: 1, + }, + }, + appUsage: []appUsage{}, }, } } diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden index d6aa666ba2076..501c94a34ddef 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden @@ -4,14 +4,16 @@ "end_time": "2023-08-22T00:00:00Z", "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], - "active_users": 2, + "active_users": 3, "apps_usage": [ { "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], "type": "builtin", "display_name": "Visual Studio Code", @@ -22,7 +24,8 @@ { "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], "type": "builtin", "display_name": "JetBrains", @@ -33,24 +36,26 @@ { "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], "type": "builtin", "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 3600 }, { "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], "type": "builtin", "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 8100 + "seconds": 11700 }, { "template_ids": [ @@ -82,10 +87,11 @@ "end_time": "2023-08-16T00:00:00Z", "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], "interval": "day", - "active_users": 2 + "active_users": 3 }, { "start_time": "2023-08-16T00:00:00Z", diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden index d6aa666ba2076..501c94a34ddef 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden @@ -4,14 +4,16 @@ "end_time": "2023-08-22T00:00:00Z", "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], - "active_users": 2, + "active_users": 3, "apps_usage": [ { "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], "type": "builtin", "display_name": "Visual Studio Code", @@ -22,7 +24,8 @@ { "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], "type": "builtin", "display_name": "JetBrains", @@ -33,24 +36,26 @@ { "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], "type": "builtin", "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 3600 }, { "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], "type": "builtin", "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 8100 + "seconds": 11700 }, { "template_ids": [ @@ -82,10 +87,11 @@ "end_time": "2023-08-16T00:00:00Z", "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], "interval": "day", - "active_users": 2 + "active_users": 3 }, { "start_time": "2023-08-16T00:00:00Z", diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden index 2926f200d8ae5..b625470105bfa 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden @@ -2,11 +2,15 @@ "report": { "start_time": "2023-08-15T00:00:00Z", "end_time": "2023-08-22T00:00:00Z", - "template_ids": [], - "active_users": 0, + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], + "active_users": 1, "apps_usage": [ { - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], "type": "builtin", "display_name": "Visual Studio Code", "slug": "vscode", @@ -14,7 +18,9 @@ "seconds": 0 }, { - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], "type": "builtin", "display_name": "JetBrains", "slug": "jetbrains", @@ -22,20 +28,24 @@ "seconds": 0 }, { - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], "type": "builtin", "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 3600 }, { - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], "type": "builtin", "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 3600 } ], "parameters_usage": [] @@ -44,9 +54,11 @@ { "start_time": "2023-08-15T00:00:00Z", "end_time": "2023-08-16T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], "interval": "day", - "active_users": 0 + "active_users": 1 }, { "start_time": "2023-08-16T00:00:00Z", From b73879919836e96651e822bd7c6818f0492566da Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 16:18:32 +0000 Subject: [PATCH 22/33] add third user app usage --- coderd/insights_test.go | 25 +++++++++++++++---- ..._workspaces_week_all_templates.json.golden | 15 +++++++++-- ...orkspaces_week_deployment_wide.json.golden | 15 +++++++++-- ...r_timezone_(S\303\243o_Paulo).json.golden" | 20 ++++++++++++--- ...workspaces_week_third_template.json.golden | 16 ++++++++++-- 5 files changed, 76 insertions(+), 15 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 8989da0da2c2f..e84a4805b2321 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -918,17 +918,25 @@ func TestTemplateInsights_Golden(t *testing.T) { for ws, data := range testData { for _, usage := range data.appUsage { found := false - wrongWorkspace := false for _, app := range ws.apps { if usage.app == app { // Pointer equality found = true break } - if *usage.app == *app { - wrongWorkspace = true + } + if !found { + for _, user := range users { + for _, workspace := range user.workspaces { + for _, app := range workspace.apps { + if usage.app == app { // Pointer equality + require.True(t, found, "test bug: app %q not in workspace %q: want user=%s workspace=%s; got user=%s workspace=%s ", usage.app.name, ws.name, ws.user.(*testUser).name, ws.name, user.name, workspace.name) + break + } + } + } } + require.True(t, found, "test bug: app %q not in workspace %q", usage.app.name, ws.name) } - require.True(t, found, "test bug: app %q not in workspace %q [wrongWorkspace=%v]", usage.app.name, ws.name, wrongWorkspace) } } @@ -1164,7 +1172,14 @@ func TestTemplateInsights_Golden(t *testing.T) { sessionCountReconnectingPTY: 1, }, }, - appUsage: []appUsage{}, + appUsage: []appUsage{ + { + app: users[2].workspaces[0].apps[0], + startedAt: frozenWeekAgo.AddDate(0, 0, 2), + endedAt: frozenWeekAgo.AddDate(0, 0, 2).Add(5 * time.Minute), + requests: 1, + }, + }, }, } } diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden index 501c94a34ddef..9b9eaccd12ebe 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden @@ -77,6 +77,16 @@ "slug": "app3", "icon": "/icon2.png", "seconds": 900 + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], + "type": "app", + "display_name": "otherapp1", + "slug": "otherapp1", + "icon": "/icon1.png", + "seconds": 300 } ], "parameters_usage": [] @@ -107,10 +117,11 @@ "end_time": "2023-08-18T00:00:00Z", "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], "interval": "day", - "active_users": 1 + "active_users": 2 }, { "start_time": "2023-08-18T00:00:00Z", diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden index 501c94a34ddef..9b9eaccd12ebe 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden @@ -77,6 +77,16 @@ "slug": "app3", "icon": "/icon2.png", "seconds": 900 + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], + "type": "app", + "display_name": "otherapp1", + "slug": "otherapp1", + "icon": "/icon1.png", + "seconds": 300 } ], "parameters_usage": [] @@ -107,10 +117,11 @@ "end_time": "2023-08-18T00:00:00Z", "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], "interval": "day", - "active_users": 1 + "active_users": 2 }, { "start_time": "2023-08-18T00:00:00Z", diff --git "a/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" "b/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" index 82da0b932f442..670a5f63f8617 100644 --- "a/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" +++ "b/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" @@ -4,9 +4,10 @@ "end_time": "2023-08-22T00:00:00-03:00", "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], - "active_users": 2, + "active_users": 3, "apps_usage": [ { "template_ids": [ @@ -67,6 +68,16 @@ "slug": "app3", "icon": "/icon2.png", "seconds": 900 + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], + "type": "app", + "display_name": "otherapp1", + "slug": "otherapp1", + "icon": "/icon1.png", + "seconds": 300 } ], "parameters_usage": [] @@ -86,10 +97,11 @@ "end_time": "2023-08-17T00:00:00-03:00", "template_ids": [ "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], "interval": "day", - "active_users": 1 + "active_users": 2 }, { "start_time": "2023-08-17T00:00:00-03:00", diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden index b625470105bfa..43046d4dcd74c 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden @@ -46,6 +46,16 @@ "slug": "ssh", "icon": "/icon/terminal.svg", "seconds": 3600 + }, + { + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], + "type": "app", + "display_name": "otherapp1", + "slug": "otherapp1", + "icon": "/icon1.png", + "seconds": 300 } ], "parameters_usage": [] @@ -70,9 +80,11 @@ { "start_time": "2023-08-17T00:00:00Z", "end_time": "2023-08-18T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], "interval": "day", - "active_users": 0 + "active_users": 1 }, { "start_time": "2023-08-18T00:00:00Z", From 79bdbf92716e4fafbd2ee7b83760124a46865c02 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 16:20:21 +0000 Subject: [PATCH 23/33] add webterminal app usage --- coderd/insights_test.go | 11 +++++++++++ ...sers_and_workspaces_week_all_templates.json.golden | 6 ++++-- ...rs_and_workspaces_week_deployment_wide.json.golden | 6 ++++-- ...eek_other_timezone_(S\303\243o_Paulo).json.golden" | 5 +++-- ...ers_and_workspaces_week_third_template.json.golden | 6 ++++-- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index e84a4805b2321..f3a517d31ef98 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -990,6 +990,11 @@ func TestTemplateInsights_Golden(t *testing.T) { }, apps: []templateApp{ {name: "otherapp1", icon: "/icon1.png"}, + + // This "special test app" will be converted into web + // terminal usage, this is not included in stats since we + // currently rely on agent stats for this data. + {name: "terminal", icon: "/terminal.png"}, }, }, } @@ -1179,6 +1184,12 @@ func TestTemplateInsights_Golden(t *testing.T) { endedAt: frozenWeekAgo.AddDate(0, 0, 2).Add(5 * time.Minute), requests: 1, }, + { // Excluded from apps, but counted as active during the day. + app: users[2].workspaces[0].apps[1], + startedAt: frozenWeekAgo.AddDate(0, 0, 3), + endedAt: frozenWeekAgo.AddDate(0, 0, 3).Add(5 * time.Minute), + requests: 1, + }, }, }, } diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden index 9b9eaccd12ebe..88cd9066039f9 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_all_templates.json.golden @@ -126,9 +126,11 @@ { "start_time": "2023-08-18T00:00:00Z", "end_time": "2023-08-19T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], "interval": "day", - "active_users": 0 + "active_users": 1 }, { "start_time": "2023-08-19T00:00:00Z", diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden index 9b9eaccd12ebe..88cd9066039f9 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_deployment_wide.json.golden @@ -126,9 +126,11 @@ { "start_time": "2023-08-18T00:00:00Z", "end_time": "2023-08-19T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], "interval": "day", - "active_users": 0 + "active_users": 1 }, { "start_time": "2023-08-19T00:00:00Z", diff --git "a/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" "b/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" index 670a5f63f8617..0cb67a0eda5f5 100644 --- "a/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" +++ "b/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" @@ -107,10 +107,11 @@ "start_time": "2023-08-17T00:00:00-03:00", "end_time": "2023-08-18T00:00:00-03:00", "template_ids": [ - "00000000-0000-0000-0000-000000000002" + "00000000-0000-0000-0000-000000000002", + "00000000-0000-0000-0000-000000000003" ], "interval": "day", - "active_users": 1 + "active_users": 2 }, { "start_time": "2023-08-18T00:00:00-03:00", diff --git a/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden b/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden index 43046d4dcd74c..ea4002e09f152 100644 --- a/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden +++ b/coderd/testdata/insights/multiple_users_and_workspaces_week_third_template.json.golden @@ -89,9 +89,11 @@ { "start_time": "2023-08-18T00:00:00Z", "end_time": "2023-08-19T00:00:00Z", - "template_ids": [], + "template_ids": [ + "00000000-0000-0000-0000-000000000003" + ], "interval": "day", - "active_users": 0 + "active_users": 1 }, { "start_time": "2023-08-19T00:00:00Z", From ce2bb7862df490bd8169e9ed6e56294364cc3b53 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 17:26:45 +0000 Subject: [PATCH 24/33] use stable sorting in Go and minor query tweaks and fakedb fixes --- coderd/database/db2sdk/db2sdk.go | 48 ++++++++++++++----- coderd/database/dbfake/dbfake.go | 17 +++++-- coderd/database/queries.sql.go | 9 ++-- coderd/database/queries/insights.sql | 11 ++--- coderd/insights.go | 27 +++++++++++ ...rday_and_today_deployment_wide.json.golden | 16 +++---- 6 files changed, 92 insertions(+), 36 deletions(-) diff --git a/coderd/database/db2sdk/db2sdk.go b/coderd/database/db2sdk/db2sdk.go index 101965e2124fe..d6a5bf4b69ef0 100644 --- a/coderd/database/db2sdk/db2sdk.go +++ b/coderd/database/db2sdk/db2sdk.go @@ -3,9 +3,10 @@ package db2sdk import ( "encoding/json" - "sort" + "strings" "github.com/google/uuid" + "golang.org/x/exp/slices" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/parameter" @@ -125,9 +126,34 @@ func Role(role rbac.Role) codersdk.Role { } func TemplateInsightsParameters(parameterRows []database.GetTemplateParameterInsightsRow) ([]codersdk.TemplateParameterUsage, error) { - parametersByNum := make(map[int64]*codersdk.TemplateParameterUsage) + // Use a stable sort, similarly to how we would sort in the query, note that + // we don't sort in the query because order varies depending on the table + // collation. + // + // ORDER BY utp.name, utp.type, utp.display_name, utp.description, utp.options, wbp.value + slices.SortFunc(parameterRows, func(a, b database.GetTemplateParameterInsightsRow) int { + if a.Name != b.Name { + return strings.Compare(a.Name, b.Name) + } + if a.Type != b.Type { + return strings.Compare(a.Type, b.Type) + } + if a.DisplayName != b.DisplayName { + return strings.Compare(a.DisplayName, b.DisplayName) + } + if a.Description != b.Description { + return strings.Compare(a.Description, b.Description) + } + if string(a.Options) != string(b.Options) { + return strings.Compare(string(a.Options), string(b.Options)) + } + return strings.Compare(a.Value, b.Value) + }) + + parametersUsage := []codersdk.TemplateParameterUsage{} + indexByNum := make(map[int64]int) for _, param := range parameterRows { - if _, ok := parametersByNum[param.Num]; !ok { + if _, ok := indexByNum[param.Num]; !ok { var opts []codersdk.TemplateVersionParameterOption err := json.Unmarshal(param.Options, &opts) if err != nil { @@ -139,28 +165,24 @@ func TemplateInsightsParameters(parameterRows []database.GetTemplateParameterIns return nil, err } - parametersByNum[param.Num] = &codersdk.TemplateParameterUsage{ + parametersUsage = append(parametersUsage, codersdk.TemplateParameterUsage{ TemplateIDs: param.TemplateIDs, Name: param.Name, Type: param.Type, DisplayName: param.DisplayName, Description: plaintextDescription, Options: opts, - } + }) + indexByNum[param.Num] = len(parametersUsage) - 1 } - parametersByNum[param.Num].Values = append(parametersByNum[param.Num].Values, codersdk.TemplateParameterValue{ + + i := indexByNum[param.Num] + parametersUsage[i].Values = append(parametersUsage[i].Values, codersdk.TemplateParameterValue{ Value: param.Value, Count: param.Count, }) } - parametersUsage := []codersdk.TemplateParameterUsage{} - for _, param := range parametersByNum { - parametersUsage = append(parametersUsage, *param) - } - sort.Slice(parametersUsage, func(i, j int) bool { - return parametersUsage[i].Name < parametersUsage[j].Name - }) return parametersUsage, nil } diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 8415955e78f86..e55d655d4c126 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -2095,6 +2095,8 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G }) } + // NOTE(mafredri): Add sorting if we decide on how to handle PostgreSQL collations. + // ORDER BY access_method, slug_or_port, display_name, icon, is_app return rows, nil } @@ -2282,9 +2284,9 @@ func (q *FakeQuerier) GetTemplateDailyInsights(ctx context.Context, arg database // (was.session_started_at >= ts.from_ AND was.session_started_at < ts.to_) // OR (was.session_ended_at > ts.from_ AND was.session_ended_at < ts.to_) // OR (was.session_started_at < ts.from_ AND was.session_ended_at >= ts.to_) - if !(((s.SessionStartedAt.After(arg.StartTime) || s.SessionStartedAt.Equal(arg.StartTime)) && s.SessionStartedAt.Before(arg.EndTime)) || - (s.SessionEndedAt.After(arg.StartTime) && s.SessionEndedAt.Before(arg.EndTime)) || - (s.SessionStartedAt.Before(arg.StartTime) && (s.SessionEndedAt.After(arg.EndTime) || s.SessionEndedAt.Equal(arg.EndTime)))) { + if !(((s.SessionStartedAt.After(ds.startTime) || s.SessionStartedAt.Equal(ds.startTime)) && s.SessionStartedAt.Before(ds.endTime)) || + (s.SessionEndedAt.After(ds.startTime) && s.SessionEndedAt.Before(ds.endTime)) || + (s.SessionStartedAt.Before(ds.startTime) && (s.SessionEndedAt.After(ds.endTime) || s.SessionEndedAt.Equal(ds.endTime)))) { continue } @@ -2293,6 +2295,10 @@ func (q *FakeQuerier) GetTemplateDailyInsights(ctx context.Context, arg database return nil, err } + if len(arg.TemplateIDs) > 0 && !slices.Contains(arg.TemplateIDs, w.TemplateID) { + continue + } + ds.userSet[s.UserID] = struct{}{} ds.templateIDSet[w.TemplateID] = struct{}{} break @@ -2430,7 +2436,8 @@ func (q *FakeQuerier) GetTemplateParameterInsights(ctx context.Context, arg data if tvp.TemplateVersionID != tv.ID { continue } - key := fmt.Sprintf("%s:%s:%s:%s", tvp.Name, tvp.DisplayName, tvp.Description, tvp.Options) + // GROUP BY tvp.name, tvp.type, tvp.display_name, tvp.description, tvp.options + key := fmt.Sprintf("%s:%s:%s:%s:%s", tvp.Name, tvp.Type, tvp.DisplayName, tvp.Description, tvp.Options) if _, ok := uniqueTemplateParams[key]; !ok { num++ uniqueTemplateParams[key] = &database.GetTemplateParameterInsightsRow{ @@ -2480,6 +2487,8 @@ func (q *FakeQuerier) GetTemplateParameterInsights(ctx context.Context, arg data } } + // NOTE(mafredri): Add sorting if we decide on how to handle PostgreSQL collations. + // ORDER BY utp.name, utp.type, utp.display_name, utp.description, utp.options, wbp.value return rows, nil } diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 60d1a8d9f1ac7..87f8e309ac453 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1520,7 +1520,6 @@ SELECT SUM(seconds) AS usage_seconds FROM app_stats_by_user_and_agent GROUP BY access_method, slug_or_port, display_name, icon, is_app -ORDER BY access_method, slug_or_port, display_name, icon, is_app ` type GetTemplateAppInsightsParams struct { @@ -1789,13 +1788,13 @@ WITH latest_workspace_builds AS ( array_agg(DISTINCT wb.template_id)::uuid[] AS template_ids, array_agg(wb.id)::uuid[] AS workspace_build_ids, tvp.name, + tvp.type, tvp.display_name, tvp.description, - tvp.options, - tvp.type + tvp.options FROM latest_workspace_builds wb JOIN template_version_parameters tvp ON (tvp.template_version_id = wb.template_version_id) - GROUP BY tvp.name, tvp.display_name, tvp.description, tvp.options, tvp.type + GROUP BY tvp.name, tvp.type, tvp.display_name, tvp.description, tvp.options ) SELECT @@ -1810,7 +1809,7 @@ SELECT COUNT(wbp.value) AS count FROM unique_template_params utp JOIN workspace_build_parameters wbp ON (utp.workspace_build_ids @> ARRAY[wbp.workspace_build_id] AND utp.name = wbp.name) -GROUP BY utp.num, utp.name, utp.display_name, utp.description, utp.options, utp.template_ids, utp.type, wbp.value +GROUP BY utp.num, utp.template_ids, utp.name, utp.type, utp.display_name, utp.description, utp.options, wbp.value ` type GetTemplateParameterInsightsParams struct { diff --git a/coderd/database/queries/insights.sql b/coderd/database/queries/insights.sql index 20190a4717c49..2dd17c9f07123 100644 --- a/coderd/database/queries/insights.sql +++ b/coderd/database/queries/insights.sql @@ -133,8 +133,7 @@ SELECT is_app, SUM(seconds) AS usage_seconds FROM app_stats_by_user_and_agent -GROUP BY access_method, slug_or_port, display_name, icon, is_app -ORDER BY access_method, slug_or_port, display_name, icon, is_app; +GROUP BY access_method, slug_or_port, display_name, icon, is_app; -- name: GetTemplateDailyInsights :many -- GetTemplateDailyInsights returns all daily intervals between start and end @@ -231,13 +230,13 @@ WITH latest_workspace_builds AS ( array_agg(DISTINCT wb.template_id)::uuid[] AS template_ids, array_agg(wb.id)::uuid[] AS workspace_build_ids, tvp.name, + tvp.type, tvp.display_name, tvp.description, - tvp.options, - tvp.type + tvp.options FROM latest_workspace_builds wb JOIN template_version_parameters tvp ON (tvp.template_version_id = wb.template_version_id) - GROUP BY tvp.name, tvp.display_name, tvp.description, tvp.options, tvp.type + GROUP BY tvp.name, tvp.type, tvp.display_name, tvp.description, tvp.options ) SELECT @@ -252,4 +251,4 @@ SELECT COUNT(wbp.value) AS count FROM unique_template_params utp JOIN workspace_build_parameters wbp ON (utp.workspace_build_ids @> ARRAY[wbp.workspace_build_id] AND utp.name = wbp.name) -GROUP BY utp.num, utp.name, utp.display_name, utp.description, utp.options, utp.template_ids, utp.type, wbp.value; +GROUP BY utp.num, utp.template_ids, utp.name, utp.type, utp.display_name, utp.description, utp.options, wbp.value; diff --git a/coderd/insights.go b/coderd/insights.go index dc8be8291af13..e19f95d40dc0c 100644 --- a/coderd/insights.go +++ b/coderd/insights.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "strings" "time" "github.com/google/uuid" @@ -379,6 +380,32 @@ func convertTemplateInsightsApps(usage database.GetTemplateInsightsRow, appUsage }, } + // Use a stable sort, similarly to how we would sort in the query, note that + // we don't sort in the query because order varies depending on the table + // collation. + // + // ORDER BY access_method, slug_or_port, display_name, icon, is_app + slices.SortFunc(appUsage, func(a, b database.GetTemplateAppInsightsRow) int { + if a.AccessMethod != b.AccessMethod { + return strings.Compare(a.AccessMethod, b.AccessMethod) + } + if a.SlugOrPort != b.SlugOrPort { + return strings.Compare(a.SlugOrPort, b.SlugOrPort) + } + if a.DisplayName.String != b.DisplayName.String { + return strings.Compare(a.DisplayName.String, b.DisplayName.String) + } + if a.Icon.String != b.Icon.String { + return strings.Compare(a.Icon.String, b.Icon.String) + } + if !a.IsApp && b.IsApp { + return -1 + } else if a.IsApp && !b.IsApp { + return 1 + } + return 0 + }) + // Template apps. for _, app := range appUsage { if !app.IsApp { diff --git a/coderd/testdata/insights/parameters_yesterday_and_today_deployment_wide.json.golden b/coderd/testdata/insights/parameters_yesterday_and_today_deployment_wide.json.golden index d0ab4fcc84049..ec051784889b7 100644 --- a/coderd/testdata/insights/parameters_yesterday_and_today_deployment_wide.json.golden +++ b/coderd/testdata/insights/parameters_yesterday_and_today_deployment_wide.json.golden @@ -68,13 +68,13 @@ "type": "string", "description": "This is first parameter", "values": [ - { - "value": "abc", - "count": 2 - }, { "value": "ABC", "count": 1 + }, + { + "value": "abc", + "count": 2 } ] }, @@ -104,13 +104,13 @@ "type": "string", "description": "This is third parameter", "values": [ - { - "value": "bbb", - "count": 1 - }, { "value": "BBB", "count": 2 + }, + { + "value": "bbb", + "count": 1 } ] }, From 0896439c596a4e379e60be2adf1fc59d51ac067c Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 20:00:16 +0000 Subject: [PATCH 25/33] use github.com/google/go-cmp/cmp for diffs --- coderd/insights_test.go | 13 ++++++++++++- go.mod | 5 ++--- go.sum | 24 ------------------------ 3 files changed, 14 insertions(+), 28 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index f3a517d31ef98..42037e92fac68 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -1366,7 +1367,17 @@ func TestTemplateInsights_Golden(t *testing.T) { err = json.NewDecoder(f).Decode(&want) require.NoError(t, err, "want no error decoding golden file") - assert.Equal(t, want, report, "golden file mismatch: %s, run \"make update-golden-files\", verify and commit the changes", goldenFile) + cmpOpts := []cmp.Option{ + // Ensure readable UUIDs in diff. + cmp.Transformer("UUIDs", func(in []uuid.UUID) (s []string) { + for _, id := range in { + s = append(s, id.String()) + } + return s + }), + } + // Use cmp.Diff here because it produces more readable diffs. + assert.Empty(t, cmp.Diff(want, report, cmpOpts...), "golden file mismatch (-want +got): %s, run \"make update-golden-files\", verify and commit the changes", goldenFile) }) } }) diff --git a/go.mod b/go.mod index 74a6df56fe430..611a1713ae1d6 100644 --- a/go.mod +++ b/go.mod @@ -77,7 +77,6 @@ require ( github.com/andybalholm/brotli v1.0.5 github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 github.com/awalterschulze/gographviz v2.0.3+incompatible - github.com/bep/debounce v1.2.1 github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 github.com/bramvdbogaerde/go-scp v1.2.1-0.20221219230748-977ee74ac37b github.com/briandowns/spinner v1.18.1 @@ -121,6 +120,7 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-migrate/migrate/v4 v4.16.0 github.com/golang/mock v1.6.0 + github.com/google/go-cmp v0.5.9 github.com/google/go-github/v43 v43.0.1-0.20220414155304-00e42332e405 github.com/google/uuid v1.3.0 github.com/hashicorp/go-multierror v1.1.1 @@ -185,6 +185,7 @@ require ( golang.org/x/sys v0.11.0 golang.org/x/term v0.11.0 golang.org/x/text v0.12.0 + golang.org/x/time v0.3.0 golang.org/x/tools v0.12.0 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b @@ -277,7 +278,6 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/flatbuffers v23.1.21+incompatible // indirect - github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c // indirect github.com/google/s2a-go v0.1.5 // indirect @@ -383,7 +383,6 @@ require ( go.opentelemetry.io/otel/metric v1.16.0 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect - golang.org/x/time v0.3.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect diff --git a/go.sum b/go.sum index b7c43db687add..ccdb57b437e56 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,6 @@ github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8 github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756 h1:L6S7kR7SlhQKplIBpkra3s6yhcZV51lhRnXmYc4HohI= github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= @@ -144,8 +142,6 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= -github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/bep/godartsass v1.2.0 h1:E2VvQrxAHAFwbjyOIExAMmogTItSKodoKuijNrGm5yU= github.com/bep/godartsass v1.2.0/go.mod h1:6LvK9RftsXMxGfsA0LDV12AGc4Jylnu6NgHL+Q5/pE8= github.com/bep/godartsass/v2 v2.0.0 h1:Ruht+BpBWkpmW+yAM2dkp7RSSeN0VLaTobyW0CiSP3Y= @@ -283,7 +279,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= @@ -326,7 +321,6 @@ github.com/go-chi/httprate v0.7.1 h1:d5kXARdms2PREQfU4pHvq44S6hJ1hPu4OXLeBKmCKWs github.com/go-chi/httprate v0.7.1/go.mod h1:6GOYBSwnpra4CQfAKXu8sQZg+nZ0M1g9QnyFvxrAB8A= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -334,7 +328,6 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -371,7 +364,6 @@ github.com/go-playground/validator/v10 v10.15.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QX github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= @@ -511,7 +503,6 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= -github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= @@ -558,7 +549,6 @@ github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3s github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 h1:AgcIVYPa6XJnU3phs104wLj8l5GEththEw6+F79YsIY= github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= -github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -568,7 +558,6 @@ github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 h1:+aAGyK41KRn8jbF2Q7PLL0Sxwg6dShGcQSeCC7nZQ8E= github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16/go.mod h1:IKrnDWs3/Mqq5n0lI+RxA2sB7MvN/vbMBP3ehXg65UI= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedib0t/go-pretty/v6 v6.4.0 h1:YlI/2zYDrweA4MThiYMKtGRfT+2qZOO65ulej8GTcVI= github.com/jedib0t/go-pretty/v6 v6.4.0/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -585,17 +574,14 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jsimonetti/rtnetlink v1.3.2 h1:dcn0uWkfxycEEyNy0IGfx3GrhQ38LH7odjxAghimsVI= github.com/jsimonetti/rtnetlink v1.3.2/go.mod h1:BBu4jZCpTjP6Gk0/wfrO8qcqymnN3g0hoFqObRmUo6U= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/juju/errors v1.0.0 h1:yiq7kjCLll1BiaRuNY53MGI0+EQ3rF6GB+wvboZDefM= github.com/juju/errors v1.0.0/go.mod h1:B5x9thDqx0wIMH3+aLIMP9HjItInYWObRovoCFM5Qe8= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/justinas/nosurf v1.1.1 h1:92Aw44hjSK4MxJeMSyDa7jwuI9GR2J/JCQiaKvXXSlk= github.com/justinas/nosurf v1.1.1/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -671,12 +657,10 @@ github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= @@ -690,7 +674,6 @@ github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= @@ -710,10 +693,8 @@ github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= @@ -729,7 +710,6 @@ github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4Y github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek= github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o= @@ -801,7 +781,6 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= @@ -809,7 +788,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= @@ -927,7 +905,6 @@ github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGj github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0= github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= @@ -1386,7 +1363,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 5b4733abe9b88179a9c874969b9789082455ee67 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 20:00:26 +0000 Subject: [PATCH 26/33] restructure --- coderd/insights_test.go | 104 ++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 42037e92fac68..d94a559996e1c 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -657,6 +657,58 @@ func TestTemplateInsights_Golden(t *testing.T) { appUsage []appUsage } + prepareFixtureAndTestData := func(t *testing.T, makeFixture func() ([]*testTemplate, []*testUser), makeData func([]*testTemplate, []*testUser) map[*testWorkspace]testDataGen) ([]*testTemplate, []*testUser, map[*testWorkspace]testDataGen) { + var stableIDs []uuid.UUID + newStableUUID := func() uuid.UUID { + stableIDs = append(stableIDs, uuid.MustParse(fmt.Sprintf("00000000-0000-0000-0000-%012d", len(stableIDs)+1))) + stableID := stableIDs[len(stableIDs)-1] + return stableID + } + + templates, users := makeFixture() + for _, template := range templates { + template.id = newStableUUID() + } + for _, user := range users { + for _, workspace := range user.workspaces { + workspace.user = user + for _, app := range workspace.template.apps { + app := workspaceApp(app) + workspace.apps = append(workspace.apps, &app) + } + } + } + + testData := makeData(templates, users) + // Sanity check. + for ws, data := range testData { + for _, usage := range data.appUsage { + found := false + for _, app := range ws.apps { + if usage.app == app { // Pointer equality + found = true + break + } + } + if !found { + for _, user := range users { + for _, workspace := range user.workspaces { + for _, app := range workspace.apps { + if usage.app == app { // Pointer equality + require.True(t, found, "test bug: app %q not in workspace %q: want user=%s workspace=%s; got user=%s workspace=%s ", usage.app.name, ws.name, ws.user.(*testUser).name, ws.name, user.name, workspace.name) + break + } + } + } + } + require.True(t, found, "test bug: app %q not in workspace %q", usage.app.name, ws.name) + } + } + } + + return templates, users, testData + } + prepare := func(t *testing.T, templates []*testTemplate, users []*testUser, testData map[*testWorkspace]testDataGen) *codersdk.Client { logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: false}).Leveled(slog.LevelDebug) db, pubsub := dbtestutil.NewDB(t) @@ -892,58 +944,6 @@ func TestTemplateInsights_Golden(t *testing.T) { return client } - prepareFixtureAndTestData := func(t *testing.T, makeFixture func() ([]*testTemplate, []*testUser), makeData func([]*testTemplate, []*testUser) map[*testWorkspace]testDataGen) ([]*testTemplate, []*testUser, map[*testWorkspace]testDataGen) { - var stableIDs []uuid.UUID - newStableUUID := func() uuid.UUID { - stableIDs = append(stableIDs, uuid.MustParse(fmt.Sprintf("00000000-0000-0000-0000-%012d", len(stableIDs)+1))) - stableID := stableIDs[len(stableIDs)-1] - return stableID - } - - templates, users := makeFixture() - for _, template := range templates { - template.id = newStableUUID() - } - for _, user := range users { - for _, workspace := range user.workspaces { - workspace.user = user - for _, app := range workspace.template.apps { - app := workspaceApp(app) - workspace.apps = append(workspace.apps, &app) - } - } - } - - testData := makeData(templates, users) - // Sanity check. - for ws, data := range testData { - for _, usage := range data.appUsage { - found := false - for _, app := range ws.apps { - if usage.app == app { // Pointer equality - found = true - break - } - } - if !found { - for _, user := range users { - for _, workspace := range user.workspaces { - for _, app := range workspace.apps { - if usage.app == app { // Pointer equality - require.True(t, found, "test bug: app %q not in workspace %q: want user=%s workspace=%s; got user=%s workspace=%s ", usage.app.name, ws.name, ws.user.(*testUser).name, ws.name, user.name, workspace.name) - break - } - } - } - } - require.True(t, found, "test bug: app %q not in workspace %q", usage.app.name, ws.name) - } - } - } - - return templates, users, testData - } - baseTemplateAndUserFixture := func() ([]*testTemplate, []*testUser) { // Test templates and configuration to generate. templates := []*testTemplate{ From 3607a80c195b1854d0796722fb820b3590ad0c2d Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 20:20:10 +0000 Subject: [PATCH 27/33] more fakedb fixes --- coderd/database/dbfake/dbfake.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index e55d655d4c126..88f67a4ff49ce 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -2018,6 +2018,10 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G return nil, err } + if len(arg.TemplateIDs) > 0 && !slices.Contains(arg.TemplateIDs, w.TemplateID) { + continue + } + app, _ := q.getWorkspaceAppByAgentIDAndSlugNoLock(ctx, database.GetWorkspaceAppByAgentIDAndSlugParams{ AgentID: s.AgentID, Slug: s.SlugOrPort, @@ -2280,6 +2284,15 @@ func (q *FakeQuerier) GetTemplateDailyInsights(ctx context.Context, arg database continue } + w, err := q.getWorkspaceByIDNoLock(ctx, s.WorkspaceID) + if err != nil { + return nil, err + } + + if len(arg.TemplateIDs) > 0 && !slices.Contains(arg.TemplateIDs, w.TemplateID) { + continue + } + for _, ds := range dailyStats { // (was.session_started_at >= ts.from_ AND was.session_started_at < ts.to_) // OR (was.session_ended_at > ts.from_ AND was.session_ended_at < ts.to_) @@ -2290,15 +2303,6 @@ func (q *FakeQuerier) GetTemplateDailyInsights(ctx context.Context, arg database continue } - w, err := q.getWorkspaceByIDNoLock(ctx, s.WorkspaceID) - if err != nil { - return nil, err - } - - if len(arg.TemplateIDs) > 0 && !slices.Contains(arg.TemplateIDs, w.TemplateID) { - continue - } - ds.userSet[s.UserID] = struct{}{} ds.templateIDSet[w.TemplateID] = struct{}{} break From 688e1d0ef53c74ccd59646fcbb0911d5a7f00fd3 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 20:50:41 +0000 Subject: [PATCH 28/33] fix fakedb logic bugs --- coderd/database/dbfake/dbfake.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 88f67a4ff49ce..4765dce58a8b3 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -2270,7 +2270,6 @@ func (q *FakeQuerier) GetTemplateDailyInsights(ctx context.Context, arg database } ds.userSet[s.UserID] = struct{}{} ds.templateIDSet[s.TemplateID] = struct{}{} - break } } @@ -2305,7 +2304,6 @@ func (q *FakeQuerier) GetTemplateDailyInsights(ctx context.Context, arg database ds.userSet[s.UserID] = struct{}{} ds.templateIDSet[w.TemplateID] = struct{}{} - break } } From 8c294d06d7ba38a37a13f1f20497aca072b7d9c5 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 23 Aug 2023 20:54:34 +0000 Subject: [PATCH 29/33] fix commented code --- coderd/insights_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index d94a559996e1c..b799102bdb6dd 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1129,8 +1129,8 @@ func TestTemplateInsights_Golden(t *testing.T) { // considered both correct and incorrect behavior. // { // One hour of usage, but same user and same template app, only count once. // app: users[0].workspaces[1].apps[0], - // startedAt: weekAgo, - // endedAt: weekAgo.Add(time.Hour), + // startedAt: frozenWeekAgo, + // endedAt: frozenWeekAgo.Add(time.Hour), // requests: 1, // }, { From decbdfede3c407fabd63aaf2b6c9442b9c302fa9 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 24 Aug 2023 09:49:11 +0000 Subject: [PATCH 30/33] fix test bug --- coderd/insights_test.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index b799102bdb6dd..ed3833b9da12c 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -676,6 +676,16 @@ func TestTemplateInsights_Golden(t *testing.T) { app := workspaceApp(app) workspace.apps = append(workspace.apps, &app) } + for _, bp := range workspace.buildParameters { + foundBuildParam := false + for _, param := range workspace.template.parameters { + if bp.templateParameter == param { + foundBuildParam = true + break + } + } + require.True(t, foundBuildParam, "test bug: parameter not in workspace %s template %q", workspace.name, workspace.template.name) + } } } @@ -1019,10 +1029,9 @@ func TestTemplateInsights_Golden(t *testing.T) { name: "workspace2", template: templates[1], buildParameters: []buildParameter{ - {templateParameter: templates[0].parameters[0], value: "ABC"}, - {templateParameter: templates[0].parameters[1], value: "123"}, - {templateParameter: templates[0].parameters[2], value: "BBB"}, - {templateParameter: templates[0].parameters[3], value: "option2"}, + {templateParameter: templates[1].parameters[0], value: "ABC"}, + {templateParameter: templates[1].parameters[1], value: "123"}, + {templateParameter: templates[1].parameters[2], value: "BBB"}, }, }, { From 870aff1f80c1f4062e9a615f145fe3a9aca25b84 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 24 Aug 2023 09:56:14 +0000 Subject: [PATCH 31/33] add test for tpl1 param4 option2 --- coderd/insights_test.go | 9 ++++++++- ...sterday_and_today_deployment_wide.json.golden | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index ed3833b9da12c..4c8c2020fc8bd 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1035,7 +1035,7 @@ func TestTemplateInsights_Golden(t *testing.T) { }, }, { - name: "otherworkspace1", + name: "otherworkspace3", template: templates[2], }, }, @@ -1065,6 +1065,13 @@ func TestTemplateInsights_Golden(t *testing.T) { {templateParameter: templates[2].parameters[0], value: "xyz"}, }, }, + { + name: "workspace2", + template: templates[0], + buildParameters: []buildParameter{ + {templateParameter: templates[0].parameters[3], value: "option2"}, + }, + }, }, }, } diff --git a/coderd/testdata/insights/parameters_yesterday_and_today_deployment_wide.json.golden b/coderd/testdata/insights/parameters_yesterday_and_today_deployment_wide.json.golden index ec051784889b7..fc7ccd8a50ec4 100644 --- a/coderd/testdata/insights/parameters_yesterday_and_today_deployment_wide.json.golden +++ b/coderd/testdata/insights/parameters_yesterday_and_today_deployment_wide.json.golden @@ -68,6 +68,10 @@ "type": "string", "description": "This is first parameter", "values": [ + { + "value": "", + "count": 1 + }, { "value": "ABC", "count": 1 @@ -88,6 +92,10 @@ "type": "string", "description": "This is second parameter", "values": [ + { + "value": "", + "count": 1 + }, { "value": "123", "count": 3 @@ -104,6 +112,10 @@ "type": "string", "description": "This is third parameter", "values": [ + { + "value": "", + "count": 1 + }, { "value": "BBB", "count": 2 @@ -140,6 +152,10 @@ { "value": "option1", "count": 2 + }, + { + "value": "option2", + "count": 1 } ] } From 75a96d7a32a96a39b7eacdd81028570ff9f30e36 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 24 Aug 2023 10:11:00 +0000 Subject: [PATCH 32/33] add outside timeframe tests for apps --- coderd/insights_test.go | 21 +++++++++++++++---- ...r_timezone_(S\303\243o_Paulo).json.golden" | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 4c8c2020fc8bd..6ec5d1b3d12dc 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -1165,7 +1165,7 @@ func TestTemplateInsights_Golden(t *testing.T) { }, users[1].workspaces[0]: { agentStats: []agentStat{ - { // One hour of usage before timeframe (exclude). + { // One hour of agent usage before timeframe (exclude). startedAt: frozenWeekAgo.Add(-time.Hour), endedAt: frozenWeekAgo, sessionCountVSCode: 1, @@ -1176,14 +1176,27 @@ func TestTemplateInsights_Golden(t *testing.T) { endedAt: frozenWeekAgo.Add(time.Hour), sessionCountSSH: 1, }, - { // One hour of usage after timeframe (exclude in UTC, include in São Paulo). + { // One hour of agent usage after timeframe (exclude in UTC, include in São Paulo). startedAt: frozenWeekAgo.AddDate(0, 0, 7), endedAt: frozenWeekAgo.AddDate(0, 0, 7).Add(time.Hour), sessionCountVSCode: 1, sessionCountSSH: 1, }, }, - appUsage: []appUsage{}, + appUsage: []appUsage{ + { // One hour of app usage before timeframe (exclude). + app: users[1].workspaces[0].apps[2], + startedAt: frozenWeekAgo.Add(-time.Hour), + endedAt: frozenWeekAgo, + requests: 1, + }, + { // One hour of app usage after timeframe (exclude in UTC, include in São Paulo). + app: users[1].workspaces[0].apps[2], + startedAt: frozenWeekAgo.AddDate(0, 0, 7), + endedAt: frozenWeekAgo.AddDate(0, 0, 7).Add(time.Hour), + requests: 1, + }, + }, }, users[2].workspaces[0]: { agentStats: []agentStat{ @@ -1201,7 +1214,7 @@ func TestTemplateInsights_Golden(t *testing.T) { endedAt: frozenWeekAgo.AddDate(0, 0, 2).Add(5 * time.Minute), requests: 1, }, - { // Excluded from apps, but counted as active during the day. + { // Special app; excluded from apps, but counted as active during the day. app: users[2].workspaces[0].apps[1], startedAt: frozenWeekAgo.AddDate(0, 0, 3), endedAt: frozenWeekAgo.AddDate(0, 0, 3).Add(5 * time.Minute), diff --git "a/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" "b/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" index 0cb67a0eda5f5..1582835e4d665 100644 --- "a/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" +++ "b/coderd/testdata/insights/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" @@ -67,7 +67,7 @@ "display_name": "app3", "slug": "app3", "icon": "/icon2.png", - "seconds": 900 + "seconds": 4500 }, { "template_ids": [ From e72e2658a453f345cabeb29e2129abbd2eba2652 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 24 Aug 2023 10:11:59 +0000 Subject: [PATCH 33/33] remove previous insights test in favor of new one --- coderd/insights_test.go | 340 ---------------------------------------- 1 file changed, 340 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 6ec5d1b3d12dc..29c79561cf8a4 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -16,7 +16,6 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "golang.org/x/exp/slices" "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" @@ -241,345 +240,6 @@ func TestUserLatencyInsights_BadRequest(t *testing.T) { assert.Error(t, err, "want error for end time partial day when not today") } -func TestTemplateInsights(t *testing.T) { - t.Parallel() - - const ( - firstParameterName = "first_parameter" - firstParameterDisplayName = "First PARAMETER" - firstParameterType = "string" - firstParameterDescription = "This is first parameter" - firstParameterValue = "abc" - - secondParameterName = "second_parameter" - secondParameterDisplayName = "Second PARAMETER" - secondParameterType = "number" - secondParameterDescription = "This is second parameter" - secondParameterValue = "123" - - thirdParameterName = "third_parameter" - thirdParameterDisplayName = "Third PARAMETER" - thirdParameterType = "string" - thirdParameterDescription = "This is third parameter" - thirdParameterValue = "bbb" - thirdParameterOptionName1 = "This is AAA" - thirdParameterOptionValue1 = "aaa" - thirdParameterOptionName2 = "This is BBB" - thirdParameterOptionValue2 = "bbb" - thirdParameterOptionName3 = "This is CCC" - thirdParameterOptionValue3 = "ccc" - - testAppSlug = "test-app" - testAppName = "Test App" - testAppIcon = "/icon.png" - testAppURL = "http://127.1.0.1:65536" // Not used. - ) - - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) - opts := &coderdtest.Options{ - Logger: &logger, - IncludeProvisionerDaemon: true, - AgentStatsRefreshInterval: time.Millisecond * 100, - } - client, _, coderdAPI := coderdtest.NewWithAPI(t, opts) - - user := coderdtest.CreateFirstUser(t, client) - _, otherUser := coderdtest.CreateAnotherUser(t, client, user.OrganizationID) - authToken := uuid.NewString() - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ - Parse: echo.ParseComplete, - ProvisionPlan: []*proto.Provision_Response{ - { - Type: &proto.Provision_Response_Complete{ - Complete: &proto.Provision_Complete{ - Parameters: []*proto.RichParameter{ - {Name: firstParameterName, DisplayName: firstParameterDisplayName, Type: firstParameterType, Description: firstParameterDescription, Required: true}, - {Name: secondParameterName, DisplayName: secondParameterDisplayName, Type: secondParameterType, Description: secondParameterDescription, Required: true}, - {Name: thirdParameterName, DisplayName: thirdParameterDisplayName, Type: thirdParameterType, Description: thirdParameterDescription, Required: true, Options: []*proto.RichParameterOption{ - {Name: thirdParameterOptionName1, Value: thirdParameterOptionValue1}, - {Name: thirdParameterOptionName2, Value: thirdParameterOptionValue2}, - {Name: thirdParameterOptionName3, Value: thirdParameterOptionValue3}, - }}, - }, - }, - }, - }, - }, - ProvisionApply: []*proto.Provision_Response{{ - Type: &proto.Provision_Response_Complete{ - Complete: &proto.Provision_Complete{ - Resources: []*proto.Resource{{ - Name: "example", - Type: "aws_instance", - Agents: []*proto.Agent{{ - Id: uuid.NewString(), - Name: "dev", - Auth: &proto.Agent_Token{ - Token: authToken, - }, - Apps: []*proto.App{ - { - Slug: testAppSlug, - DisplayName: testAppName, - Icon: testAppIcon, - SharingLevel: proto.AppSharingLevel_OWNER, - Url: testAppURL, - }, - }, - }}, - }}, - }, - }, - }}, - }) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - require.Empty(t, template.BuildTimeStats[codersdk.WorkspaceTransitionStart]) - coderdtest.AwaitTemplateVersionJob(t, client, version.ID) - - buildParameters := []codersdk.WorkspaceBuildParameter{ - {Name: firstParameterName, Value: firstParameterValue}, - {Name: secondParameterName, Value: secondParameterValue}, - {Name: thirdParameterName, Value: thirdParameterValue}, - } - - workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) { - cwr.RichParameterValues = buildParameters - }) - coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) - - // Start an agent so that we can generate stats. - agentClient := agentsdk.New(client.URL) - agentClient.SetSessionToken(authToken) - agentCloser := agent.New(agent.Options{ - Logger: logger.Named("agent"), - Client: agentClient, - }) - defer func() { - _ = agentCloser.Close() - }() - resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) - - // Start must be at the beginning of the day, initialize it early in case - // the day changes so that we get the relevant stats faster. - y, m, d := time.Now().UTC().Date() - today := time.Date(y, m, d, 0, 0, 0, 0, time.UTC) - requestStartTime := today - requestEndTime := time.Now().UTC().Truncate(time.Hour).Add(time.Hour) - - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() - - // TODO(mafredri): We should prefer to set up an app and generate - // data by accessing it. - // Insert entries within and outside timeframe. - reporter := workspaceapps.NewStatsDBReporter(coderdAPI.Database, workspaceapps.DefaultStatsDBReporterBatchSize) - //nolint:gocritic // This is a test. - err := reporter.Report(dbauthz.AsSystemRestricted(ctx), []workspaceapps.StatsReport{ - { - UserID: user.UserID, - WorkspaceID: workspace.ID, - AgentID: resources[0].Agents[0].ID, - AccessMethod: workspaceapps.AccessMethodPath, - SlugOrPort: testAppSlug, - SessionID: uuid.New(), - // Outside report range. - SessionStartedAt: requestStartTime.Add(-1 * time.Minute), - SessionEndedAt: requestStartTime, - Requests: 1, - }, - { - UserID: user.UserID, - WorkspaceID: workspace.ID, - AgentID: resources[0].Agents[0].ID, - AccessMethod: workspaceapps.AccessMethodPath, - SlugOrPort: testAppSlug, - SessionID: uuid.New(), - // One minute of usage (rounded up to 5 due to query intervals). - // TODO(mafredri): We'll fix this in a future refactor so that it's - // 1 minute increments instead of 5. - SessionStartedAt: requestStartTime, - SessionEndedAt: requestStartTime.Add(1 * time.Minute), - Requests: 1, - }, - { - // Other use is using users workspace, this will result in an - // additional active user and more time spent in app. - UserID: otherUser.ID, - WorkspaceID: workspace.ID, - AgentID: resources[0].Agents[0].ID, - AccessMethod: workspaceapps.AccessMethodPath, - SlugOrPort: testAppSlug, - SessionID: uuid.New(), - // One minute of usage (rounded up to 5 due to query intervals). - SessionStartedAt: requestStartTime, - SessionEndedAt: requestStartTime.Add(1 * time.Minute), - Requests: 1, - }, - { - UserID: user.UserID, - WorkspaceID: workspace.ID, - AgentID: resources[0].Agents[0].ID, - AccessMethod: workspaceapps.AccessMethodPath, - SlugOrPort: testAppSlug, - SessionID: uuid.New(), - // Five additional minutes of usage. - SessionStartedAt: requestStartTime.Add(10 * time.Minute), - SessionEndedAt: requestStartTime.Add(15 * time.Minute), - Requests: 1, - }, - { - UserID: user.UserID, - WorkspaceID: workspace.ID, - AgentID: resources[0].Agents[0].ID, - AccessMethod: workspaceapps.AccessMethodPath, - SlugOrPort: testAppSlug, - SessionID: uuid.New(), - // Outside report range. - SessionStartedAt: requestEndTime, - SessionEndedAt: requestEndTime.Add(1 * time.Minute), - Requests: 1, - }, - }) - require.NoError(t, err, "want no error inserting stats") - - // Connect to the agent to generate usage/latency stats. - conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{ - Logger: logger.Named("client"), - }) - require.NoError(t, err) - defer conn.Close() - - sshConn, err := conn.SSHClient(ctx) - require.NoError(t, err) - defer sshConn.Close() - - // Start an SSH session to generate SSH usage stats. - sess, err := sshConn.NewSession() - require.NoError(t, err) - defer sess.Close() - - r, w := io.Pipe() - defer r.Close() - defer w.Close() - sess.Stdin = r - err = sess.Start("cat") - require.NoError(t, err) - - // Start an rpty session to generate rpty usage stats. - rpty, err := client.WorkspaceAgentReconnectingPTY(ctx, codersdk.WorkspaceAgentReconnectingPTYOpts{ - AgentID: resources[0].Agents[0].ID, - Reconnect: uuid.New(), - Width: 80, - Height: 24, - }) - require.NoError(t, err) - defer rpty.Close() - - var resp codersdk.TemplateInsightsResponse - var req codersdk.TemplateInsightsRequest - waitForAppSeconds := func(slug string) func() bool { - return func() bool { - req = codersdk.TemplateInsightsRequest{ - StartTime: requestStartTime, - EndTime: requestEndTime, - Interval: codersdk.InsightsReportIntervalDay, - } - resp, err = client.TemplateInsights(ctx, req) - if !assert.NoError(t, err) { - return false - } - - if slices.IndexFunc(resp.Report.AppsUsage, func(au codersdk.TemplateAppUsage) bool { - return au.Slug == slug && au.Seconds > 0 - }) != -1 { - return true - } - return false - } - } - require.Eventually(t, waitForAppSeconds("reconnecting-pty"), testutil.WaitMedium, testutil.IntervalFast, "reconnecting-pty seconds missing") - require.Eventually(t, waitForAppSeconds("ssh"), testutil.WaitMedium, testutil.IntervalFast, "ssh seconds missing") - - // We got our data, close down sessions and connections. - _ = rpty.Close() - _ = sess.Close() - _ = sshConn.Close() - - assert.WithinDuration(t, req.StartTime, resp.Report.StartTime, 0) - assert.WithinDuration(t, req.EndTime, resp.Report.EndTime, 0) - assert.Equal(t, int64(2), resp.Report.ActiveUsers, "want two active users") - var gotApps []codersdk.TemplateAppUsage - // Check builtin apps usage. - for _, app := range resp.Report.AppsUsage { - if app.Type != codersdk.TemplateAppsTypeBuiltin { - gotApps = append(gotApps, app) - continue - } - if slices.Contains([]string{"reconnecting-pty", "ssh"}, app.Slug) { - assert.Equal(t, app.Seconds, int64(300), "want app %q to have 5 minutes of usage", app.Slug) - } else { - assert.Equal(t, app.Seconds, int64(0), "want app %q to have 0 minutes of usage", app.Slug) - } - } - // Check app usage. - assert.Len(t, gotApps, 1, "want one app") - assert.Equal(t, []codersdk.TemplateAppUsage{ - { - TemplateIDs: []uuid.UUID{template.ID}, - Type: codersdk.TemplateAppsTypeApp, - Slug: testAppSlug, - DisplayName: testAppName, - Icon: testAppIcon, - Seconds: 300 + 300 + 300, // Three times 5 minutes of usage (actually 1 + 1 + 5, but see TODO above). - }, - }, gotApps, "want app usage to match") - - // The full timeframe is <= 24h, so the interval matches exactly. - require.Len(t, resp.IntervalReports, 1, "want one interval report") - assert.WithinDuration(t, req.StartTime, resp.IntervalReports[0].StartTime, 0) - assert.WithinDuration(t, req.EndTime, resp.IntervalReports[0].EndTime, 0) - assert.Equal(t, int64(2), resp.IntervalReports[0].ActiveUsers, "want two active users in the interval report") - - // The workspace uses 3 parameters - require.Len(t, resp.Report.ParametersUsage, 3) - assert.Equal(t, firstParameterName, resp.Report.ParametersUsage[0].Name) - assert.Equal(t, firstParameterType, resp.Report.ParametersUsage[0].Type) - assert.Equal(t, firstParameterDescription, resp.Report.ParametersUsage[0].Description) - assert.Equal(t, firstParameterDisplayName, resp.Report.ParametersUsage[0].DisplayName) - assert.Contains(t, resp.Report.ParametersUsage[0].Values, codersdk.TemplateParameterValue{ - Value: firstParameterValue, - Count: 1, - }) - assert.Contains(t, resp.Report.ParametersUsage[0].TemplateIDs, template.ID) - assert.Empty(t, resp.Report.ParametersUsage[0].Options) - - assert.Equal(t, secondParameterName, resp.Report.ParametersUsage[1].Name) - assert.Equal(t, secondParameterType, resp.Report.ParametersUsage[1].Type) - assert.Equal(t, secondParameterDescription, resp.Report.ParametersUsage[1].Description) - assert.Equal(t, secondParameterDisplayName, resp.Report.ParametersUsage[1].DisplayName) - assert.Contains(t, resp.Report.ParametersUsage[1].Values, codersdk.TemplateParameterValue{ - Value: secondParameterValue, - Count: 1, - }) - assert.Contains(t, resp.Report.ParametersUsage[1].TemplateIDs, template.ID) - assert.Empty(t, resp.Report.ParametersUsage[1].Options) - - assert.Equal(t, thirdParameterName, resp.Report.ParametersUsage[2].Name) - assert.Equal(t, thirdParameterType, resp.Report.ParametersUsage[2].Type) - assert.Equal(t, thirdParameterDescription, resp.Report.ParametersUsage[2].Description) - assert.Equal(t, thirdParameterDisplayName, resp.Report.ParametersUsage[2].DisplayName) - assert.Contains(t, resp.Report.ParametersUsage[2].Values, codersdk.TemplateParameterValue{ - Value: thirdParameterValue, - Count: 1, - }) - assert.Contains(t, resp.Report.ParametersUsage[2].TemplateIDs, template.ID) - assert.Equal(t, []codersdk.TemplateVersionParameterOption{ - {Name: thirdParameterOptionName1, Value: thirdParameterOptionValue1}, - {Name: thirdParameterOptionName2, Value: thirdParameterOptionValue2}, - {Name: thirdParameterOptionName3, Value: thirdParameterOptionValue3}, - }, resp.Report.ParametersUsage[2].Options) -} - func TestTemplateInsights_Golden(t *testing.T) { t.Parallel()