Skip to content

Commit ac41b5b

Browse files
feat(coderd): include chat model organization IDs in telemetry (coder#27956)
Include the owning organization ID for each chat model configuration in telemetry snapshots. Depends on coder#27955 _This pull request description was generated by Coder Agents._
1 parent f32592e commit ac41b5b

5 files changed

Lines changed: 41 additions & 33 deletions

File tree

‎coderd/database/querier.go‎

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎coderd/database/queries.sql.go‎

Lines changed: 12 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎coderd/database/queries/chats.sql‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2246,7 +2246,7 @@ WHERE chats.id = deletable.id
22462246
-- snapshot collection. Uses updated_at so that long-running chats
22472247
-- still appear in each snapshot window while they are active.
22482248
SELECT
2249-
c.id, c.owner_id, c.created_at, c.updated_at, c.status,
2249+
c.id, c.owner_id, c.organization_id, c.created_at, c.updated_at, c.status,
22502250
(c.parent_chat_id IS NOT NULL)::bool AS has_parent,
22512251
c.root_chat_id, c.workspace_id,
22522252
c.mode, c.archived, c.last_model_config_id, c.client_type,
@@ -2280,9 +2280,8 @@ WHERE cm.created_at > @created_after
22802280
GROUP BY cm.chat_id;
22812281

22822282
-- name: GetChatModelConfigsForTelemetry :many
2283-
-- Returns all model configurations for telemetry snapshot collection.
22842283
-- deleted = false guarantees ai_provider_id is non-null, so INNER JOIN is safe.
2285-
SELECT cmc.id, ap.type::text AS provider, cmc.model, cmc.context_limit, cmc.enabled, cmc.is_default
2284+
SELECT cmc.id, ap.type::text AS provider, cmc.model, cmc.context_limit, cmc.enabled, cmc.is_default, cmc.organization_id
22862285
FROM chat_model_configs cmc
22872286
JOIN ai_providers ap ON ap.id = cmc.ai_provider_id
22882287
WHERE cmc.deleted = false;

‎coderd/telemetry/telemetry.go‎

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2270,6 +2270,7 @@ func ConvertChat(dbChat database.GetChatsUpdatedAfterRow) Chat {
22702270
c := Chat{
22712271
ID: dbChat.ID,
22722272
OwnerID: dbChat.OwnerID,
2273+
OrganizationID: dbChat.OrganizationID,
22732274
CreatedAt: dbChat.CreatedAt,
22742275
UpdatedAt: dbChat.UpdatedAt,
22752276
Status: string(dbChat.Status),
@@ -2319,12 +2320,13 @@ func ConvertChatMessageSummary(dbRow database.GetChatMessageSummariesPerChatRow)
23192320
// telemetry ChatModelConfig.
23202321
func ConvertChatModelConfig(dbRow database.GetChatModelConfigsForTelemetryRow) ChatModelConfig {
23212322
return ChatModelConfig{
2322-
ID: dbRow.ID,
2323-
Provider: dbRow.Provider,
2324-
Model: dbRow.Model,
2325-
ContextLimit: dbRow.ContextLimit,
2326-
Enabled: dbRow.Enabled,
2327-
IsDefault: dbRow.IsDefault,
2323+
ID: dbRow.ID,
2324+
OrganizationID: dbRow.OrganizationID,
2325+
Provider: dbRow.Provider,
2326+
Model: dbRow.Model,
2327+
ContextLimit: dbRow.ContextLimit,
2328+
Enabled: dbRow.Enabled,
2329+
IsDefault: dbRow.IsDefault,
23282330
}
23292331
}
23302332

@@ -2577,6 +2579,7 @@ type BoundaryUsageSummary struct {
25772579
type Chat struct {
25782580
ID uuid.UUID `json:"id"`
25792581
OwnerID uuid.UUID `json:"owner_id"`
2582+
OrganizationID uuid.UUID `json:"organization_id"`
25802583
CreatedAt time.Time `json:"created_at"`
25812584
UpdatedAt time.Time `json:"updated_at"`
25822585
Status string `json:"status"`
@@ -2612,12 +2615,14 @@ type ChatMessageSummary struct {
26122615
// ChatModelConfig contains model configuration metadata for
26132616
// telemetry. Sensitive fields like API keys are excluded.
26142617
type ChatModelConfig struct {
2615-
ID uuid.UUID `json:"id"`
2616-
Provider string `json:"provider"`
2617-
Model string `json:"model"`
2618-
ContextLimit int64 `json:"context_limit"`
2619-
Enabled bool `json:"enabled"`
2620-
IsDefault bool `json:"is_default"`
2618+
ID uuid.UUID `json:"id"`
2619+
OrganizationID uuid.UUID `json:"organization_id"`
2620+
Provider string `json:"provider"`
2621+
Model string `json:"model"`
2622+
ContextLimit int64 `json:"context_limit"`
2623+
Enabled bool `json:"enabled"`
2624+
// Each organization has at most one default configuration.
2625+
IsDefault bool `json:"is_default"`
26212626
}
26222627

26232628
// ChatDiffStatusSummary contains aggregate PR counts across all

‎coderd/telemetry/telemetry_test.go‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,14 +1648,15 @@ func TestChatsTelemetry(t *testing.T) {
16481648
ContextLimit: 200000,
16491649
})
16501650

1651-
// Create a second model config to test full dump.
1651+
org2 := dbgen.Organization(t, db, database.Organization{})
16521652
modelCfg2 := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
1653-
AIProviderID: uuid.NullUUID{UUID: openaiProvider.ID, Valid: true},
1654-
Model: "gpt-4o",
1655-
DisplayName: "GPT-4o",
1653+
AIProviderID: uuid.NullUUID{UUID: openaiProvider.ID, Valid: true},
1654+
OrganizationID: org2.ID,
1655+
Model: "gpt-4o",
1656+
DisplayName: "GPT-4o",
16561657
})
16571658

1658-
// Create a soft-deleted model config — should NOT appear in telemetry.
1659+
// Soft-deleted model configurations must not appear in telemetry.
16591660
deletedCfg := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
16601661
AIProviderID: uuid.NullUUID{UUID: anthropicProvider.ID, Valid: true},
16611662
Model: "claude-deleted",
@@ -1814,9 +1815,7 @@ func TestChatsTelemetry(t *testing.T) {
18141815
ProviderResponseID: sql.NullString{String: "resp-3", Valid: true},
18151816
})
18161817

1817-
// Insert a soft-deleted message on root chat with large token values.
1818-
// This acts as "poison" — if the deleted filter is missing, totals
1819-
// will be inflated and assertions below will fail.
1818+
// Large token values expose a missing soft-delete filter in the totals.
18201819
poisonMsg := dbgen.ChatMessage(t, db, database.ChatMessage{
18211820
ChatID: rootChat.ID,
18221821
ModelConfigID: uuid.NullUUID{UUID: modelCfg.ID, Valid: true},
@@ -1854,6 +1853,7 @@ func TestChatsTelemetry(t *testing.T) {
18541853
// Root chat assertions.
18551854
assert.Equal(t, rootChat.ID, foundRoot.ID)
18561855
assert.Equal(t, user.ID, foundRoot.OwnerID)
1856+
assert.Equal(t, org.ID, foundRoot.OrganizationID)
18571857
assert.Equal(t, "running", foundRoot.Status)
18581858
assert.False(t, foundRoot.HasParent)
18591859
assert.Nil(t, foundRoot.RootChatID)
@@ -1935,6 +1935,7 @@ func TestChatsTelemetry(t *testing.T) {
19351935

19361936
cfg1, ok := configMap[modelCfg.ID]
19371937
require.True(t, ok)
1938+
assert.Equal(t, org.ID, cfg1.OrganizationID)
19381939
assert.Equal(t, "anthropic", cfg1.Provider)
19391940
assert.Equal(t, "claude-sonnet-4-20250514", cfg1.Model)
19401941
assert.Equal(t, int64(200000), cfg1.ContextLimit)
@@ -1943,6 +1944,7 @@ func TestChatsTelemetry(t *testing.T) {
19431944

19441945
cfg2, ok := configMap[modelCfg2.ID]
19451946
require.True(t, ok)
1947+
assert.Equal(t, org2.ID, cfg2.OrganizationID)
19461948
assert.Equal(t, "openai", cfg2.Provider)
19471949
assert.Equal(t, "gpt-4o", cfg2.Model)
19481950
assert.Equal(t, int64(128000), cfg2.ContextLimit)

0 commit comments

Comments
 (0)