feat(coderd): add PR status summary to telemetry snapshots (#24379)

Adds aggregate PR counts (total, open, merged, closed) from
`chat_diff_statuses` to telemetry snapshots, giving visibility into AI
agent PR outcomes across deployments.

The existing telemetry system reports `Chats`, `ChatMessageSummaries`,
and `ChatModelConfigs`, but had no PR-level data. This adds a
`ChatDiffStatusSummary` field to the `Snapshot` struct with four
all-time counts derived from a single aggregate query.

<details>
<summary>Implementation details</summary>

- New SQL query `GetChatDiffStatusSummary` counts `chat_diff_statuses`
rows with non-NULL `pull_request_state`, grouped by state
(open/merged/closed).
- `ChatDiffStatusSummary` struct added to telemetry `Snapshot`,
collected via a parallel `eg.Go()` block in `createSnapshot()`.
- `dbauthz` wrapper uses `rbac.ResourceSystem` (telemetry-only pattern).
- Test covers both empty state (zero counts) and populated state (mixed
states + NULL-state exclusion).

</details>

> 🤖 Generated by Coder Agents
This commit is contained in:
Dean Sheather
2026-04-17 21:56:11 +10:00
committed by GitHub
parent db8191277b
commit 4ba74dcdc8
9 changed files with 291 additions and 12 deletions
+8
View File
@@ -2658,6 +2658,14 @@ func (q *querier) GetChatDiffStatusByChatID(ctx context.Context, chatID uuid.UUI
return q.db.GetChatDiffStatusByChatID(ctx, chatID)
}
func (q *querier) GetChatDiffStatusSummary(ctx context.Context) (database.GetChatDiffStatusSummaryRow, error) {
// Telemetry queries are called from system contexts only.
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
return database.GetChatDiffStatusSummaryRow{}, err
}
return q.db.GetChatDiffStatusSummary(ctx)
}
func (q *querier) GetChatDiffStatusesByChatIDs(ctx context.Context, chatIDs []uuid.UUID) ([]database.ChatDiffStatus, error) {
if len(chatIDs) == 0 {
return []database.ChatDiffStatus{}, nil
+4
View File
@@ -4192,6 +4192,10 @@ func (s *MethodTestSuite) TestSystemFunctions() {
dbm.EXPECT().GetChatMessageSummariesPerChat(gomock.Any(), ts).Return([]database.GetChatMessageSummariesPerChatRow{}, nil).AnyTimes()
check.Args(ts).Asserts(rbac.ResourceSystem, policy.ActionRead)
}))
s.Run("GetChatDiffStatusSummary", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().GetChatDiffStatusSummary(gomock.Any()).Return(database.GetChatDiffStatusSummaryRow{}, nil).AnyTimes()
check.Args().Asserts(rbac.ResourceSystem, policy.ActionRead)
}))
s.Run("GetChatModelConfigsForTelemetry", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().GetChatModelConfigsForTelemetry(gomock.Any()).Return([]database.GetChatModelConfigsForTelemetryRow{}, nil).AnyTimes()
check.Args().Asserts(rbac.ResourceSystem, policy.ActionRead)