@@ -17,7 +17,9 @@ import (
17
17
"cdr.dev/slog/sloggers/slogtest"
18
18
"github.com/coder/coder/agent"
19
19
"github.com/coder/coder/coderd/coderdtest"
20
+ "github.com/coder/coder/coderd/database/dbauthz"
20
21
"github.com/coder/coder/coderd/rbac"
22
+ "github.com/coder/coder/coderd/workspaceapps"
21
23
"github.com/coder/coder/codersdk"
22
24
"github.com/coder/coder/codersdk/agentsdk"
23
25
"github.com/coder/coder/provisioner/echo"
@@ -257,14 +259,19 @@ func TestTemplateInsights(t *testing.T) {
257
259
thirdParameterOptionValue2 = "bbb"
258
260
thirdParameterOptionName3 = "This is CCC"
259
261
thirdParameterOptionValue3 = "ccc"
262
+
263
+ testAppSlug = "test-app"
264
+ testAppName = "Test App"
265
+ testAppIcon = "/icon.png"
266
+ testAppURL = "http://127.1.0.1:65536" // Not used.
260
267
)
261
268
262
269
logger := slogtest .Make (t , nil ).Leveled (slog .LevelDebug )
263
270
opts := & coderdtest.Options {
264
271
IncludeProvisionerDaemon : true ,
265
272
AgentStatsRefreshInterval : time .Millisecond * 100 ,
266
273
}
267
- client := coderdtest .New (t , opts )
274
+ client , _ , coderdAPI := coderdtest .NewWithAPI (t , opts )
268
275
269
276
user := coderdtest .CreateFirstUser (t , client )
270
277
authToken := uuid .NewString ()
@@ -287,7 +294,32 @@ func TestTemplateInsights(t *testing.T) {
287
294
},
288
295
},
289
296
},
290
- ProvisionApply : echo .ProvisionApplyWithAgent (authToken ),
297
+ ProvisionApply : []* proto.Provision_Response {{
298
+ Type : & proto.Provision_Response_Complete {
299
+ Complete : & proto.Provision_Complete {
300
+ Resources : []* proto.Resource {{
301
+ Name : "example" ,
302
+ Type : "aws_instance" ,
303
+ Agents : []* proto.Agent {{
304
+ Id : uuid .NewString (),
305
+ Name : "dev" ,
306
+ Auth : & proto.Agent_Token {
307
+ Token : authToken ,
308
+ },
309
+ Apps : []* proto.App {
310
+ {
311
+ Slug : testAppSlug ,
312
+ DisplayName : testAppName ,
313
+ Icon : testAppIcon ,
314
+ SharingLevel : proto .AppSharingLevel_OWNER ,
315
+ Url : testAppURL ,
316
+ },
317
+ },
318
+ }},
319
+ }},
320
+ },
321
+ },
322
+ }},
291
323
})
292
324
template := coderdtest .CreateTemplate (t , client , user .OrganizationID , version .ID )
293
325
require .Empty (t , template .BuildTimeStats [codersdk .WorkspaceTransitionStart ])
@@ -320,10 +352,70 @@ func TestTemplateInsights(t *testing.T) {
320
352
// the day changes so that we get the relevant stats faster.
321
353
y , m , d := time .Now ().UTC ().Date ()
322
354
today := time .Date (y , m , d , 0 , 0 , 0 , 0 , time .UTC )
355
+ requestStartTime := today
356
+ requestEndTime := time .Now ().UTC ().Truncate (time .Hour ).Add (time .Hour )
323
357
324
358
ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitLong )
325
359
defer cancel ()
326
360
361
+ // TODO(mafredri): We should prefer to set up an app and generate
362
+ // data by accessing it.
363
+ // Insert entries within and outside timeframe.
364
+ reporter := workspaceapps .NewStatsDBReporter (coderdAPI .Database , workspaceapps .DefaultStatsDBReporterBatchSize )
365
+ err := reporter .Report (dbauthz .AsSystemRestricted (ctx ), []workspaceapps.StatsReport {
366
+ {
367
+ UserID : user .UserID ,
368
+ WorkspaceID : workspace .ID ,
369
+ AgentID : resources [0 ].Agents [0 ].ID ,
370
+ AccessMethod : workspaceapps .AccessMethodPath ,
371
+ SlugOrPort : testAppSlug ,
372
+ SessionID : uuid .New (),
373
+ // Outside report range.
374
+ SessionStartedAt : requestStartTime .Add (- 1 * time .Minute ),
375
+ SessionEndedAt : requestStartTime ,
376
+ Requests : 1 ,
377
+ },
378
+ {
379
+ UserID : user .UserID ,
380
+ WorkspaceID : workspace .ID ,
381
+ AgentID : resources [0 ].Agents [0 ].ID ,
382
+ AccessMethod : workspaceapps .AccessMethodPath ,
383
+ SlugOrPort : testAppSlug ,
384
+ SessionID : uuid .New (),
385
+ // One minute of usage (rounded up to 5 due to query intervals).
386
+ // TODO(mafredri): We'll fix this in a future refactor so that it's
387
+ // 1 minute increments instead of 5.
388
+ SessionStartedAt : requestStartTime ,
389
+ SessionEndedAt : requestStartTime .Add (1 * time .Minute ),
390
+ Requests : 1 ,
391
+ },
392
+ {
393
+ UserID : user .UserID ,
394
+ WorkspaceID : workspace .ID ,
395
+ AgentID : resources [0 ].Agents [0 ].ID ,
396
+ AccessMethod : workspaceapps .AccessMethodPath ,
397
+ SlugOrPort : testAppSlug ,
398
+ SessionID : uuid .New (),
399
+ // Five additional minutes of usage.
400
+ SessionStartedAt : requestStartTime .Add (10 * time .Minute ),
401
+ SessionEndedAt : requestStartTime .Add (15 * time .Minute ),
402
+ Requests : 1 ,
403
+ },
404
+ {
405
+ UserID : user .UserID ,
406
+ WorkspaceID : workspace .ID ,
407
+ AgentID : resources [0 ].Agents [0 ].ID ,
408
+ AccessMethod : workspaceapps .AccessMethodPath ,
409
+ SlugOrPort : testAppSlug ,
410
+ SessionID : uuid .New (),
411
+ // Outside report range.
412
+ SessionStartedAt : requestEndTime ,
413
+ SessionEndedAt : requestEndTime .Add (1 * time .Minute ),
414
+ Requests : 1 ,
415
+ },
416
+ })
417
+ require .NoError (t , err , "want no error inserting stats" )
418
+
327
419
// Connect to the agent to generate usage/latency stats.
328
420
conn , err := client .DialWorkspaceAgent (ctx , resources [0 ].Agents [0 ].ID , & codersdk.DialWorkspaceAgentOptions {
329
421
Logger : logger .Named ("client" ),
@@ -362,8 +454,8 @@ func TestTemplateInsights(t *testing.T) {
362
454
waitForAppSeconds := func (slug string ) func () bool {
363
455
return func () bool {
364
456
req = codersdk.TemplateInsightsRequest {
365
- StartTime : today ,
366
- EndTime : time . Now (). UTC (). Truncate ( time . Hour ). Add ( time . Hour ) ,
457
+ StartTime : requestStartTime ,
458
+ EndTime : requestEndTime ,
367
459
Interval : codersdk .InsightsReportIntervalDay ,
368
460
}
369
461
resp , err = client .TemplateInsights (ctx , req )
@@ -390,13 +482,31 @@ func TestTemplateInsights(t *testing.T) {
390
482
assert .WithinDuration (t , req .StartTime , resp .Report .StartTime , 0 )
391
483
assert .WithinDuration (t , req .EndTime , resp .Report .EndTime , 0 )
392
484
assert .Equal (t , resp .Report .ActiveUsers , int64 (1 ), "want one active user" )
485
+ var gotApps []codersdk.TemplateAppUsage
486
+ // Check builtin apps usage.
393
487
for _ , app := range resp .Report .AppsUsage {
488
+ if app .Type != codersdk .TemplateAppsTypeBuiltin {
489
+ gotApps = append (gotApps , app )
490
+ continue
491
+ }
394
492
if slices .Contains ([]string {"reconnecting-pty" , "ssh" }, app .Slug ) {
395
493
assert .Equal (t , app .Seconds , int64 (300 ), "want app %q to have 5 minutes of usage" , app .Slug )
396
494
} else {
397
495
assert .Equal (t , app .Seconds , int64 (0 ), "want app %q to have 0 minutes of usage" , app .Slug )
398
496
}
399
497
}
498
+ // Check app usage.
499
+ assert .Equal (t , gotApps , []codersdk.TemplateAppUsage {
500
+ {
501
+ TemplateIDs : []uuid.UUID {template .ID },
502
+ Type : codersdk .TemplateAppsTypeApp ,
503
+ Slug : testAppSlug ,
504
+ DisplayName : testAppName ,
505
+ Icon : testAppIcon ,
506
+ Seconds : 300 + 300 , // Two times 5 minutes of usage (actually 1 + 5, but see TODO above).
507
+ },
508
+ }, "want app usage to match" )
509
+
400
510
// The full timeframe is <= 24h, so the interval matches exactly.
401
511
require .Len (t , resp .IntervalReports , 1 , "want one interval report" )
402
512
assert .WithinDuration (t , req .StartTime , resp .IntervalReports [0 ].StartTime , 0 )
0 commit comments