feat(telemetry): track API credit top-up success via audit events (#6500)

Summary
- Add TelemetryEvents.API_CREDIT_TOPUP_SUCCEEDED and provider method
trackApiCreditTopupSucceeded
- Introduce topupTrackerStore to persist pending top-ups per user
(localStorage) and reconcile against recent audit logs
- Hook purchase flow to start tracking before opening Stripe checkout
- Reconcile after fetching audit events (UsageLogsTable) and after
fetchBalance, then emit telemetry, refresh balance, and clear pending
- Minor refactor in customerEventsService to return awaited result

Implementation details
- Matching strategy:
  - Event type: credit_added
  - Time window: createdAt between top-up start time and +24h
  - Amount: if known, e.params.amount must equal expected cents
- Cross-tab/user changes: synchronize via storage event and userId
watcher

Limitations / Follow-up
- Reconciliation fetches only page 1 (limit 10) of events; in
high-volume cases, a recent credit_added could fall outside the first
page
- The window and pagination issue will be "resolved by a followup PR to
core and cloud"

Files touched
- src/stores/topupTrackerStore.ts (new)
- src/components/dialog/content/setting/UsageLogsTable.vue
- src/composables/auth/useFirebaseAuthActions.ts
- src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts
- src/platform/telemetry/types.ts
- src/services/customerEventsService.ts

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6500-feat-telemetry-track-API-credit-top-up-success-via-audit-events-29e6d73d365081169941efae70cf71fe)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Christian Byrne <chrbyrne96@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Benjamin Lu
2025-11-01 13:17:41 -07:00
committed by GitHub
parent de535269ee
commit 72389637ed
7 changed files with 309 additions and 3 deletions

View File

@@ -1,6 +1,11 @@
import type { OverridedMixpanel } from 'mixpanel-browser'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import {
checkForCompletedTopup as checkTopupUtil,
clearTopupTracking as clearTopupUtil,
startTopupTracking as startTopupUtil
} from '@/platform/telemetry/topupTracker'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { useWorkflowTemplatesStore } from '@/platform/workflow/templates/repositories/workflowTemplatesStore'
import { app } from '@/scripts/app'
@@ -172,6 +177,23 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
)
}
trackApiCreditTopupSucceeded(): void {
this.trackEvent(TelemetryEvents.API_CREDIT_TOPUP_SUCCEEDED)
}
// Credit top-up tracking methods (composition with utility functions)
startTopupTracking(): void {
startTopupUtil()
}
checkForCompletedTopup(events: any[] | undefined | null): boolean {
return checkTopupUtil(events)
}
clearTopupTracking(): void {
clearTopupUtil()
}
trackRunButton(options?: { subscribe_to_run?: boolean }): void {
const executionContext = this.getExecutionContext()

View File

@@ -0,0 +1,61 @@
import { useTelemetry } from '@/platform/telemetry'
import type { AuditLog } from '@/services/customerEventsService'
const STORAGE_KEY = 'pending_topup_timestamp'
const MAX_AGE_MS = 24 * 60 * 60 * 1000 // 24 hours
/**
* Start tracking a credit top-up purchase.
* Call this before opening the Stripe checkout window.
*/
export function startTopupTracking(): void {
localStorage.setItem(STORAGE_KEY, Date.now().toString())
}
/**
* Check if a pending top-up has completed by looking for a credit_added event
* that occurred after the tracking started.
*
* @param events - Array of audit log events to check
* @returns true if a completed top-up was detected and telemetry was sent
*/
export function checkForCompletedTopup(
events: AuditLog[] | undefined | null
): boolean {
const timestampStr = localStorage.getItem(STORAGE_KEY)
if (!timestampStr) return false
const timestamp = parseInt(timestampStr, 10)
// Auto-cleanup if expired (older than 24 hours)
if (Date.now() - timestamp > MAX_AGE_MS) {
localStorage.removeItem(STORAGE_KEY)
return false
}
if (!events || events.length === 0) return false
// Find credit_added event that occurred after our timestamp
const completedTopup = events.find(
(e) =>
e.event_type === 'credit_added' &&
e.createdAt &&
new Date(e.createdAt).getTime() > timestamp
)
if (completedTopup) {
useTelemetry()?.trackApiCreditTopupSucceeded()
localStorage.removeItem(STORAGE_KEY)
return true
}
return false
}
/**
* Clear any pending top-up tracking.
* Useful for testing or manual cleanup.
*/
export function clearTopupTracking(): void {
localStorage.removeItem(STORAGE_KEY)
}

View File

@@ -247,10 +247,16 @@ export interface TelemetryProvider {
trackMonthlySubscriptionSucceeded(): void
trackAddApiCreditButtonClicked(): void
trackApiCreditTopupButtonPurchaseClicked(amount: number): void
trackApiCreditTopupSucceeded(): void
trackRunButton(options?: { subscribe_to_run?: boolean }): void
trackRunTriggeredViaKeybinding(): void
trackRunTriggeredViaMenu(): void
// Credit top-up tracking (composition with internal utilities)
startTopupTracking(): void
checkForCompletedTopup(events: any[] | undefined | null): boolean
clearTopupTracking(): void
// Survey flow events
trackSurvey(stage: 'opened' | 'submitted', responses?: SurveyResponses): void
@@ -315,6 +321,7 @@ export const TelemetryEvents = {
ADD_API_CREDIT_BUTTON_CLICKED: 'app:add_api_credit_button_clicked',
API_CREDIT_TOPUP_BUTTON_PURCHASE_CLICKED:
'app:api_credit_topup_button_purchase_clicked',
API_CREDIT_TOPUP_SUCCEEDED: 'app:api_credit_topup_succeeded',
// Onboarding Survey
USER_SURVEY_OPENED: 'app:user_survey_opened',