@@ -193,57 +193,93 @@ GROUP BY template_id;
193
193
-- GetTemplateAppInsights returns the aggregate usage of each app in a given
194
194
-- timeframe. The result can be filtered on template_ids, meaning only user data
195
195
-- from workspaces based on those templates will be included.
196
- WITH app_stats_by_user_and_agent AS (
197
- SELECT
198
- s .start_time ,
199
- 60 as seconds,
200
- w .template_id ,
201
- was .user_id ,
202
- was .agent_id ,
203
- was .access_method ,
204
- was .slug_or_port ,
205
- wa .display_name ,
206
- wa .icon ,
207
- (wa .slug IS NOT NULL )::boolean AS is_app
208
- FROM workspace_app_stats was
209
- JOIN workspaces w ON (
210
- w .id = was .workspace_id
211
- AND CASE WHEN COALESCE(array_length(@template_ids::uuid[], 1 ), 0 ) > 0 THEN w .template_id = ANY(@template_ids::uuid[]) ELSE TRUE END
212
- )
213
- -- We do a left join here because we want to include user IDs that have used
214
- -- e.g. ports when counting active users.
215
- LEFT JOIN workspace_apps wa ON (
216
- wa .agent_id = was .agent_id
217
- AND wa .slug = was .slug_or_port
196
+ WITH
197
+ app_insights AS (
198
+ SELECT
199
+ tus .user_id ,
200
+ array_agg(DISTINCT tus .template_id )::uuid[] AS template_ids,
201
+ app_usage .key ::text AS app_name,
202
+ COALESCE(wa .display_name , ' ' ) AS display_name,
203
+ COALESCE(wa .icon , ' ' ) AS icon,
204
+ (wa .slug IS NOT NULL )::boolean AS is_app,
205
+ LEAST(SUM (app_usage .value ::int ), 30 ) AS app_usage_mins
206
+ FROM
207
+ template_usage_stats AS tus, jsonb_each(app_usage_mins) AS app_usage
208
+ LEFT JOIN LATERAL (
209
+ -- Fetch the latest app info for each app based on slug and template.
210
+ SELECT
211
+ app .display_name ,
212
+ app .icon ,
213
+ app .slug
214
+ FROM
215
+ workspace_apps AS app
216
+ JOIN
217
+ workspace_agents AS agent
218
+ ON
219
+ agent .id = app .agent_id
220
+ JOIN
221
+ workspace_resources AS resource
222
+ ON
223
+ resource .id = agent .resource_id
224
+ JOIN
225
+ workspace_builds AS build
226
+ ON
227
+ build .job_id = resource .job_id
228
+ JOIN
229
+ workspaces AS workspace
230
+ ON
231
+ workspace .id = build .workspace_id
232
+ WHERE
233
+ -- Requires lateral join.
234
+ app .slug = app_usage .key
235
+ AND workspace .owner_id = tus .user_id
236
+ AND workspace .template_id = tus .template_id
237
+ ORDER BY
238
+ app .created_at DESC
239
+ LIMIT 1
240
+ ) AS wa
241
+ ON
242
+ true
243
+ WHERE
244
+ tus .start_time >= @start_time::timestamptz
245
+ AND tus .end_time <= @end_time::timestamptz
246
+ AND CASE WHEN COALESCE(array_length(@template_ids::uuid[], 1 ), 0 ) > 0 THEN tus .template_id = ANY(@template_ids::uuid[]) ELSE TRUE END
247
+ GROUP BY
248
+ tus .start_time , tus .user_id , app_usage .key ::text , wa .display_name , wa .icon , wa .slug
249
+ ),
250
+ templates AS (
251
+ SELECT
252
+ app_name,
253
+ display_name,
254
+ icon,
255
+ is_app,
256
+ array_agg(DISTINCT template_id)::uuid[] AS template_ids
257
+ FROM
258
+ app_insights, unnest(template_ids) AS template_id
259
+ GROUP BY
260
+ app_name, display_name, icon, is_app
218
261
)
219
- -- This table contains both 1 minute entries and >1 minute entries,
220
- -- to calculate this with our uniqueness constraints, we generate series
221
- -- for the longer intervals.
222
- CROSS JOIN LATERAL generate_series(
223
- date_trunc(' minute' , was .session_started_at ),
224
- -- Subtract 1 microsecond to avoid creating an extra series.
225
- date_trunc(' minute' , was .session_ended_at - ' 1 microsecond' ::interval),
226
- ' 1 minute' ::interval
227
- ) s(start_time)
228
- WHERE
229
- s .start_time >= @start_time::timestamptz
230
- -- Subtract one minute because the series only contains the start time.
231
- AND s .start_time < (@end_time::timestamptz ) - ' 1 minute' ::interval
232
- GROUP BY s .start_time , w .template_id , was .user_id , was .agent_id , was .access_method , was .slug_or_port , wa .display_name , wa .icon , wa .slug
233
- )
234
262
235
263
SELECT
236
- array_agg(DISTINCT template_id)::uuid[] AS template_ids,
237
- -- Return IDs so we can combine this with GetTemplateInsights.
238
- array_agg(DISTINCT user_id)::uuid[] AS active_user_ids,
239
- access_method,
240
- slug_or_port,
241
- display_name,
242
- icon,
243
- is_app,
244
- SUM (seconds) AS usage_seconds
245
- FROM app_stats_by_user_and_agent
246
- GROUP BY access_method, slug_or_port, display_name, icon, is_app;
264
+ t .template_ids ,
265
+ array_agg(DISTINCT ai .user_id )::uuid[] AS active_user_ids,
266
+ ' ' ::text AS access_method, -- TODO(mafredri): Remove.
267
+ ai .app_name AS slug_or_port,
268
+ ai .display_name ,
269
+ ai .icon ,
270
+ ai .is_app ,
271
+ (SUM (ai .app_usage_mins ) * 60 )::bigint AS usage_seconds
272
+ FROM
273
+ app_insights AS ai
274
+ JOIN
275
+ templates AS t
276
+ ON
277
+ ai .app_name = t .app_name
278
+ AND ai .display_name = t .display_name
279
+ AND ai .icon = t .icon
280
+ AND ai .is_app = t .is_app
281
+ GROUP BY
282
+ t .template_ids , ai .app_name , ai .display_name , ai .icon , ai .is_app ;
247
283
248
284
-- name: GetTemplateAppInsightsByTemplate :many
249
285
WITH app_stats_by_user_and_agent AS (
0 commit comments