@@ -292,56 +292,51 @@ GROUP BY
292
292
t .template_ids , ai .app_name , ai .display_name , ai .icon , ai .is_app ;
293
293
294
294
-- name: GetTemplateAppInsightsByTemplate :many
295
- SELECT
296
- tus .template_id ,
297
- COUNT (DISTINCT tus .user_id ) AS active_users,
298
- app_usage .key ::text AS slug_or_port,
299
- COALESCE(wa .display_name , ' ' ) AS display_name,
300
- (SUM (app_usage .value ::int ) * 60 )::bigint AS usage_seconds
301
- FROM
302
- template_usage_stats AS tus, jsonb_each(app_usage_mins) AS app_usage
303
- LEFT JOIN LATERAL (
304
- -- The joins in this query are necessary to associate an app with a
305
- -- template, we use this to get the app metadata like display name
306
- -- and icon.
295
+ WITH app_stats_by_user_and_agent AS (
307
296
SELECT
308
- app .display_name ,
309
- app .slug
310
- FROM
311
- workspace_apps AS app
312
- JOIN
313
- workspace_agents AS agent
314
- ON
315
- agent .id = app .agent_id
316
- JOIN
317
- workspace_resources AS resource
318
- ON
319
- resource .id = agent .resource_id
320
- JOIN
321
- workspace_builds AS build
322
- ON
323
- build .job_id = resource .job_id
324
- JOIN
325
- workspaces AS workspace
326
- ON
327
- workspace .id = build .workspace_id
297
+ s .start_time ,
298
+ 60 as seconds,
299
+ w .template_id ,
300
+ was .user_id ,
301
+ was .agent_id ,
302
+ was .slug_or_port ,
303
+ wa .display_name ,
304
+ (wa .slug IS NOT NULL )::boolean AS is_app
305
+ FROM workspace_app_stats was
306
+ JOIN workspaces w ON (
307
+ w .id = was .workspace_id
308
+ )
309
+ -- We do a left join here because we want to include user IDs that have used
310
+ -- e.g. ports when counting active users.
311
+ LEFT JOIN workspace_apps wa ON (
312
+ wa .agent_id = was .agent_id
313
+ AND wa .slug = was .slug_or_port
314
+ )
315
+ -- This table contains both 1 minute entries and >1 minute entries,
316
+ -- to calculate this with our uniqueness constraints, we generate series
317
+ -- for the longer intervals.
318
+ CROSS JOIN LATERAL generate_series(
319
+ date_trunc(' minute' , was .session_started_at ),
320
+ -- Subtract 1 microsecond to avoid creating an extra series.
321
+ date_trunc(' minute' , was .session_ended_at - ' 1 microsecond' ::interval),
322
+ ' 1 minute' ::interval
323
+ ) s(start_time)
328
324
WHERE
329
- -- Requires lateral join.
330
- app .slug = app_usage .key
331
- AND workspace .owner_id = tus .user_id
332
- AND workspace .template_id = tus .template_id
333
- ORDER BY
334
- app .created_at DESC
335
- LIMIT 1
336
- ) wa
337
- ON
338
- true
339
- WHERE
340
- tus .start_time >= @start_time::timestamptz
341
- AND tus .end_time <= @end_time::timestamptz
342
- AND wa .slug IS NOT NULL -- Check is_app.
343
- GROUP BY
344
- tus .template_id , app_usage .key ::text , wa .display_name ;
325
+ s .start_time >= @start_time::timestamptz
326
+ -- Subtract one minute because the series only contains the start time.
327
+ AND s .start_time < (@end_time::timestamptz ) - ' 1 minute' ::interval
328
+ GROUP BY s .start_time , w .template_id , was .user_id , was .agent_id , was .slug_or_port , wa .display_name , wa .slug
329
+ )
330
+
331
+ SELECT
332
+ template_id,
333
+ display_name,
334
+ slug_or_port,
335
+ COALESCE(COUNT (DISTINCT user_id))::bigint AS active_users,
336
+ SUM (seconds) AS usage_seconds
337
+ FROM app_stats_by_user_and_agent
338
+ WHERE is_app IS TRUE
339
+ GROUP BY template_id, display_name, slug_or_port;
345
340
346
341
-- name: GetTemplateInsightsByInterval :many
347
342
-- GetTemplateInsightsByInterval returns all intervals between start and end
0 commit comments