feature: telemetry adapter abstraction (TelemetryPort + NoopAdapter + factory)#77
Merged
feature: telemetry adapter abstraction (TelemetryPort + NoopAdapter + factory)#77
Conversation
…r, and factory Introduce provider-agnostic telemetry pattern matching the existing StoragePort/StorageClientFactory architecture. UsageTelemetryService now delegates to an injected TELEMETRY_CLIENT token instead of owning HTTP logic directly. Factory reads config from NestJS ConfigService and returns NoopAdapter when telemetry is disabled or misconfigured. Closes #71
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
Member
|
@cursor review |
feature: implement PosthogTelemetryClientAdapter with posthog-node Thin wrapper around posthog-node: capture(), identify(), and shutdown() delegate directly to the PostHog client. Remove stub warning from factory since adapter is now real. Closes #73
* feature: implement HttpTelemetryClientAdapter with fetch transport Replace stub with real HTTP adapter: fire-and-forget POST to telemetry URL with 5s timeout, silent error handling. identify() and shutdown() are no-ops. Closes #72 * fix: track in-flight requests, abort on shutdown, improve error test - capture() tracks pending AbortControllers, clears on completion - shutdown() aborts all in-flight requests - Error swallowing test now drains microtask queue for proper assertion * fix: clear timers directly on shutdown to prevent delayed process exit * chore: allow underscore-prefixed unused vars in eslint config Add argsIgnorePattern and varsIgnorePattern for _ prefix to @typescript-eslint/no-unused-vars. Fixes lint errors on interface implementations with intentionally unused parameters.
…ation (#84) * feature: add GET /telemetry/config endpoint for frontend runtime configuration Returns instanceId, telemetryEnabled, provider, and optional posthog fields. Frontend uses this at runtime to initialize the correct telemetry client without build-time env vars. Closes #74 * fix: handle both boolean and string 'false' for BETTERDB_TELEMETRY in config endpoint * refactor: improve readability and type clarity in TelemetryController * feature: extract telemetry event types to shared, add DTO with class-validator Move frontend/backend telemetry event constants and types to @betterdb/shared for frontend type safety. Add TelemetryEventDto with class-validator @isin and @isObject for NestJS validation. Refactor controller to use DTO, switch statement, and private handlers. * fix: remove posthog API key and host from telemetry config endpoint * fix: add default case to event switch to throw on unhandled event types * feature: frontend TelemetryConfigProvider + ApiTelemetryClient + hook refactor (#85) * feature: frontend TelemetryConfigProvider, ApiTelemetryClient, and hook refactor Add TelemetryClient interface, NoopTelemetryClient, ApiTelemetryClient. TelemetryConfigProvider fetches GET /telemetry/config on mount, selects the correct client, and exposes it via useTelemetry() context hook. Falls back to ApiTelemetryClient on config fetch failure. Refactor useNavigationTracker and useIdleTracker to use useTelemetry() instead of direct fetchApi calls. useConnection telemetry left as-is since it creates context at a level above the provider. Closes #75 * fix: remove nonexistent 'api' provider case, use 'http' consistently * chore: move useTelemetry hook to hooks/ directory * chore: replace TelemetryConfigProvider with singleton useTelemetry hook Remove the context provider — config loading now lives in useTelemetry hook with a module-level singleton. Config is fetched once, cached, and shared across all consumers. Hook returns { client, ready }. No provider wrapping needed in App. * chore: simplify useTelemetry to async function with module-level promise * feature: block app render until telemetry client is ready in ServerStartupGuard * chore: use TanStack Query for telemetry config fetching in useTelemetry * chore: use 30min stale time and default retry for telemetry config query * feature: frontend PosthogTelemetryClient with posthog-js (#88) * feature: frontend PosthogTelemetryClient with posthog-js Thin wrapper around posthog-js: capture() maps page_view to native $pageview, identify() and shutdown() delegate directly. Wired into useTelemetry hook — activated when backend returns provider=posthog and VITE_POSTHOG_API_KEY is set at build time. Falls back to ApiTelemetryClient when key is missing. Closes #76 * chore: add telemetry env vars to .env.example * chore: add frontend telemetry env vars to .env.example * fix: set Vite envDir to monorepo root so .env vars are loaded * refactor: update PostHog client to use instance-based API, adjust env vars and tests Switch from the global `posthog` instance to an instance-based approach with `PostHog`. Updated env vars for clarity (`VITE_POSTHOG_*` to `VITE_PUBLIC_POSTHOG_*`). Refactored `useTelemetry` to manage lifecycle via `useEffect` and updated tests to mock the new client structure. * fix: store posthog.init() instance, use || for empty string host fallback - Use returned PostHog instance from init() instead of global - Use || instead of ?? so empty string host falls back to default - Remove debug console.log * fix: use module-level singleton for telemetry client to prevent per-component reset * chore: remove unused backend telemetry type exports from shared package * chore: remove NoopTelemetryClient from frontend, use ApiTelemetryClient as fallback * fix: call identify with instanceId when creating PostHog frontend client
…#90) * feature: route LicenseService heartbeat through TelemetryPort adapter (#79) Inject TELEMETRY_CLIENT into LicenseService as @optional() and delegate heartbeat telemetry_ping events through the adapter via capture() instead of direct HTTP. Version info continues via validateLicense() calls to the entitlement URL. sendStartupError() remains unchanged. * fix: make TelemetryModule @global() so TELEMETRY_CLIENT is injectable across modules
# Conflicts: # apps/web/package.json # pnpm-lock.yaml
- Update default PostHog host to EU endpoint. - Wrap `identify` calls in try/catch to prevent crashes. - Support multiple false-like values for `BETTERDB_TELEMETRY` config. - Enhance tests to cover extended false-like value handling.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
|
|
||
| for (const [placeholder, value] of Object.entries(replacements)) { | ||
| if (value && source.includes(placeholder)) { | ||
| source = source.replace(placeholder, value); |
There was a problem hiding this comment.
Script replaces only first placeholder occurrence
Low Severity
source.replace(placeholder, value) only replaces the first occurrence of each placeholder token. If the compiled output ever contains the same placeholder more than once (e.g., due to bundler inlining or source-map references), subsequent occurrences will remain as literal __BETTERDB_POSTHOG_API_KEY__ strings at runtime, causing the factory's startsWith('__') guard to treat the key as unset. Using replaceAll (or a global regex) would be more robust.
KIvanow
approved these changes
Apr 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
TelemetryPortinterface withcapture(),identify(),shutdown()— matching theStoragePortpatternNoopTelemetryAdapterfor opt-out and misconfiguration fallbackTelemetryClientFactoryreadsTELEMETRY_PROVIDERandBETTERDB_TELEMETRYfrom NestJSConfigServiceUsageTelemetryServiceto injectTELEMETRY_CLIENTtoken instead of owning HTTP logicTelemetryModulewires factory viauseFactoryprovider, callsshutdown()on destroyTELEMETRY_PROVIDER,POSTHOG_API_KEY,POSTHOG_HOSTto env schemaBETTERDB_TELEMETRY=false→ factory returnsNoopAdapterregardless of providerNoopAdapter(never crashes)Test plan
NoopTelemetryAdapter(3 tests)TelemetryClientFactory(4 tests — default, noop, opt-out, missing key)pnpm buildpassesCloses #71
Note
Medium Risk
Adds new telemetry plumbing across backend and frontend, including provider selection and build/runtime env injection, which could affect app startup behavior and outbound network calls if misconfigured. Safeguards and fallbacks reduce failure risk but this still touches core initialization paths.
Overview
Introduces a telemetry adapter abstraction end-to-end. The API now defines a
TelemetryPortand wires a globalTELEMETRY_CLIENTviaTelemetryClientFactory, supportingposthog, legacyhttp, andnoopproviders with opt-out viaBETTERDB_TELEMETRY.Backend telemetry is refactored to use the injected client.
UsageTelemetryServiceno longer posts directly; it identifies/captures through the adapter and the module shuts the client down on destroy.LicenseServiceheartbeat telemetry is rerouted through the same adapter, while version info is now sourced from entitlement validation.Frontend telemetry is added with PostHog support. A new
useTelemetryhook fetchesGET /telemetry/configand selects either an API-backed client or a PostHog JS client; existing trackers now callclient.capture(), and startup gating waits for telemetry config readiness.Config/build updates. Adds telemetry env vars to
.env.exampleand API env schema, bakes frontend PostHog vars via Docker build args, injects backend PostHog defaults post-build, and addsposthog-node/posthog-jsdependencies plus comprehensive unit/integration tests.Written by Cursor Bugbot for commit 7be67a4. This will update automatically on new commits. Configure here.