feat(coderd): add times_used to coder_apps in insights API (#13292)

For now, only applied to `coder_app`s, same logic can be implemented for
VS Code, SSH, etc.

Part of #13099
This commit is contained in:
Mathias Fredriksson
2024-05-16 16:53:01 +03:00
committed by GitHub
parent 63e06853eb
commit a0fce363cd
24 changed files with 346 additions and 102 deletions

4
coderd/apidoc/docs.go generated
View File

@ -11372,6 +11372,10 @@ const docTemplate = `{
"format": "uuid"
}
},
"times_used": {
"type": "integer",
"example": 2
},
"type": {
"allOf": [
{

View File

@ -10272,6 +10272,10 @@
"format": "uuid"
}
},
"times_used": {
"type": "integer",
"example": 2
},
"type": {
"allOf": [
{

View File

@ -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.

View File

@ -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
}

View File

@ -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

View File

@ -543,6 +543,7 @@ func convertTemplateInsightsApps(usage database.GetTemplateInsightsRow, appUsage
Slug: app.Slug,
Icon: app.Icon,
Seconds: app.UsageSeconds,
TimesUsed: app.TimesUsed,
})
}

View File

@ -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": []

View File

@ -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": []

View File

@ -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": []

View File

@ -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": []

View File

@ -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": []

View File

@ -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": []

View File

@ -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": []

View File

@ -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": []

View File

@ -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": []

View File

@ -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": []

View File

@ -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": []

View File

@ -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": []

View File

@ -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": [