From 99fcbee0fee4c18e9d2e36ef931cb30ec011e31a Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 19 Aug 2025 17:31:45 +0100 Subject: [PATCH 1/2] feat(coderd/telemetry): track AI task usage --- coderd/telemetry/telemetry.go | 4 ++ coderd/telemetry/telemetry_test.go | 72 ++++++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/coderd/telemetry/telemetry.go b/coderd/telemetry/telemetry.go index 747cf2cb47de1..cd30d61c34a0d 100644 --- a/coderd/telemetry/telemetry.go +++ b/coderd/telemetry/telemetry.go @@ -776,6 +776,7 @@ func ConvertWorkspaceBuild(build database.WorkspaceBuild) WorkspaceBuild { TemplateVersionID: build.TemplateVersionID, // #nosec G115 - Safe conversion as build numbers are expected to be positive and within uint32 range BuildNumber: uint32(build.BuildNumber), + HasAITask: build.HasAITask.Valid && build.HasAITask.Bool, } } @@ -1105,6 +1106,7 @@ func ConvertTemplateVersion(version database.TemplateVersion) TemplateVersion { if version.SourceExampleID.Valid { snapVersion.SourceExampleID = &version.SourceExampleID.String } + snapVersion.HasAITask = version.HasAITask.Valid && version.HasAITask.Bool return snapVersion } @@ -1357,6 +1359,7 @@ type WorkspaceBuild struct { TemplateVersionID uuid.UUID `json:"template_version_id"` JobID uuid.UUID `json:"job_id"` BuildNumber uint32 `json:"build_number"` + HasAITask bool `json:"has_ai_task"` } type Workspace struct { @@ -1404,6 +1407,7 @@ type TemplateVersion struct { OrganizationID uuid.UUID `json:"organization_id"` JobID uuid.UUID `json:"job_id"` SourceExampleID *string `json:"source_example_id,omitempty"` + HasAITask bool `json:"has_ai_task"` } type ProvisionerJob struct { diff --git a/coderd/telemetry/telemetry_test.go b/coderd/telemetry/telemetry_test.go index 63bdc12870cb3..624639510a2e7 100644 --- a/coderd/telemetry/telemetry_test.go +++ b/coderd/telemetry/telemetry_test.go @@ -7,6 +7,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "slices" "sort" "testing" "time" @@ -105,6 +106,52 @@ func TestTelemetry(t *testing.T) { OpenIn: database.WorkspaceAppOpenInSlimWindow, AgentID: wsagent.ID, }) + + taskJob := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{ + Provisioner: database.ProvisionerTypeTerraform, + StorageMethod: database.ProvisionerStorageMethodFile, + Type: database.ProvisionerJobTypeTemplateVersionDryRun, + OrganizationID: org.ID, + }) + taskTpl := dbgen.Template(t, db, database.Template{ + Provisioner: database.ProvisionerTypeTerraform, + OrganizationID: org.ID, + CreatedBy: user.ID, + }) + _ = dbgen.TemplateVersion(t, db, database.TemplateVersion{ + OrganizationID: org.ID, + TemplateID: uuid.NullUUID{UUID: taskTpl.ID, Valid: true}, + CreatedBy: user.ID, + JobID: taskJob.ID, + HasAITask: sql.NullBool{Bool: true, Valid: true}, + }) + taskWs := dbgen.Workspace(t, db, database.WorkspaceTable{ + OwnerID: user.ID, + OrganizationID: org.ID, + TemplateID: taskTpl.ID, + }) + taskWsResource := dbgen.WorkspaceResource(t, db, database.WorkspaceResource{ + JobID: taskJob.ID, + }) + taskWsAgent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{ + ResourceID: taskWsResource.ID, + }) + taskWsApp := dbgen.WorkspaceApp(t, db, database.WorkspaceApp{ + SharingLevel: database.AppSharingLevelOwner, + Health: database.WorkspaceAppHealthDisabled, + OpenIn: database.WorkspaceAppOpenInSlimWindow, + AgentID: taskWsAgent.ID, + }) + _ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{ + Transition: database.WorkspaceTransitionStart, + Reason: database.BuildReasonAutostart, + WorkspaceID: taskWs.ID, + TemplateVersionID: tv.ID, + JobID: taskJob.ID, + HasAITask: sql.NullBool{Valid: true, Bool: true}, + AITaskSidebarAppID: uuid.NullUUID{Valid: true, UUID: taskWsApp.ID}, + }) + group := dbgen.Group(t, db, database.Group{ OrganizationID: org.ID, }) @@ -148,19 +195,19 @@ func TestTelemetry(t *testing.T) { }) _, snapshot := collectSnapshot(ctx, t, db, nil) - require.Len(t, snapshot.ProvisionerJobs, 1) + require.Len(t, snapshot.ProvisionerJobs, 2) require.Len(t, snapshot.Licenses, 1) - require.Len(t, snapshot.Templates, 1) - require.Len(t, snapshot.TemplateVersions, 2) + require.Len(t, snapshot.Templates, 2) + require.Len(t, snapshot.TemplateVersions, 3) require.Len(t, snapshot.Users, 1) require.Len(t, snapshot.Groups, 2) // 1 member in the everyone group + 1 member in the custom group require.Len(t, snapshot.GroupMembers, 2) - require.Len(t, snapshot.Workspaces, 1) - require.Len(t, snapshot.WorkspaceApps, 1) - require.Len(t, snapshot.WorkspaceAgents, 1) - require.Len(t, snapshot.WorkspaceBuilds, 1) - require.Len(t, snapshot.WorkspaceResources, 1) + require.Len(t, snapshot.Workspaces, 2) + require.Len(t, snapshot.WorkspaceApps, 2) + require.Len(t, snapshot.WorkspaceAgents, 2) + require.Len(t, snapshot.WorkspaceBuilds, 2) + require.Len(t, snapshot.WorkspaceResources, 2) require.Len(t, snapshot.WorkspaceAgentStats, 1) require.Len(t, snapshot.WorkspaceProxies, 1) require.Len(t, snapshot.WorkspaceModules, 1) @@ -169,11 +216,18 @@ func TestTelemetry(t *testing.T) { require.Len(t, snapshot.TelemetryItems, 2) require.Len(t, snapshot.WorkspaceAgentMemoryResourceMonitors, 1) require.Len(t, snapshot.WorkspaceAgentVolumeResourceMonitors, 1) - wsa := snapshot.WorkspaceAgents[0] + wsa := snapshot.WorkspaceAgents[1] require.Len(t, wsa.Subsystems, 2) require.Equal(t, string(database.WorkspaceAgentSubsystemEnvbox), wsa.Subsystems[0]) require.Equal(t, string(database.WorkspaceAgentSubsystemExectrace), wsa.Subsystems[1]) + require.True(t, slices.ContainsFunc(snapshot.TemplateVersions, func(ttv telemetry.TemplateVersion) bool { + return ttv.HasAITask + })) + require.True(t, slices.ContainsFunc(snapshot.WorkspaceBuilds, func(twb telemetry.WorkspaceBuild) bool { + return twb.HasAITask + })) + tvs := snapshot.TemplateVersions sort.Slice(tvs, func(i, j int) bool { // Sort by SourceExampleID presence (non-nil comes before nil) From 16a0380eaa1688a319060eb590d0895468fe2fe3 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 19 Aug 2025 18:15:56 +0100 Subject: [PATCH 2/2] fix: convert to ptr --- coderd/telemetry/telemetry.go | 15 ++++++++++----- coderd/telemetry/telemetry_test.go | 14 ++++++++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/coderd/telemetry/telemetry.go b/coderd/telemetry/telemetry.go index cd30d61c34a0d..8f203126c99ba 100644 --- a/coderd/telemetry/telemetry.go +++ b/coderd/telemetry/telemetry.go @@ -768,7 +768,7 @@ func ConvertWorkspace(workspace database.Workspace) Workspace { // ConvertWorkspaceBuild anonymizes a workspace build. func ConvertWorkspaceBuild(build database.WorkspaceBuild) WorkspaceBuild { - return WorkspaceBuild{ + wb := WorkspaceBuild{ ID: build.ID, CreatedAt: build.CreatedAt, WorkspaceID: build.WorkspaceID, @@ -776,8 +776,11 @@ func ConvertWorkspaceBuild(build database.WorkspaceBuild) WorkspaceBuild { TemplateVersionID: build.TemplateVersionID, // #nosec G115 - Safe conversion as build numbers are expected to be positive and within uint32 range BuildNumber: uint32(build.BuildNumber), - HasAITask: build.HasAITask.Valid && build.HasAITask.Bool, } + if build.HasAITask.Valid { + wb.HasAITask = ptr.Ref(build.HasAITask.Bool) + } + return wb } // ConvertProvisionerJob anonymizes a provisioner job. @@ -1106,7 +1109,9 @@ func ConvertTemplateVersion(version database.TemplateVersion) TemplateVersion { if version.SourceExampleID.Valid { snapVersion.SourceExampleID = &version.SourceExampleID.String } - snapVersion.HasAITask = version.HasAITask.Valid && version.HasAITask.Bool + if version.HasAITask.Valid { + snapVersion.HasAITask = ptr.Ref(version.HasAITask.Bool) + } return snapVersion } @@ -1359,7 +1364,7 @@ type WorkspaceBuild struct { TemplateVersionID uuid.UUID `json:"template_version_id"` JobID uuid.UUID `json:"job_id"` BuildNumber uint32 `json:"build_number"` - HasAITask bool `json:"has_ai_task"` + HasAITask *bool `json:"has_ai_task"` } type Workspace struct { @@ -1407,7 +1412,7 @@ type TemplateVersion struct { OrganizationID uuid.UUID `json:"organization_id"` JobID uuid.UUID `json:"job_id"` SourceExampleID *string `json:"source_example_id,omitempty"` - HasAITask bool `json:"has_ai_task"` + HasAITask *bool `json:"has_ai_task"` } type ProvisionerJob struct { diff --git a/coderd/telemetry/telemetry_test.go b/coderd/telemetry/telemetry_test.go index 624639510a2e7..5508a7d8816f5 100644 --- a/coderd/telemetry/telemetry_test.go +++ b/coderd/telemetry/telemetry_test.go @@ -118,7 +118,7 @@ func TestTelemetry(t *testing.T) { OrganizationID: org.ID, CreatedBy: user.ID, }) - _ = dbgen.TemplateVersion(t, db, database.TemplateVersion{ + taskTV := dbgen.TemplateVersion(t, db, database.TemplateVersion{ OrganizationID: org.ID, TemplateID: uuid.NullUUID{UUID: taskTpl.ID, Valid: true}, CreatedBy: user.ID, @@ -142,7 +142,7 @@ func TestTelemetry(t *testing.T) { OpenIn: database.WorkspaceAppOpenInSlimWindow, AgentID: taskWsAgent.ID, }) - _ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{ + taskWB := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{ Transition: database.WorkspaceTransitionStart, Reason: database.BuildReasonAutostart, WorkspaceID: taskWs.ID, @@ -222,10 +222,16 @@ func TestTelemetry(t *testing.T) { require.Equal(t, string(database.WorkspaceAgentSubsystemExectrace), wsa.Subsystems[1]) require.True(t, slices.ContainsFunc(snapshot.TemplateVersions, func(ttv telemetry.TemplateVersion) bool { - return ttv.HasAITask + if ttv.ID != taskTV.ID { + return false + } + return assert.NotNil(t, ttv.HasAITask) && assert.True(t, *ttv.HasAITask) })) require.True(t, slices.ContainsFunc(snapshot.WorkspaceBuilds, func(twb telemetry.WorkspaceBuild) bool { - return twb.HasAITask + if twb.ID != taskWB.ID { + return false + } + return assert.NotNil(t, twb.HasAITask) && assert.True(t, *twb.HasAITask) })) tvs := snapshot.TemplateVersions 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