feat(telemetry): track API credit top-up succeeded via credit_added audit events\n\n- Add TelemetryEvents.API_CREDIT_TOPUP_SUCCEEDED and provider method\n- Emit on credit_added events in useCustomerEventsService with in-memory dedupe\n- No local storage, naive based on backend audit log

(cherry picked from commit 0a50e43ae467f378f5f1c155679e818ed88b0aac)
This commit is contained in:
Benjamin Lu
2025-10-31 18:27:42 -07:00
parent 2383a38aa0
commit 9a3e387d36
3 changed files with 81 additions and 1 deletions

View File

@@ -2,6 +2,8 @@ import type { OverridedMixpanel } from 'mixpanel-browser'
import type {
AuthMetadata,
CreditTopupMetadata,
CreditTopupSucceededMetadata,
ExecutionContext,
ExecutionErrorMetadata,
ExecutionSuccessMetadata,
@@ -282,6 +284,27 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
this.trackEvent(eventName)
}
trackAddApiCreditButtonClicked(): void {
this.trackEvent(TelemetryEvents.ADD_API_CREDIT_BUTTON_CLICKED)
}
trackMonthlySubscriptionSucceeded(): void {
this.trackEvent(TelemetryEvents.MONTHLY_SUBSCRIPTION_SUCCEEDED)
}
trackApiCreditTopupButtonPurchaseClicked(amount: number): void {
const metadata: CreditTopupMetadata = {
credit_amount: amount
}
this.trackEvent(
TelemetryEvents.API_CREDIT_TOPUP_BUTTON_PURCHASE_CLICKED,
metadata
)
}
trackApiCreditTopupSucceeded(metadata: CreditTopupSucceededMetadata): void {
this.trackEvent(TelemetryEvents.API_CREDIT_TOPUP_SUCCEEDED, metadata)
}
trackRunButton(options?: { subscribe_to_run?: boolean }): void {
if (this.isOnboardingMode) {
// During onboarding, track basic run button click without workflow context

View File

@@ -89,6 +89,22 @@ export interface TemplateMetadata {
template_license?: string
}
/**
* Credit topup metadata
*/
export interface CreditTopupMetadata {
credit_amount: number
}
/**
* Credit top-up succeeded metadata
*/
export interface CreditTopupSucceededMetadata {
credit_amount: number
payment_method?: string
transaction_id?: string
}
/**
* Workflow import metadata
*/
@@ -169,6 +185,10 @@ export interface TelemetryProvider {
// Subscription flow events
trackSubscription(event: 'modal_opened' | 'subscribe_clicked'): void
trackMonthlySubscriptionSucceeded(): void
trackAddApiCreditButtonClicked(): void
trackApiCreditTopupButtonPurchaseClicked(amount: number): void
trackApiCreditTopupSucceeded(metadata: CreditTopupSucceededMetadata): void
trackRunButton(options?: { subscribe_to_run?: boolean }): void
// Survey flow events
@@ -221,6 +241,11 @@ export const TelemetryEvents = {
RUN_BUTTON_CLICKED: 'app:run_button_click',
SUBSCRIPTION_REQUIRED_MODAL_OPENED: 'app:subscription_required_modal_opened',
SUBSCRIBE_NOW_BUTTON_CLICKED: 'app:subscribe_now_button_clicked',
MONTHLY_SUBSCRIPTION_SUCCEEDED: 'app:monthly_subscription_succeeded',
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',
@@ -267,6 +292,8 @@ export type TelemetryEventProperties =
| RunButtonProperties
| ExecutionErrorMetadata
| ExecutionSuccessMetadata
| CreditTopupMetadata
| CreditTopupSucceededMetadata
| WorkflowImportMetadata
| TemplateLibraryMetadata
| TemplateLibraryClosedMetadata

View File

@@ -4,6 +4,7 @@ import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { COMFY_API_BASE_URL } from '@/config/comfyApi'
import { useTelemetry } from '@/platform/telemetry'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import type { components, operations } from '@/types/comfyRegistryTypes'
import { isAbortError } from '@/utils/typeGuardUtil'
@@ -34,6 +35,8 @@ export const useCustomerEventsService = () => {
const isLoading = ref(false)
const error = ref<string | null>(null)
const { d } = useI18n()
const telemetry = useTelemetry()
const seenCreditAddedEventIds = new Set<string>()
const handleRequestError = (
err: unknown,
@@ -179,7 +182,7 @@ export const useCustomerEventsService = () => {
return null
}
return executeRequest<CustomerEventsResponse>(
const result = await executeRequest<CustomerEventsResponse>(
() =>
customerApiClient.get('/customers/events', {
params: { page, limit },
@@ -187,6 +190,33 @@ export const useCustomerEventsService = () => {
}),
{ errorContext, routeSpecificErrors }
)
if (result?.events?.length) {
for (const evt of result.events) {
if (evt?.event_id && evt.event_type === EventType.CREDIT_ADDED) {
if (!seenCreditAddedEventIds.has(evt.event_id)) {
const amount = Number((evt as any)?.params?.amount)
if (!Number.isNaN(amount) && amount > 0) {
const creditAmountUsd = amount / 100
const paymentMethod = (evt as any)?.params?.payment_method as
| string
| undefined
const transactionId = (evt as any)?.params?.transaction_id as
| string
| undefined
telemetry?.trackApiCreditTopupSucceeded({
credit_amount: creditAmountUsd,
payment_method: paymentMethod,
transaction_id: transactionId
})
}
seenCreditAddedEventIds.add(evt.event_id)
}
}
}
}
return result
}
return {