Files
ComfyUI_frontend/src/platform/telemetry/TelemetryRegistry.ts
Robin Huang fd7ce3a852 feat: add telemetry for workflow save and default view (#9734)
Add two new telemetry events: `app:workflow_saved` (with `is_app` and
`is_new` metadata) and `app:default_view_set` for App Builder (with the
chosen view mode). Instrumented in workflowService and
useAppSetDefaultView.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9734-feat-add-telemetry-for-workflow-save-and-default-view-3206d73d3650814e8678f83c8419625f)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 09:31:21 -07:00

240 lines
7.3 KiB
TypeScript

import type { AuditLog } from '@/services/customerEventsService'
import type {
AuthMetadata,
BeginCheckoutMetadata,
DefaultViewSetMetadata,
EnterLinearMetadata,
ShareFlowMetadata,
ExecutionErrorMetadata,
ExecutionSuccessMetadata,
ExecutionTriggerSource,
HelpCenterClosedMetadata,
HelpCenterOpenedMetadata,
HelpResourceClickedMetadata,
NodeSearchMetadata,
NodeSearchResultMetadata,
PageViewMetadata,
PageVisibilityMetadata,
SettingChangedMetadata,
SubscriptionMetadata,
SurveyResponses,
TabCountMetadata,
TelemetryDispatcher,
TelemetryProvider,
TemplateFilterMetadata,
TemplateLibraryClosedMetadata,
TemplateLibraryMetadata,
TemplateMetadata,
UiButtonClickMetadata,
WorkflowCreatedMetadata,
WorkflowImportMetadata,
WorkflowSavedMetadata
} from './types'
/**
* Registry that holds multiple telemetry providers and dispatches
* all tracking calls to each registered provider.
*
* Implements TelemetryDispatcher (all methods required) while dispatching
* to TelemetryProvider instances using optional chaining since providers
* only implement the methods they care about.
*/
export class TelemetryRegistry implements TelemetryDispatcher {
private providers: TelemetryProvider[] = []
registerProvider(provider: TelemetryProvider): void {
this.providers.push(provider)
}
private dispatch(action: (provider: TelemetryProvider) => void): void {
this.providers.forEach((provider) => {
try {
action(provider)
} catch (error) {
console.error('[Telemetry] Provider dispatch failed', error)
}
})
}
trackSignupOpened(): void {
this.dispatch((provider) => provider.trackSignupOpened?.())
}
trackAuth(metadata: AuthMetadata): void {
this.dispatch((provider) => provider.trackAuth?.(metadata))
}
trackUserLoggedIn(): void {
this.dispatch((provider) => provider.trackUserLoggedIn?.())
}
trackSubscription(
event: 'modal_opened' | 'subscribe_clicked',
metadata?: SubscriptionMetadata
): void {
this.dispatch((provider) => provider.trackSubscription?.(event, metadata))
}
trackBeginCheckout(metadata: BeginCheckoutMetadata): void {
this.dispatch((provider) => provider.trackBeginCheckout?.(metadata))
}
trackMonthlySubscriptionSucceeded(): void {
this.dispatch((provider) => provider.trackMonthlySubscriptionSucceeded?.())
}
trackMonthlySubscriptionCancelled(): void {
this.dispatch((provider) => provider.trackMonthlySubscriptionCancelled?.())
}
trackAddApiCreditButtonClicked(): void {
this.dispatch((provider) => provider.trackAddApiCreditButtonClicked?.())
}
trackApiCreditTopupButtonPurchaseClicked(amount: number): void {
this.dispatch((provider) =>
provider.trackApiCreditTopupButtonPurchaseClicked?.(amount)
)
}
trackApiCreditTopupSucceeded(): void {
this.dispatch((provider) => provider.trackApiCreditTopupSucceeded?.())
}
trackRunButton(options?: {
subscribe_to_run?: boolean
trigger_source?: ExecutionTriggerSource
}): void {
this.dispatch((provider) => provider.trackRunButton?.(options))
}
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
): void {
this.dispatch((provider) => provider.trackSurvey?.(stage, responses))
}
trackEmailVerification(stage: 'opened' | 'requested' | 'completed'): void {
this.dispatch((provider) => provider.trackEmailVerification?.(stage))
}
trackTemplate(metadata: TemplateMetadata): void {
this.dispatch((provider) => provider.trackTemplate?.(metadata))
}
trackTemplateLibraryOpened(metadata: TemplateLibraryMetadata): void {
this.dispatch((provider) => provider.trackTemplateLibraryOpened?.(metadata))
}
trackTemplateLibraryClosed(metadata: TemplateLibraryClosedMetadata): void {
this.dispatch((provider) => provider.trackTemplateLibraryClosed?.(metadata))
}
trackWorkflowImported(metadata: WorkflowImportMetadata): void {
this.dispatch((provider) => provider.trackWorkflowImported?.(metadata))
}
trackWorkflowOpened(metadata: WorkflowImportMetadata): void {
this.dispatch((provider) => provider.trackWorkflowOpened?.(metadata))
}
trackWorkflowSaved(metadata: WorkflowSavedMetadata): void {
this.dispatch((provider) => provider.trackWorkflowSaved?.(metadata))
}
trackDefaultViewSet(metadata: DefaultViewSetMetadata): void {
this.dispatch((provider) => provider.trackDefaultViewSet?.(metadata))
}
trackEnterLinear(metadata: EnterLinearMetadata): void {
this.dispatch((provider) => provider.trackEnterLinear?.(metadata))
}
trackShareFlow(metadata: ShareFlowMetadata): void {
this.dispatch((provider) => provider.trackShareFlow?.(metadata))
}
trackPageVisibilityChanged(metadata: PageVisibilityMetadata): void {
this.dispatch((provider) => provider.trackPageVisibilityChanged?.(metadata))
}
trackTabCount(metadata: TabCountMetadata): void {
this.dispatch((provider) => provider.trackTabCount?.(metadata))
}
trackNodeSearch(metadata: NodeSearchMetadata): void {
this.dispatch((provider) => provider.trackNodeSearch?.(metadata))
}
trackNodeSearchResultSelected(metadata: NodeSearchResultMetadata): void {
this.dispatch((provider) =>
provider.trackNodeSearchResultSelected?.(metadata)
)
}
trackTemplateFilterChanged(metadata: TemplateFilterMetadata): void {
this.dispatch((provider) => provider.trackTemplateFilterChanged?.(metadata))
}
trackHelpCenterOpened(metadata: HelpCenterOpenedMetadata): void {
this.dispatch((provider) => provider.trackHelpCenterOpened?.(metadata))
}
trackHelpResourceClicked(metadata: HelpResourceClickedMetadata): void {
this.dispatch((provider) => provider.trackHelpResourceClicked?.(metadata))
}
trackHelpCenterClosed(metadata: HelpCenterClosedMetadata): void {
this.dispatch((provider) => provider.trackHelpCenterClosed?.(metadata))
}
trackWorkflowCreated(metadata: WorkflowCreatedMetadata): void {
this.dispatch((provider) => provider.trackWorkflowCreated?.(metadata))
}
trackWorkflowExecution(): void {
this.dispatch((provider) => provider.trackWorkflowExecution?.())
}
trackExecutionError(metadata: ExecutionErrorMetadata): void {
this.dispatch((provider) => provider.trackExecutionError?.(metadata))
}
trackExecutionSuccess(metadata: ExecutionSuccessMetadata): void {
this.dispatch((provider) => provider.trackExecutionSuccess?.(metadata))
}
trackSettingChanged(metadata: SettingChangedMetadata): void {
this.dispatch((provider) => provider.trackSettingChanged?.(metadata))
}
trackUiButtonClicked(metadata: UiButtonClickMetadata): void {
this.dispatch((provider) => provider.trackUiButtonClicked?.(metadata))
}
trackPageView(pageName: string, properties?: PageViewMetadata): void {
this.dispatch((provider) => provider.trackPageView?.(pageName, properties))
}
}