From 9a3e387d369f6ce4a10bf8c9f0e42f8416336502 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Fri, 31 Oct 2025 18:27:42 -0700 Subject: [PATCH] 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) --- .../cloud/MixpanelTelemetryProvider.ts | 23 +++++++++++++ src/platform/telemetry/types.ts | 27 ++++++++++++++++ src/services/customerEventsService.ts | 32 ++++++++++++++++++- 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts b/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts index c06f9fdcb8..e6798b32d6 100644 --- a/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts +++ b/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts @@ -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 diff --git a/src/platform/telemetry/types.ts b/src/platform/telemetry/types.ts index ed5c28ecca..66f21537d5 100644 --- a/src/platform/telemetry/types.ts +++ b/src/platform/telemetry/types.ts @@ -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 diff --git a/src/services/customerEventsService.ts b/src/services/customerEventsService.ts index 0359f4c3af..7be2b6bbdd 100644 --- a/src/services/customerEventsService.ts +++ b/src/services/customerEventsService.ts @@ -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(null) const { d } = useI18n() + const telemetry = useTelemetry() + const seenCreditAddedEventIds = new Set() const handleRequestError = ( err: unknown, @@ -179,7 +182,7 @@ export const useCustomerEventsService = () => { return null } - return executeRequest( + const result = await executeRequest( () => 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 {