From 7347b818e8a614de994e960ab98ada38fc77467d Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 16 May 2024 11:12:13 +0000 Subject: [PATCH] feat(coderd): add `times_used` to `coder_app`s in insights API For now, only applied to `coder_app`s, same logic can be implemented for VS Code, SSH, etc. Part of #13099 --- coderd/apidoc/docs.go | 4 + coderd/apidoc/swagger.json | 4 + coderd/database/dbmem/dbmem.go | 74 ++++++++++++++++++- coderd/database/queries.sql.go | 46 ++++++++++-- coderd/database/queries/insights.sql | 44 +++++++++-- coderd/insights.go | 1 + ...es_three_weeks_second_template.json.golden | 18 +++-- ...ks_second_template_only_report.json.golden | 18 +++-- ..._workspaces_week_all_templates.json.golden | 24 ++++-- ...orkspaces_week_deployment_wide.json.golden | 24 ++++-- ...workspaces_week_first_template.json.golden | 21 ++++-- ...r_timezone_(S\303\243o_Paulo).json.golden" | 24 ++++-- ...orkspaces_week_second_template.json.golden | 18 +++-- ...workspaces_week_third_template.json.golden | 18 +++-- ...kly_aggregated_deployment_wide.json.golden | 24 ++++-- ...ekly_aggregated_first_template.json.golden | 21 ++++-- ...es_weekly_aggregated_templates.json.golden | 24 ++++-- ...rameters_two_days_ago,_no_data.json.golden | 15 ++-- ...rday_and_today_deployment_wide.json.golden | 15 ++-- codersdk/insights.go | 1 + docs/api/insights.md | 1 + docs/api/schemas.md | 4 + site/src/api/typesGenerated.ts | 1 + .../TemplateInsightsPage.stories.tsx | 4 + 24 files changed, 346 insertions(+), 102 deletions(-) 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: [ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy