feat: wire up Template Builder session telemetry endpoint - #27124
Conversation
Docs previewCheck off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here. |
Add POST /api/v2/templatebuilder/sessions to report template builder session events (wizard_entry, compose_completion) for telemetry. Uses direct inline reporting via api.Telemetry.Report() matching the pattern used by NetworkEvents and UserTailnetConnections. RBAC: requires policy.ActionCreate on ResourceTemplate (same as compose). No database table or migration needed; events are reported as partial telemetry snapshots.
Wire up the frontend to call POST /api/v2/templatebuilder/sessions: - wizard_entry: fired once when the builder page becomes ready - compose_completion: fired on create success or failure, includes base_template_id, module_ids, duration_seconds, and success flag Duration is tracked via Date.now() stored in the wizard state at initialization, then computed as elapsed seconds on create submission.
Add a client-supplied session_id (UUID) that correlates wizard_entry and compose_completion events for the same wizard visit. This enables precise funnel analysis and abandonment detection in BigQuery without relying on timestamp proximity heuristics. The session ID is generated once per page mount via crypto.randomUUID() and threaded through the wizard state. The backend falls back to uuid.New() if the client omits it.
…requests Reject requests with a missing session_id as 400 Bad Request via the validate:"required" struct tag instead of silently fabricating a UUID server-side. A missing session ID is a client bug.
Regenerate docs/reference/api/templatebuilder.md so the code sample fence matches generator output (sh, not shell), fixing the gen and offlinedocs unstaged-changes checks. Drop the unused export on WizardInit to satisfy the frontend knip lint check. Co-authored-by: Coder Agent <agent@coder.com>
ea6edd3 to
1b06396
Compare
Drop the reportSession alias in favor of calling sessionMutation.mutate directly, and extract the duplicated compose_completion payload into a reportCompletion helper.
…elpers Extract reportEntry and reportCompletion helpers to dedupe the session telemetry payloads, replacing the reportSession alias and the repeated compose_completion mutate calls.
…lpers together Move reportEntry and reportCompletion to component scope as useCallback hooks so both telemetry payloads are defined side by side. reportCompletion takes state and duration as parameters since state is only available inside handleCreate.
| BaseTemplateID string `json:"base_template_id,omitempty"` | ||
| ModuleIDs []string `json:"module_ids,omitempty"` | ||
| DurationSeconds float64 `json:"duration_seconds,omitempty"` | ||
| Success bool `json:"success,omitempty"` |
There was a problem hiding this comment.
thinking out loud here - these events events are aggregated outside of the Coder deployement - I think you mentioned BigQuery, right? So if these fields are missing, would BigQuery be able to do searches for ... WHERE success = false ?
There was a problem hiding this comment.
yep it's bigquery on the other side, and the struct we use in the telemetry server has Success bool, so if Success is omitted from this payload, it'll default to false before hitting bigquery.
this is a little confusing in the case of event_type = "wizard_entry" because it's meaningless there, but we can always AND event_type = "compose_completion"
TemplateBuilderSessiontelemetry types and telemetry-server ingestion were added in earlier PRs (#25082, coder/coder-telemetry-server#41), but no code ever produced session events. This adds the missing producer.Backend:
POST /api/v2/templatebuilder/sessionsreports wizard entry and compose completion events directly viaapi.Telemetry.Report(), using the same inline pattern asNetworkEventsandUserTailnetConnections. No database migration orcreateSnapshot()changes needed. RBAC requirespolicy.ActionCreateonResourceTemplate.AnyOrganization(), matching the compose endpoint.Frontend: The template builder wizard fires
wizard_entryon page mount andcompose_completionon create success or failure. A client-generated session ID (UUID) correlates the two events for the same wizard visit, enabling precise funnel analysis and abandonment detection in BigQuery. Duration is tracked viaDate.now()in the wizard state.Closes https://linear.app/codercom/issue/DEVEX-599
Implementation plan
Root Cause Analysis
The DEVEX-599 ticket diagnosis suggested missing DB tables, queries, and
eg.Goblocks. That diagnosis assumes the DB-backed periodic snapshot path is required. It is not. Investigation shows two telemetry reporting patterns in the codebase:createSnapshot()witheg.Goblocks): Used for durable entities like workspaces, templates, users.api.Telemetry.Report(&telemetry.Snapshot{...})): Used for ephemeral events likeNetworkEvents,UserTailnetConnections,CLIInvocations.Template builder sessions are ephemeral events, so the direct inline reporting pattern is the correct fit.
Backend Changes
codersdk/templatebuilder.go:TemplateBuilderSessionRequesttype withSessionID,EventTypeenum,TemplateBuilderSession()client methodcoderd/coderd.go: Route registration in/templatebuildergroupcoderd/templatebuilder_handler.go: Handler with RBAC check, request validation, session ID fallback, and inline telemetry reportcoderd/templatebuilder_handler_test.go: Tests for wizard entry, compose completion, invalid event type, disabled feature, and member RBAC rejectionFrontend Changes
site/src/api/api.ts:recordTemplateBuilderSessionAPI methodsite/src/api/queries/templateBuilder.ts: React Query mutationsite/src/pages/TemplateBuilder/wizardState.ts:sessionIdandenteredAtfields,createWizardState()factory for per-mount initializationsite/src/pages/TemplateBuilder/TemplateBuilderPageView.tsx:sessionIdprop,useReducerinitializer formsite/src/pages/TemplateBuilder/TemplateBuilderPage.tsx: Telemetry calls for wizard entry (on mount) and compose completion (on create success/failure)