diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 0a22d84d13642..8e7fad2c05a49 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -11372,6 +11372,10 @@ const docTemplate = `{ "format": "uuid" } }, + "times_used": { + "type": "integer", + "example": 2 + }, "type": { "allOf": [ { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 331b1512393f7..582ccc74f22c3 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -10272,6 +10272,10 @@ "format": "uuid" } }, + "times_used": { + "type": "integer", + "example": 2 + }, "type": { "allOf": [ { diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 8a2ce25b34367..d1bbd6df49492 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -3149,6 +3149,30 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G GROUP BY start_time, user_id, slug, display_name, icon ), + -- Analyze the users unique app usage across all templates. Count + -- usage across consecutive intervals as continuous usage. + times_used AS ( + SELECT DISTINCT ON (user_id, slug, display_name, icon, uniq) + slug, + display_name, + icon, + -- Turn start_time into a unique identifier that identifies a users + -- continuous app usage. The value of uniq is otherwise garbage. + -- + -- Since we're aggregating per user app usage across templates, + -- there can be duplicate start_times. To handle this, we use the + -- dense_rank() function, otherwise row_number() would suffice. + start_time - ( + dense_rank() OVER ( + PARTITION BY + user_id, slug, display_name, icon + ORDER BY + start_time + ) * '30 minutes'::interval + ) AS uniq + FROM + template_usage_stats_with_apps + ), */ // Due to query optimizations, this logic is somewhat inverted from @@ -3160,12 +3184,19 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G DisplayName string Icon string } + type appTimesUsedGroupBy struct { + UserID uuid.UUID + Slug string + DisplayName string + Icon string + } type appInsightsRow struct { appInsightsGroupBy TemplateIDs []uuid.UUID AppUsageMins int64 } appInsightRows := make(map[appInsightsGroupBy]appInsightsRow) + appTimesUsedRows := make(map[appTimesUsedGroupBy]map[time.Time]struct{}) // FROM for _, stat := range q.templateUsageStats { // WHERE @@ -3201,9 +3232,42 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G row.TemplateIDs = append(row.TemplateIDs, stat.TemplateID) row.AppUsageMins = least(row.AppUsageMins+appUsage, 30) appInsightRows[key] = row + + // Prepare to do times_used calculation, distinct start times. + timesUsedKey := appTimesUsedGroupBy{ + UserID: stat.UserID, + Slug: slug, + DisplayName: app.DisplayName, + Icon: app.Icon, + } + if appTimesUsedRows[timesUsedKey] == nil { + appTimesUsedRows[timesUsedKey] = make(map[time.Time]struct{}) + } + // This assigns a distinct time, so we don't need to + // dense_rank() later on, we can simply do row_number(). + appTimesUsedRows[timesUsedKey][stat.StartTime] = struct{}{} } } + appTimesUsedTempRows := make(map[appTimesUsedGroupBy][]time.Time) + for key, times := range appTimesUsedRows { + for t := range times { + appTimesUsedTempRows[key] = append(appTimesUsedTempRows[key], t) + } + } + for _, times := range appTimesUsedTempRows { + slices.SortFunc(times, func(a, b time.Time) int { + return int(a.Sub(b)) + }) + } + for key, times := range appTimesUsedTempRows { + uniq := make(map[time.Time]struct{}) + for i, t := range times { + uniq[t.Add(-(30 * time.Minute * time.Duration(i)))] = struct{}{} + } + appTimesUsedRows[key] = uniq + } + /* -- Even though we allow identical apps to be aggregated across -- templates, we still want to be able to report which templates @@ -3288,14 +3352,20 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G var rows []database.GetTemplateAppInsightsRow for key, gr := range groupedRows { - rows = append(rows, database.GetTemplateAppInsightsRow{ + row := database.GetTemplateAppInsightsRow{ TemplateIDs: templateRows[key].TemplateIDs, ActiveUsers: int64(len(uniqueSortedUUIDs(gr.ActiveUserIDs))), Slug: key.Slug, DisplayName: key.DisplayName, Icon: key.Icon, UsageSeconds: gr.UsageSeconds, - }) + } + for tuk, uniq := range appTimesUsedRows { + if key.Slug == tuk.Slug && key.DisplayName == tuk.DisplayName && key.Icon == tuk.Icon { + row.TimesUsed += int64(len(uniq)) + } + } + rows = append(rows, row) } // NOTE(mafredri): Add sorting if we decide on how to handle PostgreSQL collations. diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index e0fba2dad35bd..f4e7d4d70e4b6 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1805,7 +1805,7 @@ WITH apps.slug, apps.display_name, apps.icon, - tus.app_usage_mins + (tus.app_usage_mins -> apps.slug)::smallint AS usage_mins FROM apps JOIN @@ -1829,14 +1829,36 @@ WITH display_name, icon, -- See motivation in GetTemplateInsights for LEAST(SUM(n), 30). - LEAST(SUM(app_usage.value::smallint), 30) AS usage_mins + LEAST(SUM(usage_mins), 30) AS usage_mins FROM - template_usage_stats_with_apps, jsonb_each(app_usage_mins) AS app_usage - WHERE - app_usage.key = slug + template_usage_stats_with_apps GROUP BY start_time, user_id, slug, display_name, icon ), + -- Analyze the users unique app usage across all templates. Count + -- usage across consecutive intervals as continuous usage. + times_used AS ( + SELECT DISTINCT ON (user_id, slug, display_name, icon, uniq) + slug, + display_name, + icon, + -- Turn start_time into a unique identifier that identifies a users + -- continuous app usage. The value of uniq is otherwise garbage. + -- + -- Since we're aggregating per user app usage across templates, + -- there can be duplicate start_times. To handle this, we use the + -- dense_rank() function, otherwise row_number() would suffice. + start_time - ( + dense_rank() OVER ( + PARTITION BY + user_id, slug, display_name, icon + ORDER BY + start_time + ) * '30 minutes'::interval + ) AS uniq + FROM + template_usage_stats_with_apps + ), -- Even though we allow identical apps to be aggregated across -- templates, we still want to be able to report which templates -- the data comes from. @@ -1858,7 +1880,17 @@ SELECT ai.slug, ai.display_name, ai.icon, - (SUM(ai.usage_mins) * 60)::bigint AS usage_seconds + (SUM(ai.usage_mins) * 60)::bigint AS usage_seconds, + COALESCE(( + SELECT + COUNT(*) + FROM + times_used + WHERE + times_used.slug = ai.slug + AND times_used.display_name = ai.display_name + AND times_used.icon = ai.icon + ), 0)::bigint AS times_used FROM app_insights AS ai JOIN @@ -1884,6 +1916,7 @@ type GetTemplateAppInsightsRow struct { DisplayName string `db:"display_name" json:"display_name"` Icon string `db:"icon" json:"icon"` UsageSeconds int64 `db:"usage_seconds" json:"usage_seconds"` + TimesUsed int64 `db:"times_used" json:"times_used"` } // GetTemplateAppInsights returns the aggregate usage of each app in a given @@ -1905,6 +1938,7 @@ func (q *sqlQuerier) GetTemplateAppInsights(ctx context.Context, arg GetTemplate &i.DisplayName, &i.Icon, &i.UsageSeconds, + &i.TimesUsed, ); err != nil { return nil, err } diff --git a/coderd/database/queries/insights.sql b/coderd/database/queries/insights.sql index cd526efeb516e..79b0d43529e4b 100644 --- a/coderd/database/queries/insights.sql +++ b/coderd/database/queries/insights.sql @@ -249,7 +249,7 @@ WITH apps.slug, apps.display_name, apps.icon, - tus.app_usage_mins + (tus.app_usage_mins -> apps.slug)::smallint AS usage_mins FROM apps JOIN @@ -273,14 +273,36 @@ WITH display_name, icon, -- See motivation in GetTemplateInsights for LEAST(SUM(n), 30). - LEAST(SUM(app_usage.value::smallint), 30) AS usage_mins + LEAST(SUM(usage_mins), 30) AS usage_mins FROM - template_usage_stats_with_apps, jsonb_each(app_usage_mins) AS app_usage - WHERE - app_usage.key = slug + template_usage_stats_with_apps GROUP BY start_time, user_id, slug, display_name, icon ), + -- Analyze the users unique app usage across all templates. Count + -- usage across consecutive intervals as continuous usage. + times_used AS ( + SELECT DISTINCT ON (user_id, slug, display_name, icon, uniq) + slug, + display_name, + icon, + -- Turn start_time into a unique identifier that identifies a users + -- continuous app usage. The value of uniq is otherwise garbage. + -- + -- Since we're aggregating per user app usage across templates, + -- there can be duplicate start_times. To handle this, we use the + -- dense_rank() function, otherwise row_number() would suffice. + start_time - ( + dense_rank() OVER ( + PARTITION BY + user_id, slug, display_name, icon + ORDER BY + start_time + ) * '30 minutes'::interval + ) AS uniq + FROM + template_usage_stats_with_apps + ), -- Even though we allow identical apps to be aggregated across -- templates, we still want to be able to report which templates -- the data comes from. @@ -302,7 +324,17 @@ SELECT ai.slug, ai.display_name, ai.icon, - (SUM(ai.usage_mins) * 60)::bigint AS usage_seconds + (SUM(ai.usage_mins) * 60)::bigint AS usage_seconds, + COALESCE(( + SELECT + COUNT(*) + FROM + times_used + WHERE + times_used.slug = ai.slug + AND times_used.display_name = ai.display_name + AND times_used.icon = ai.icon + ), 0)::bigint AS times_used FROM app_insights AS ai JOIN diff --git a/coderd/insights.go b/coderd/insights.go index 2da27e2561762..a54e79a525644 100644 --- a/coderd/insights.go +++ b/coderd/insights.go @@ -543,6 +543,7 @@ func convertTemplateInsightsApps(usage database.GetTemplateInsightsRow, appUsage Slug: app.Slug, Icon: app.Icon, Seconds: app.UsageSeconds, + TimesUsed: app.TimesUsed, }) } diff --git a/coderd/testdata/insights/template/multiple_users_and_workspaces_three_weeks_second_template.json.golden b/coderd/testdata/insights/template/multiple_users_and_workspaces_three_weeks_second_template.json.golden index b5552f1db6902..05681323e56e5 100644 --- a/coderd/testdata/insights/template/multiple_users_and_workspaces_three_weeks_second_template.json.golden +++ b/coderd/testdata/insights/template/multiple_users_and_workspaces_three_weeks_second_template.json.golden @@ -15,7 +15,8 @@ "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [], @@ -23,7 +24,8 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -33,7 +35,8 @@ "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 7200 + "seconds": 7200, + "times_used": 0 }, { "template_ids": [ @@ -43,7 +46,8 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 10800 + "seconds": 10800, + "times_used": 0 }, { "template_ids": [], @@ -51,7 +55,8 @@ "display_name": "SFTP", "slug": "sftp", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -61,7 +66,8 @@ "display_name": "app1", "slug": "app1", "icon": "/icon1.png", - "seconds": 25200 + "seconds": 25200, + "times_used": 2 } ], "parameters_usage": [] diff --git a/coderd/testdata/insights/template/multiple_users_and_workspaces_three_weeks_second_template_only_report.json.golden b/coderd/testdata/insights/template/multiple_users_and_workspaces_three_weeks_second_template_only_report.json.golden index a5ad121ea8a3c..cfd4e17fb203a 100644 --- a/coderd/testdata/insights/template/multiple_users_and_workspaces_three_weeks_second_template_only_report.json.golden +++ b/coderd/testdata/insights/template/multiple_users_and_workspaces_three_weeks_second_template_only_report.json.golden @@ -15,7 +15,8 @@ "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [], @@ -23,7 +24,8 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -33,7 +35,8 @@ "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 7200 + "seconds": 7200, + "times_used": 0 }, { "template_ids": [ @@ -43,7 +46,8 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 10800 + "seconds": 10800, + "times_used": 0 }, { "template_ids": [], @@ -51,7 +55,8 @@ "display_name": "SFTP", "slug": "sftp", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -61,7 +66,8 @@ "display_name": "app1", "slug": "app1", "icon": "/icon1.png", - "seconds": 25200 + "seconds": 25200, + "times_used": 2 } ], "parameters_usage": [] diff --git a/coderd/testdata/insights/template/multiple_users_and_workspaces_week_all_templates.json.golden b/coderd/testdata/insights/template/multiple_users_and_workspaces_week_all_templates.json.golden index b3eef47ce02e9..dd716fd84f3e3 100644 --- a/coderd/testdata/insights/template/multiple_users_and_workspaces_week_all_templates.json.golden +++ b/coderd/testdata/insights/template/multiple_users_and_workspaces_week_all_templates.json.golden @@ -18,7 +18,8 @@ "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [ @@ -28,7 +29,8 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 120 + "seconds": 120, + "times_used": 0 }, { "template_ids": [ @@ -38,7 +40,8 @@ "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [ @@ -50,7 +53,8 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 11520 + "seconds": 11520, + "times_used": 0 }, { "template_ids": [], @@ -58,7 +62,8 @@ "display_name": "SFTP", "slug": "sftp", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -69,7 +74,8 @@ "display_name": "app1", "slug": "app1", "icon": "/icon1.png", - "seconds": 25380 + "seconds": 25380, + "times_used": 4 }, { "template_ids": [ @@ -79,7 +85,8 @@ "display_name": "app3", "slug": "app3", "icon": "/icon2.png", - "seconds": 720 + "seconds": 720, + "times_used": 1 }, { "template_ids": [ @@ -89,7 +96,8 @@ "display_name": "otherapp1", "slug": "otherapp1", "icon": "/icon1.png", - "seconds": 300 + "seconds": 300, + "times_used": 1 } ], "parameters_usage": [] diff --git a/coderd/testdata/insights/template/multiple_users_and_workspaces_week_deployment_wide.json.golden b/coderd/testdata/insights/template/multiple_users_and_workspaces_week_deployment_wide.json.golden index b3eef47ce02e9..dd716fd84f3e3 100644 --- a/coderd/testdata/insights/template/multiple_users_and_workspaces_week_deployment_wide.json.golden +++ b/coderd/testdata/insights/template/multiple_users_and_workspaces_week_deployment_wide.json.golden @@ -18,7 +18,8 @@ "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [ @@ -28,7 +29,8 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 120 + "seconds": 120, + "times_used": 0 }, { "template_ids": [ @@ -38,7 +40,8 @@ "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [ @@ -50,7 +53,8 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 11520 + "seconds": 11520, + "times_used": 0 }, { "template_ids": [], @@ -58,7 +62,8 @@ "display_name": "SFTP", "slug": "sftp", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -69,7 +74,8 @@ "display_name": "app1", "slug": "app1", "icon": "/icon1.png", - "seconds": 25380 + "seconds": 25380, + "times_used": 4 }, { "template_ids": [ @@ -79,7 +85,8 @@ "display_name": "app3", "slug": "app3", "icon": "/icon2.png", - "seconds": 720 + "seconds": 720, + "times_used": 1 }, { "template_ids": [ @@ -89,7 +96,8 @@ "display_name": "otherapp1", "slug": "otherapp1", "icon": "/icon1.png", - "seconds": 300 + "seconds": 300, + "times_used": 1 } ], "parameters_usage": [] diff --git a/coderd/testdata/insights/template/multiple_users_and_workspaces_week_first_template.json.golden b/coderd/testdata/insights/template/multiple_users_and_workspaces_week_first_template.json.golden index 9adec1dd2a666..bdb882543a409 100644 --- a/coderd/testdata/insights/template/multiple_users_and_workspaces_week_first_template.json.golden +++ b/coderd/testdata/insights/template/multiple_users_and_workspaces_week_first_template.json.golden @@ -15,7 +15,8 @@ "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [ @@ -25,7 +26,8 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 120 + "seconds": 120, + "times_used": 0 }, { "template_ids": [], @@ -33,7 +35,8 @@ "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -43,7 +46,8 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 7920 + "seconds": 7920, + "times_used": 0 }, { "template_ids": [], @@ -51,7 +55,8 @@ "display_name": "SFTP", "slug": "sftp", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -61,7 +66,8 @@ "display_name": "app1", "slug": "app1", "icon": "/icon1.png", - "seconds": 3780 + "seconds": 3780, + "times_used": 3 }, { "template_ids": [ @@ -71,7 +77,8 @@ "display_name": "app3", "slug": "app3", "icon": "/icon2.png", - "seconds": 720 + "seconds": 720, + "times_used": 1 } ], "parameters_usage": [] diff --git "a/coderd/testdata/insights/template/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" "b/coderd/testdata/insights/template/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" index e45e23bd88d29..4624f17d6fb26 100644 --- "a/coderd/testdata/insights/template/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" +++ "b/coderd/testdata/insights/template/multiple_users_and_workspaces_week_other_timezone_(S\303\243o_Paulo).json.golden" @@ -17,7 +17,8 @@ "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [ @@ -27,7 +28,8 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 120 + "seconds": 120, + "times_used": 0 }, { "template_ids": [], @@ -35,7 +37,8 @@ "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -45,7 +48,8 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 4320 + "seconds": 4320, + "times_used": 0 }, { "template_ids": [], @@ -53,7 +57,8 @@ "display_name": "SFTP", "slug": "sftp", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -64,7 +69,8 @@ "display_name": "app1", "slug": "app1", "icon": "/icon1.png", - "seconds": 21720 + "seconds": 21720, + "times_used": 2 }, { "template_ids": [ @@ -74,7 +80,8 @@ "display_name": "app3", "slug": "app3", "icon": "/icon2.png", - "seconds": 4320 + "seconds": 4320, + "times_used": 2 }, { "template_ids": [ @@ -84,7 +91,8 @@ "display_name": "otherapp1", "slug": "otherapp1", "icon": "/icon1.png", - "seconds": 300 + "seconds": 300, + "times_used": 1 } ], "parameters_usage": [] diff --git a/coderd/testdata/insights/template/multiple_users_and_workspaces_week_second_template.json.golden b/coderd/testdata/insights/template/multiple_users_and_workspaces_week_second_template.json.golden index 0aaae268732d7..bf3790516ebc6 100644 --- a/coderd/testdata/insights/template/multiple_users_and_workspaces_week_second_template.json.golden +++ b/coderd/testdata/insights/template/multiple_users_and_workspaces_week_second_template.json.golden @@ -15,7 +15,8 @@ "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [], @@ -23,7 +24,8 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [], @@ -31,7 +33,8 @@ "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -41,7 +44,8 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [], @@ -49,7 +53,8 @@ "display_name": "SFTP", "slug": "sftp", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -59,7 +64,8 @@ "display_name": "app1", "slug": "app1", "icon": "/icon1.png", - "seconds": 25200 + "seconds": 25200, + "times_used": 2 } ], "parameters_usage": [] diff --git a/coderd/testdata/insights/template/multiple_users_and_workspaces_week_third_template.json.golden b/coderd/testdata/insights/template/multiple_users_and_workspaces_week_third_template.json.golden index fc0e3785d1d2f..37bd18a11ec89 100644 --- a/coderd/testdata/insights/template/multiple_users_and_workspaces_week_third_template.json.golden +++ b/coderd/testdata/insights/template/multiple_users_and_workspaces_week_third_template.json.golden @@ -13,7 +13,8 @@ "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [], @@ -21,7 +22,8 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -31,7 +33,8 @@ "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [ @@ -41,7 +44,8 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [], @@ -49,7 +53,8 @@ "display_name": "SFTP", "slug": "sftp", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -59,7 +64,8 @@ "display_name": "otherapp1", "slug": "otherapp1", "icon": "/icon1.png", - "seconds": 300 + "seconds": 300, + "times_used": 1 } ], "parameters_usage": [] diff --git a/coderd/testdata/insights/template/multiple_users_and_workspaces_weekly_aggregated_deployment_wide.json.golden b/coderd/testdata/insights/template/multiple_users_and_workspaces_weekly_aggregated_deployment_wide.json.golden index 37012ce9d312f..e408b34fa7e43 100644 --- a/coderd/testdata/insights/template/multiple_users_and_workspaces_weekly_aggregated_deployment_wide.json.golden +++ b/coderd/testdata/insights/template/multiple_users_and_workspaces_weekly_aggregated_deployment_wide.json.golden @@ -18,7 +18,8 @@ "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 7200 + "seconds": 7200, + "times_used": 0 }, { "template_ids": [ @@ -28,7 +29,8 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 120 + "seconds": 120, + "times_used": 0 }, { "template_ids": [ @@ -38,7 +40,8 @@ "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [ @@ -50,7 +53,8 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 15120 + "seconds": 15120, + "times_used": 0 }, { "template_ids": [], @@ -58,7 +62,8 @@ "display_name": "SFTP", "slug": "sftp", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -69,7 +74,8 @@ "display_name": "app1", "slug": "app1", "icon": "/icon1.png", - "seconds": 25380 + "seconds": 25380, + "times_used": 4 }, { "template_ids": [ @@ -79,7 +85,8 @@ "display_name": "app3", "slug": "app3", "icon": "/icon2.png", - "seconds": 3600 + "seconds": 3600, + "times_used": 1 }, { "template_ids": [ @@ -89,7 +96,8 @@ "display_name": "otherapp1", "slug": "otherapp1", "icon": "/icon1.png", - "seconds": 300 + "seconds": 300, + "times_used": 1 } ], "parameters_usage": [] diff --git a/coderd/testdata/insights/template/multiple_users_and_workspaces_weekly_aggregated_first_template.json.golden b/coderd/testdata/insights/template/multiple_users_and_workspaces_weekly_aggregated_first_template.json.golden index 6852211092390..a37b5d49180d8 100644 --- a/coderd/testdata/insights/template/multiple_users_and_workspaces_weekly_aggregated_first_template.json.golden +++ b/coderd/testdata/insights/template/multiple_users_and_workspaces_weekly_aggregated_first_template.json.golden @@ -15,7 +15,8 @@ "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [ @@ -25,7 +26,8 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 120 + "seconds": 120, + "times_used": 0 }, { "template_ids": [], @@ -33,7 +35,8 @@ "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -43,7 +46,8 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 7920 + "seconds": 7920, + "times_used": 0 }, { "template_ids": [], @@ -51,7 +55,8 @@ "display_name": "SFTP", "slug": "sftp", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -61,7 +66,8 @@ "display_name": "app1", "slug": "app1", "icon": "/icon1.png", - "seconds": 3780 + "seconds": 3780, + "times_used": 3 }, { "template_ids": [ @@ -71,7 +77,8 @@ "display_name": "app3", "slug": "app3", "icon": "/icon2.png", - "seconds": 720 + "seconds": 720, + "times_used": 1 } ], "parameters_usage": [] diff --git a/coderd/testdata/insights/template/multiple_users_and_workspaces_weekly_aggregated_templates.json.golden b/coderd/testdata/insights/template/multiple_users_and_workspaces_weekly_aggregated_templates.json.golden index 38df7fbced082..6d5d38a6b2278 100644 --- a/coderd/testdata/insights/template/multiple_users_and_workspaces_weekly_aggregated_templates.json.golden +++ b/coderd/testdata/insights/template/multiple_users_and_workspaces_weekly_aggregated_templates.json.golden @@ -18,7 +18,8 @@ "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 7200 + "seconds": 7200, + "times_used": 0 }, { "template_ids": [ @@ -28,7 +29,8 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 120 + "seconds": 120, + "times_used": 0 }, { "template_ids": [ @@ -38,7 +40,8 @@ "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 3600 + "seconds": 3600, + "times_used": 0 }, { "template_ids": [ @@ -50,7 +53,8 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 15120 + "seconds": 15120, + "times_used": 0 }, { "template_ids": [], @@ -58,7 +62,8 @@ "display_name": "SFTP", "slug": "sftp", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [ @@ -69,7 +74,8 @@ "display_name": "app1", "slug": "app1", "icon": "/icon1.png", - "seconds": 25380 + "seconds": 25380, + "times_used": 4 }, { "template_ids": [ @@ -79,7 +85,8 @@ "display_name": "app3", "slug": "app3", "icon": "/icon2.png", - "seconds": 3600 + "seconds": 3600, + "times_used": 1 }, { "template_ids": [ @@ -89,7 +96,8 @@ "display_name": "otherapp1", "slug": "otherapp1", "icon": "/icon1.png", - "seconds": 300 + "seconds": 300, + "times_used": 1 } ], "parameters_usage": [] diff --git a/coderd/testdata/insights/template/parameters_two_days_ago,_no_data.json.golden b/coderd/testdata/insights/template/parameters_two_days_ago,_no_data.json.golden index dd9761ef0a2ce..3d6328e3134a3 100644 --- a/coderd/testdata/insights/template/parameters_two_days_ago,_no_data.json.golden +++ b/coderd/testdata/insights/template/parameters_two_days_ago,_no_data.json.golden @@ -11,7 +11,8 @@ "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [], @@ -19,7 +20,8 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [], @@ -27,7 +29,8 @@ "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [], @@ -35,7 +38,8 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [], @@ -43,7 +47,8 @@ "display_name": "SFTP", "slug": "sftp", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 } ], "parameters_usage": [] diff --git a/coderd/testdata/insights/template/parameters_yesterday_and_today_deployment_wide.json.golden b/coderd/testdata/insights/template/parameters_yesterday_and_today_deployment_wide.json.golden index 7f0c5b2ed9520..dfdaf745fd18d 100644 --- a/coderd/testdata/insights/template/parameters_yesterday_and_today_deployment_wide.json.golden +++ b/coderd/testdata/insights/template/parameters_yesterday_and_today_deployment_wide.json.golden @@ -11,7 +11,8 @@ "display_name": "Visual Studio Code", "slug": "vscode", "icon": "/icon/code.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [], @@ -19,7 +20,8 @@ "display_name": "JetBrains", "slug": "jetbrains", "icon": "/icon/intellij.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [], @@ -27,7 +29,8 @@ "display_name": "Web Terminal", "slug": "reconnecting-pty", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [], @@ -35,7 +38,8 @@ "display_name": "SSH", "slug": "ssh", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 }, { "template_ids": [], @@ -43,7 +47,8 @@ "display_name": "SFTP", "slug": "sftp", "icon": "/icon/terminal.svg", - "seconds": 0 + "seconds": 0, + "times_used": 0 } ], "parameters_usage": [ diff --git a/codersdk/insights.go b/codersdk/insights.go index 27eb8c3009d30..c9e708de8f34a 100644 --- a/codersdk/insights.go +++ b/codersdk/insights.go @@ -217,6 +217,7 @@ type TemplateAppUsage struct { Slug string `json:"slug" example:"vscode"` Icon string `json:"icon"` Seconds int64 `json:"seconds" example:"80500"` + TimesUsed int64 `json:"times_used" example:"2"` } // TemplateParameterUsage shows the usage of a parameter for one or more diff --git a/docs/api/insights.md b/docs/api/insights.md index 4b8609ae4ffd3..7dae576b847b8 100644 --- a/docs/api/insights.md +++ b/docs/api/insights.md @@ -81,6 +81,7 @@ curl -X GET http://coder-server:8080/api/v2/insights/templates?before=0&after=0 "seconds": 80500, "slug": "vscode", "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "times_used": 2, "type": "builtin" } ], diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 42f8f43517233..cd5c1366e392a 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -4558,6 +4558,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "seconds": 80500, "slug": "vscode", "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "times_used": 2, "type": "builtin" } ``` @@ -4571,6 +4572,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | `seconds` | integer | false | | | | `slug` | string | false | | | | `template_ids` | array of string | false | | | +| `times_used` | integer | false | | | | `type` | [codersdk.TemplateAppsType](#codersdktemplateappstype) | false | | | ## codersdk.TemplateAppsType @@ -4700,6 +4702,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "seconds": 80500, "slug": "vscode", "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "times_used": 2, "type": "builtin" } ], @@ -4765,6 +4768,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "seconds": 80500, "slug": "vscode", "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "times_used": 2, "type": "builtin" } ], diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 9331339ed1aa1..b3280d200328a 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1093,6 +1093,7 @@ export interface TemplateAppUsage { readonly slug: string; readonly icon: string; readonly seconds: number; + readonly times_used: number; } // From codersdk/templates.go diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.stories.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.stories.tsx index 894beb3a600d0..3630a936929a3 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.stories.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.stories.tsx @@ -68,6 +68,7 @@ export const Loaded: Story = { slug: "vscode", icon: "/icon/code.svg", seconds: 2513400, + times_used: 0, }, { template_ids: ["0d286645-29aa-4eaf-9b52-cc5d2740c90b"], @@ -76,6 +77,7 @@ export const Loaded: Story = { slug: "jetbrains", icon: "/icon/intellij.svg", seconds: 0, + times_used: 0, }, { template_ids: ["0d286645-29aa-4eaf-9b52-cc5d2740c90b"], @@ -84,6 +86,7 @@ export const Loaded: Story = { slug: "reconnecting-pty", icon: "/icon/terminal.svg", seconds: 110400, + times_used: 0, }, { template_ids: ["0d286645-29aa-4eaf-9b52-cc5d2740c90b"], @@ -92,6 +95,7 @@ export const Loaded: Story = { slug: "ssh", icon: "/icon/terminal.svg", seconds: 1020900, + times_used: 0, }, ], parameters_usage: [