feat(coderd/telemetry): add telemetry for database Tasks (#20279)

Adds Tasks to telemetry snapshots

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
This commit is contained in:
Cian Johnston
2025-10-17 10:48:56 +01:00
committed by GitHub
co-authored by Mathias Fredriksson
parent 14e80022c9
commit dc6e50d6b7
5 changed files with 102 additions and 7 deletions
+7 -2
View File
@@ -1561,7 +1561,7 @@ func AIBridgeToolUsage(t testing.TB, db database.Store, seed database.InsertAIBr
return toolUsage
}
func Task(t testing.TB, db database.Store, orig database.TaskTable) database.TaskTable {
func Task(t testing.TB, db database.Store, orig database.TaskTable) database.Task {
t.Helper()
parameters := orig.TemplateParameters
@@ -1581,7 +1581,12 @@ func Task(t testing.TB, db database.Store, orig database.TaskTable) database.Tas
})
require.NoError(t, err, "failed to insert task")
return task
// Return the Task from the view instead of the TaskTable
fetched, err := db.GetTaskByID(genCtx, task.ID)
require.NoError(t, err, "failed to fetch task")
require.Equal(t, task.ID, fetched.ID)
return fetched
}
func TaskWorkspaceApp(t testing.TB, db database.Store, orig database.TaskWorkspaceApp) database.TaskWorkspaceApp {
+2 -2
View File
@@ -6709,7 +6709,7 @@ func TestTasksWithStatusView(t *testing.T) {
buildTransition database.WorkspaceTransition,
agentState database.WorkspaceAgentLifecycleState,
appHealths []database.WorkspaceAppHealth,
) database.TaskTable {
) database.Task {
t.Helper()
template := dbgen.Template(t, db, database.Template{
@@ -7455,7 +7455,7 @@ func TestListTasks(t *testing.T) {
})
// Helper function to create a task
createTask := func(orgID, ownerID uuid.UUID) database.TaskTable {
createTask := func(orgID, ownerID uuid.UUID) database.Task {
ws := dbgen.Workspace(t, db, database.WorkspaceTable{
OrganizationID: orgID,
OwnerID: ownerID,
@@ -3003,9 +3003,9 @@ func TestCompleteJob(t *testing.T) {
OwnerID: user.ID,
OrganizationID: pd.OrganizationID,
})
var taskTable database.TaskTable
var genTask database.Task
if tc.isTask {
taskTable = dbgen.Task(t, db, database.TaskTable{
genTask = dbgen.Task(t, db, database.TaskTable{
OwnerID: user.ID,
OrganizationID: pd.OrganizationID,
WorkspaceID: uuid.NullUUID{UUID: workspaceTable.ID, Valid: true},
@@ -3081,7 +3081,7 @@ func TestCompleteJob(t *testing.T) {
require.Equal(t, tc.expectHasAiTask, build.HasAITask.Bool)
if tc.isTask {
task, err := db.GetTaskByID(ctx, taskTable.ID)
task, err := db.GetTaskByID(ctx, genTask.ID)
require.NoError(t, err)
require.Equal(t, tc.expectTaskStatus, task.Status)
}
+60
View File
@@ -730,6 +730,19 @@ func (r *remoteReporter) createSnapshot() (*Snapshot, error) {
}
return nil
})
eg.Go(func() error {
dbTasks, err := r.options.Database.ListTasks(ctx, database.ListTasksParams{
OwnerID: uuid.Nil,
OrganizationID: uuid.Nil,
})
if err != nil {
return err
}
for _, dbTask := range dbTasks {
snapshot.Tasks = append(snapshot.Tasks, ConvertTask(dbTask))
}
return nil
})
err := eg.Wait()
if err != nil {
@@ -1205,6 +1218,7 @@ type Snapshot struct {
Workspaces []Workspace `json:"workspaces"`
NetworkEvents []NetworkEvent `json:"network_events"`
Organizations []Organization `json:"organizations"`
Tasks []Task `json:"tasks"`
TelemetryItems []TelemetryItem `json:"telemetry_items"`
UserTailnetConnections []UserTailnetConnection `json:"user_tailnet_connections"`
PrebuiltWorkspaces []PrebuiltWorkspace `json:"prebuilt_workspaces"`
@@ -1753,6 +1767,52 @@ type Organization struct {
CreatedAt time.Time `json:"created_at"`
}
type Task struct {
ID string `json:"id"`
OrganizationID string `json:"organization_id"`
OwnerID string `json:"owner_id"`
Name string `json:"name"`
WorkspaceID *string `json:"workspace_id"`
WorkspaceBuildNumber *int64 `json:"workspace_build_number"`
WorkspaceAgentID *string `json:"workspace_agent_id"`
WorkspaceAppID *string `json:"workspace_app_id"`
TemplateVersionID string `json:"template_version_id"`
PromptHash string `json:"prompt_hash"` // Prompt is hashed for privacy.
CreatedAt time.Time `json:"created_at"`
Status string `json:"status"`
}
// ConvertTask anonymizes a Task.
func ConvertTask(task database.Task) Task {
t := &Task{
ID: task.ID.String(),
OrganizationID: task.OrganizationID.String(),
OwnerID: task.OwnerID.String(),
Name: task.Name,
WorkspaceID: nil,
WorkspaceBuildNumber: nil,
WorkspaceAgentID: nil,
WorkspaceAppID: nil,
TemplateVersionID: task.TemplateVersionID.String(),
PromptHash: fmt.Sprintf("%x", sha256.Sum256([]byte(task.Prompt))),
CreatedAt: task.CreatedAt,
Status: string(task.Status),
}
if task.WorkspaceID.Valid {
t.WorkspaceID = ptr.Ref(task.WorkspaceID.UUID.String())
}
if task.WorkspaceBuildNumber.Valid {
t.WorkspaceBuildNumber = ptr.Ref(int64(task.WorkspaceBuildNumber.Int32))
}
if task.WorkspaceAgentID.Valid {
t.WorkspaceAgentID = ptr.Ref(task.WorkspaceAgentID.UUID.String())
}
if task.WorkspaceAppID.Valid {
t.WorkspaceAppID = ptr.Ref(task.WorkspaceAppID.UUID.String())
}
return *t
}
type telemetryItemKey string
// The comment below gets rid of the warning that the name "TelemetryItemKey" has
+30
View File
@@ -151,6 +151,20 @@ func TestTelemetry(t *testing.T) {
HasAITask: sql.NullBool{Valid: true, Bool: true},
AITaskSidebarAppID: uuid.NullUUID{Valid: true, UUID: taskWsApp.ID},
})
task := dbgen.Task(t, db, database.TaskTable{
OwnerID: user.ID,
OrganizationID: org.ID,
WorkspaceID: uuid.NullUUID{Valid: true, UUID: taskWs.ID},
TemplateVersionID: taskTV.ID,
Prompt: "example prompt",
TemplateParameters: json.RawMessage(`{"foo": "bar"}`),
})
taskWA := dbgen.TaskWorkspaceApp(t, db, database.TaskWorkspaceApp{
TaskID: task.ID,
WorkspaceAgentID: uuid.NullUUID{Valid: true, UUID: taskWsAgent.ID},
WorkspaceAppID: uuid.NullUUID{Valid: true, UUID: taskWsApp.ID},
WorkspaceBuildNumber: taskWB.BuildNumber,
})
group := dbgen.Group(t, db, database.Group{
OrganizationID: org.ID,
@@ -220,6 +234,22 @@ func TestTelemetry(t *testing.T) {
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.Len(t, snapshot.Tasks, 1)
for _, snapTask := range snapshot.Tasks {
assert.Equal(t, task.ID.String(), snapTask.ID)
assert.Equal(t, task.OrganizationID.String(), snapTask.OrganizationID)
assert.Equal(t, task.OwnerID.String(), snapTask.OwnerID)
assert.Equal(t, task.Name, snapTask.Name)
if assert.True(t, task.WorkspaceID.Valid) {
assert.Equal(t, task.WorkspaceID.UUID.String(), *snapTask.WorkspaceID)
}
assert.EqualValues(t, taskWA.WorkspaceBuildNumber, *snapTask.WorkspaceBuildNumber)
assert.Equal(t, taskWA.WorkspaceAgentID.UUID.String(), *snapTask.WorkspaceAgentID)
assert.Equal(t, taskWA.WorkspaceAppID.UUID.String(), *snapTask.WorkspaceAppID)
assert.Equal(t, task.TemplateVersionID.String(), snapTask.TemplateVersionID)
assert.Equal(t, "e196fe22e61cfa32d8c38749e0ce348108bb4cae29e2c36cdcce7e77faa9eb5f", snapTask.PromptHash)
assert.Equal(t, task.CreatedAt.UTC(), snapTask.CreatedAt.UTC())
}
require.True(t, slices.ContainsFunc(snapshot.TemplateVersions, func(ttv telemetry.TemplateVersion) bool {
if ttv.ID != taskTV.ID {