mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-17 01:07:56 +00:00
Compare commits
4 Commits
feat/parti
...
deepme987/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40534f16cd | ||
|
|
abcc2b72df | ||
|
|
e0f58ad0c5 | ||
|
|
2b4016adaf |
@@ -162,7 +162,6 @@ import { useExternalLink } from '@/composables/useExternalLink'
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { clearTopupTracking } from '@/platform/telemetry/topupTracker'
|
||||
import { useSettingsDialog } from '@/platform/settings/composables/useSettingsDialog'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { cn } from '@comfyorg/tailwind-utils'
|
||||
@@ -241,10 +240,7 @@ function handlePresetClick(amount: number) {
|
||||
selectedPreset.value = amount
|
||||
}
|
||||
|
||||
function handleClose(clearTracking = true) {
|
||||
if (clearTracking) {
|
||||
clearTopupTracking()
|
||||
}
|
||||
function handleClose() {
|
||||
dialogStore.closeDialog({ key: 'top-up-credits' })
|
||||
}
|
||||
|
||||
@@ -257,8 +253,8 @@ async function handleBuy() {
|
||||
telemetry?.trackApiCreditTopupButtonPurchaseClicked(payAmount.value)
|
||||
await authActions.purchaseCredits(payAmount.value)
|
||||
|
||||
// Close top-up dialog (keep tracking) and open credits panel to show updated balance
|
||||
handleClose(false)
|
||||
// Close top-up dialog and open credits panel to show updated balance
|
||||
handleClose()
|
||||
|
||||
// In workspace mode (personal workspace), show workspace settings panel
|
||||
// Otherwise, show legacy subscription/credits panel
|
||||
|
||||
@@ -99,7 +99,6 @@ import ProgressSpinner from 'primevue/progressspinner'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import type { AuditLog } from '@/services/customerEventsService'
|
||||
import {
|
||||
EventType,
|
||||
@@ -163,9 +162,6 @@ const loadEvents = async () => {
|
||||
if (response.totalPages) {
|
||||
pagination.value.totalPages = response.totalPages
|
||||
}
|
||||
|
||||
// Check if a pending top-up has completed
|
||||
useTelemetry()?.checkForCompletedTopup(response.events)
|
||||
} else {
|
||||
error.value = customerEventService.error.value || 'Failed to load events'
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import type { ErrorRecoveryStrategy } from '@/composables/useErrorHandling'
|
||||
import { st, t } from '@/i18n'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
@@ -134,7 +133,6 @@ export const useAuthActions = () => {
|
||||
)
|
||||
}
|
||||
|
||||
useTelemetry()?.startTopupTracking()
|
||||
window.open(response.checkout_url, '_blank')
|
||||
}, reportError)
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { AuditLog } from '@/services/customerEventsService'
|
||||
|
||||
import type {
|
||||
AuthMetadata,
|
||||
BeginCheckoutMetadata,
|
||||
@@ -108,33 +106,10 @@ export class TelemetryRegistry implements TelemetryDispatcher {
|
||||
)
|
||||
}
|
||||
|
||||
trackApiCreditTopupSucceeded(): void {
|
||||
this.dispatch((provider) => provider.trackApiCreditTopupSucceeded?.())
|
||||
}
|
||||
|
||||
trackRunButton(properties: RunButtonProperties): void {
|
||||
this.dispatch((provider) => provider.trackRunButton?.(properties))
|
||||
}
|
||||
|
||||
startTopupTracking(): void {
|
||||
this.dispatch((provider) => provider.startTopupTracking?.())
|
||||
}
|
||||
|
||||
checkForCompletedTopup(events: AuditLog[] | undefined | null): boolean {
|
||||
return this.providers.some((provider) => {
|
||||
try {
|
||||
return provider.checkForCompletedTopup?.(events) ?? false
|
||||
} catch (error) {
|
||||
console.error('[Telemetry] Provider dispatch failed', error)
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
clearTopupTracking(): void {
|
||||
this.dispatch((provider) => provider.clearTopupTracking?.())
|
||||
}
|
||||
|
||||
trackSurvey(
|
||||
stage: 'opened' | 'submitted',
|
||||
responses?: SurveyResponses
|
||||
|
||||
@@ -17,13 +17,6 @@ vi.mock('@/composables/auth/useCurrentUser', () => ({
|
||||
useCurrentUser: () => ({ onUserResolved: mockOnUserResolved })
|
||||
}))
|
||||
|
||||
const topupMocks = vi.hoisted(() => ({
|
||||
startTopupTracking: vi.fn(),
|
||||
clearTopupTracking: vi.fn(),
|
||||
checkForCompletedTopup: vi.fn().mockReturnValue(true)
|
||||
}))
|
||||
vi.mock('@/platform/telemetry/topupTracker', () => topupMocks)
|
||||
|
||||
const mockNormalizeSurveyResponses = vi.hoisted(() => vi.fn())
|
||||
vi.mock('@/platform/telemetry/utils/surveyNormalization', () => ({
|
||||
normalizeSurveyResponses: mockNormalizeSurveyResponses
|
||||
@@ -101,6 +94,7 @@ describe('MixpanelTelemetryProvider — with configured token', () => {
|
||||
await waitForMixpanelInit()
|
||||
|
||||
provider.trackUserLoggedIn()
|
||||
provider.trackAddApiCreditButtonClicked()
|
||||
|
||||
expect(mockMixpanel.init).toHaveBeenCalledWith(
|
||||
'test-token',
|
||||
@@ -110,6 +104,10 @@ describe('MixpanelTelemetryProvider — with configured token', () => {
|
||||
TelemetryEvents.USER_LOGGED_IN,
|
||||
{}
|
||||
)
|
||||
expect(mockMixpanel.track).toHaveBeenCalledWith(
|
||||
TelemetryEvents.ADD_API_CREDIT_BUTTON_CLICKED,
|
||||
{}
|
||||
)
|
||||
})
|
||||
|
||||
it('queues events fired before loaded() and flushes them once Mixpanel reports ready', async () => {
|
||||
@@ -344,11 +342,6 @@ describe('MixpanelTelemetryProvider — direct event tracking methods', () => {
|
||||
(p) => p.trackMonthlySubscriptionCancelled(),
|
||||
TelemetryEvents.MONTHLY_SUBSCRIPTION_CANCELLED
|
||||
],
|
||||
[
|
||||
'trackApiCreditTopupSucceeded',
|
||||
(p) => p.trackApiCreditTopupSucceeded(),
|
||||
TelemetryEvents.API_CREDIT_TOPUP_SUCCEEDED
|
||||
],
|
||||
[
|
||||
'trackTemplate',
|
||||
(p) => p.trackTemplate(templateMetadata),
|
||||
@@ -503,23 +496,3 @@ describe('MixpanelTelemetryProvider — direct event tracking methods', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('MixpanelTelemetryProvider — topup delegation', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
delete (window as unknown as ConfigWindow).__CONFIG__
|
||||
})
|
||||
|
||||
it('forwards topup lifecycle calls to the topupTracker utility', () => {
|
||||
const provider = new MixpanelTelemetryProvider()
|
||||
|
||||
provider.startTopupTracking()
|
||||
provider.clearTopupTracking()
|
||||
const result = provider.checkForCompletedTopup([])
|
||||
|
||||
expect(topupMocks.startTopupTracking).toHaveBeenCalled()
|
||||
expect(topupMocks.clearTopupTracking).toHaveBeenCalled()
|
||||
expect(topupMocks.checkForCompletedTopup).toHaveBeenCalledWith([])
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,12 +3,6 @@ import { omit } from 'es-toolkit'
|
||||
import { watch } from 'vue'
|
||||
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import {
|
||||
checkForCompletedTopup as checkTopupUtil,
|
||||
clearTopupTracking as clearTopupUtil,
|
||||
startTopupTracking as startTopupUtil
|
||||
} from '@/platform/telemetry/topupTracker'
|
||||
import type { AuditLog } from '@/services/customerEventsService'
|
||||
|
||||
import type {
|
||||
AuthMetadata,
|
||||
@@ -254,23 +248,6 @@ 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: AuditLog[] | undefined | null): boolean {
|
||||
return checkTopupUtil(events)
|
||||
}
|
||||
|
||||
clearTopupTracking(): void {
|
||||
clearTopupUtil()
|
||||
}
|
||||
|
||||
trackRunButton(properties: RunButtonProperties): void {
|
||||
this.trackEvent(TelemetryEvents.RUN_BUTTON_CLICKED, properties)
|
||||
}
|
||||
|
||||
@@ -366,10 +366,6 @@ export class PostHogTelemetryProvider implements TelemetryProvider {
|
||||
})
|
||||
}
|
||||
|
||||
trackApiCreditTopupSucceeded(): void {
|
||||
this.trackEvent(TelemetryEvents.API_CREDIT_TOPUP_SUCCEEDED)
|
||||
}
|
||||
|
||||
trackRunButton(properties: RunButtonProperties): void {
|
||||
this.trackEvent(TelemetryEvents.RUN_BUTTON_CLICKED, properties)
|
||||
}
|
||||
|
||||
@@ -2,13 +2,6 @@ import type {
|
||||
ComfyDesktop2TelemetryBridge,
|
||||
ComfyDesktop2TelemetryValue
|
||||
} from '@comfyorg/comfyui-desktop-bridge-types'
|
||||
import {
|
||||
checkForCompletedTopup as checkTopupUtil,
|
||||
clearTopupTracking as clearTopupUtil,
|
||||
startTopupTracking as startTopupUtil
|
||||
} from '@/platform/telemetry/topupTracker'
|
||||
import type { AuditLog } from '@/services/customerEventsService'
|
||||
|
||||
import type {
|
||||
AuthMetadata,
|
||||
BeginCheckoutMetadata,
|
||||
@@ -136,26 +129,10 @@ export class HostTelemetrySink implements TelemetryProvider {
|
||||
})
|
||||
}
|
||||
|
||||
trackApiCreditTopupSucceeded(): void {
|
||||
this.capture(TelemetryEvents.API_CREDIT_TOPUP_SUCCEEDED)
|
||||
}
|
||||
|
||||
trackRunButton(properties: RunButtonProperties): void {
|
||||
this.capture(TelemetryEvents.RUN_BUTTON_CLICKED, properties)
|
||||
}
|
||||
|
||||
startTopupTracking(): void {
|
||||
startTopupUtil()
|
||||
}
|
||||
|
||||
checkForCompletedTopup(events: AuditLog[] | undefined | null): boolean {
|
||||
return checkTopupUtil(events)
|
||||
}
|
||||
|
||||
clearTopupTracking(): void {
|
||||
clearTopupUtil()
|
||||
}
|
||||
|
||||
trackSurvey(
|
||||
stage: 'opened' | 'submitted',
|
||||
responses?: SurveyResponses
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
startTopupTracking,
|
||||
checkForCompletedTopup,
|
||||
clearTopupTracking
|
||||
} from '@/platform/telemetry/topupTracker'
|
||||
import type { AuditLog } from '@/services/customerEventsService'
|
||||
|
||||
// Mock localStorage
|
||||
const mockLocalStorage = vi.hoisted(() => ({
|
||||
getItem: vi.fn(),
|
||||
setItem: vi.fn(),
|
||||
removeItem: vi.fn()
|
||||
}))
|
||||
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: mockLocalStorage,
|
||||
writable: true
|
||||
})
|
||||
|
||||
// Mock telemetry
|
||||
const mockTelemetry = vi.hoisted(() => ({
|
||||
trackApiCreditTopupSucceeded: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/telemetry', () => ({
|
||||
useTelemetry: vi.fn(() => mockTelemetry)
|
||||
}))
|
||||
|
||||
describe('topupTracker', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('startTopupTracking', () => {
|
||||
it('should save current timestamp to localStorage', () => {
|
||||
const beforeTimestamp = Date.now()
|
||||
|
||||
startTopupTracking()
|
||||
|
||||
expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
|
||||
'pending_topup_timestamp',
|
||||
expect.any(String)
|
||||
)
|
||||
|
||||
const savedTimestamp = parseInt(
|
||||
mockLocalStorage.setItem.mock.calls[0][1],
|
||||
10
|
||||
)
|
||||
expect(savedTimestamp).toBeGreaterThanOrEqual(beforeTimestamp)
|
||||
expect(savedTimestamp).toBeLessThanOrEqual(Date.now())
|
||||
})
|
||||
})
|
||||
|
||||
describe('checkForCompletedTopup', () => {
|
||||
it('should return false if no pending topup exists', () => {
|
||||
mockLocalStorage.getItem.mockReturnValue(null)
|
||||
|
||||
const result = checkForCompletedTopup([])
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(mockTelemetry.trackApiCreditTopupSucceeded).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return false if events array is empty', () => {
|
||||
mockLocalStorage.getItem.mockReturnValue(Date.now().toString())
|
||||
|
||||
const result = checkForCompletedTopup([])
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(mockTelemetry.trackApiCreditTopupSucceeded).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return false if events array is null', () => {
|
||||
mockLocalStorage.getItem.mockReturnValue(Date.now().toString())
|
||||
|
||||
const result = checkForCompletedTopup(null)
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(mockTelemetry.trackApiCreditTopupSucceeded).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should auto-cleanup if timestamp is older than 24 hours', () => {
|
||||
const oldTimestamp = Date.now() - 25 * 60 * 60 * 1000 // 25 hours ago
|
||||
mockLocalStorage.getItem.mockReturnValue(oldTimestamp.toString())
|
||||
|
||||
const events: AuditLog[] = [
|
||||
{
|
||||
event_id: 'test-1',
|
||||
event_type: 'credit_added',
|
||||
createdAt: new Date().toISOString(),
|
||||
params: { amount: 500 }
|
||||
}
|
||||
]
|
||||
|
||||
const result = checkForCompletedTopup(events)
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(mockLocalStorage.removeItem).toHaveBeenCalledWith(
|
||||
'pending_topup_timestamp'
|
||||
)
|
||||
expect(mockTelemetry.trackApiCreditTopupSucceeded).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should detect completed topup and fire telemetry', () => {
|
||||
const startTimestamp = Date.now() - 5 * 60 * 1000 // 5 minutes ago
|
||||
mockLocalStorage.getItem.mockReturnValue(startTimestamp.toString())
|
||||
|
||||
const events: AuditLog[] = [
|
||||
{
|
||||
event_id: 'test-1',
|
||||
event_type: 'api_usage_completed',
|
||||
createdAt: new Date(startTimestamp - 1000).toISOString(),
|
||||
params: {}
|
||||
},
|
||||
{
|
||||
event_id: 'test-2',
|
||||
event_type: 'credit_added',
|
||||
createdAt: new Date(startTimestamp + 1000).toISOString(),
|
||||
params: { amount: 500 }
|
||||
}
|
||||
]
|
||||
|
||||
const result = checkForCompletedTopup(events)
|
||||
|
||||
expect(result).toBe(true)
|
||||
expect(mockTelemetry.trackApiCreditTopupSucceeded).toHaveBeenCalledOnce()
|
||||
expect(mockLocalStorage.removeItem).toHaveBeenCalledWith(
|
||||
'pending_topup_timestamp'
|
||||
)
|
||||
})
|
||||
|
||||
it('should not detect topup if credit_added event is before tracking started', () => {
|
||||
const startTimestamp = Date.now()
|
||||
mockLocalStorage.getItem.mockReturnValue(startTimestamp.toString())
|
||||
|
||||
const events: AuditLog[] = [
|
||||
{
|
||||
event_id: 'test-1',
|
||||
event_type: 'credit_added',
|
||||
createdAt: new Date(startTimestamp - 1000).toISOString(), // Before tracking
|
||||
params: { amount: 500 }
|
||||
}
|
||||
]
|
||||
|
||||
const result = checkForCompletedTopup(events)
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(mockTelemetry.trackApiCreditTopupSucceeded).not.toHaveBeenCalled()
|
||||
expect(mockLocalStorage.removeItem).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should ignore events without createdAt timestamp', () => {
|
||||
const startTimestamp = Date.now()
|
||||
mockLocalStorage.getItem.mockReturnValue(startTimestamp.toString())
|
||||
|
||||
const events: AuditLog[] = [
|
||||
{
|
||||
event_id: 'test-1',
|
||||
event_type: 'credit_added',
|
||||
createdAt: undefined,
|
||||
params: { amount: 500 }
|
||||
}
|
||||
]
|
||||
|
||||
const result = checkForCompletedTopup(events)
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(mockTelemetry.trackApiCreditTopupSucceeded).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should only match credit_added events, not other event types', () => {
|
||||
const startTimestamp = Date.now()
|
||||
mockLocalStorage.getItem.mockReturnValue(startTimestamp.toString())
|
||||
|
||||
const events: AuditLog[] = [
|
||||
{
|
||||
event_id: 'test-1',
|
||||
event_type: 'api_usage_completed',
|
||||
createdAt: new Date(startTimestamp + 1000).toISOString(),
|
||||
params: {}
|
||||
},
|
||||
{
|
||||
event_id: 'test-2',
|
||||
event_type: 'account_created',
|
||||
createdAt: new Date(startTimestamp + 2000).toISOString(),
|
||||
params: {}
|
||||
}
|
||||
]
|
||||
|
||||
const result = checkForCompletedTopup(events)
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(mockTelemetry.trackApiCreditTopupSucceeded).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('clearTopupTracking', () => {
|
||||
it('should remove pending topup from localStorage', () => {
|
||||
clearTopupTracking()
|
||||
|
||||
expect(mockLocalStorage.removeItem).toHaveBeenCalledWith(
|
||||
'pending_topup_timestamp'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,61 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
import type { SubscriptionDialogReason } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
|
||||
import type { TierKey } from '@/platform/cloud/subscription/constants/tierPricing'
|
||||
import type { BillingCycle } from '@/platform/cloud/subscription/utils/subscriptionTierRank'
|
||||
import type { AuditLog } from '@/services/customerEventsService'
|
||||
import type { AppMode } from '@/utils/appMode'
|
||||
|
||||
/**
|
||||
@@ -463,6 +462,9 @@ export interface SubscriptionSuccessMetadata extends Record<string, unknown> {
|
||||
ecommerce: EcommerceMetadata
|
||||
}
|
||||
|
||||
// Top-up success/failure analytics come from the backend
|
||||
// (billing:topup_completed / billing:topup_failed), not the client.
|
||||
|
||||
/**
|
||||
* Telemetry provider interface for individual providers.
|
||||
* All methods are optional - providers only implement what they need.
|
||||
@@ -485,14 +487,8 @@ export interface TelemetryProvider {
|
||||
trackMonthlySubscriptionCancelled?(): void
|
||||
trackAddApiCreditButtonClicked?(): void
|
||||
trackApiCreditTopupButtonPurchaseClicked?(amount: number): void
|
||||
trackApiCreditTopupSucceeded?(): void
|
||||
trackRunButton?(properties: RunButtonProperties): void
|
||||
|
||||
// Credit top-up tracking (composition with internal utilities)
|
||||
startTopupTracking?(): void
|
||||
checkForCompletedTopup?(events: AuditLog[] | undefined | null): boolean
|
||||
clearTopupTracking?(): void
|
||||
|
||||
// Survey flow events
|
||||
trackSurvey?(stage: 'opened' | 'submitted', responses?: SurveyResponses): void
|
||||
|
||||
@@ -588,7 +584,6 @@ 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',
|
||||
BEGIN_CHECKOUT: 'begin_checkout',
|
||||
|
||||
// Onboarding Survey
|
||||
|
||||
@@ -37,14 +37,15 @@ vi.mock('@/stores/dialogStore', () => ({
|
||||
useDialogStore: () => ({ closeDialog: vi.fn() })
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/telemetry', () => ({
|
||||
useTelemetry: () => ({
|
||||
trackApiCreditTopupButtonPurchaseClicked: vi.fn()
|
||||
})
|
||||
const { mockTrackApiCreditTopupButtonPurchaseClicked } = vi.hoisted(() => ({
|
||||
mockTrackApiCreditTopupButtonPurchaseClicked: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/telemetry/topupTracker', () => ({
|
||||
clearTopupTracking: vi.fn()
|
||||
vi.mock('@/platform/telemetry', () => ({
|
||||
useTelemetry: () => ({
|
||||
trackApiCreditTopupButtonPurchaseClicked:
|
||||
mockTrackApiCreditTopupButtonPurchaseClicked
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useExternalLink', () => ({
|
||||
|
||||
@@ -160,7 +160,6 @@ import FormattedNumberStepper from '@/components/ui/stepper/FormattedNumberStepp
|
||||
import { useBillingContext } from '@/composables/billing/useBillingContext'
|
||||
import { useExternalLink } from '@/composables/useExternalLink'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { clearTopupTracking } from '@/platform/telemetry/topupTracker'
|
||||
import { useSettingsDialog } from '@/platform/settings/composables/useSettingsDialog'
|
||||
import { useBillingOperationStore } from '@/platform/workspace/stores/billingOperationStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
@@ -241,10 +240,7 @@ function handlePresetClick(amount: number) {
|
||||
selectedPreset.value = amount
|
||||
}
|
||||
|
||||
function handleClose(clearTracking = true) {
|
||||
if (clearTracking) {
|
||||
clearTopupTracking()
|
||||
}
|
||||
function handleClose() {
|
||||
dialogStore.closeDialog({ key: 'top-up-credits' })
|
||||
}
|
||||
|
||||
@@ -266,7 +262,7 @@ async function handleBuy() {
|
||||
life: 5000
|
||||
})
|
||||
await Promise.all([fetchBalance(), fetchStatus()])
|
||||
handleClose(false)
|
||||
handleClose()
|
||||
settingsDialog.show('workspace')
|
||||
} else if (response.status === 'pending') {
|
||||
billingOperationStore.startOperation(response.billing_op_id, 'topup')
|
||||
|
||||
@@ -214,21 +214,6 @@ describe('billingOperationStore', () => {
|
||||
expect(mockTrackMonthlySubscriptionSucceeded).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('does not fire purchase telemetry on topup success', async () => {
|
||||
vi.mocked(workspaceApi.getBillingOpStatus).mockResolvedValue({
|
||||
id: 'op-1',
|
||||
status: 'succeeded',
|
||||
started_at: new Date().toISOString()
|
||||
})
|
||||
|
||||
const store = useBillingOperationStore()
|
||||
void store.startOperation('op-1', 'topup')
|
||||
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
|
||||
expect(mockTrackMonthlySubscriptionSucceeded).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('shows topup success message for topup operations', async () => {
|
||||
vi.mocked(workspaceApi.getBillingOpStatus).mockResolvedValue({
|
||||
id: 'op-1',
|
||||
@@ -311,6 +296,7 @@ describe('billingOperationStore', () => {
|
||||
detail: undefined
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('polling timeout', () => {
|
||||
|
||||
Reference in New Issue
Block a user