Backport: telemetry API credit + node metrics; fix onboarding payload typing (#6492)

This backport adds the new telemetry:

- Subscription/credit events:
  - MONTHLY_SUBSCRIPTION_SUCCEEDED
  - ADD_API_CREDIT_BUTTON_CLICKED
  - API_CREDIT_TOPUP_BUTTON_PURCHASE_CLICKED(amount)

- Run/Execution context now includes node composition metrics:
  - total_node_count, subgraph_count, has_api_nodes, api_node_names
  - ExecutionContext also includes custom_node_count and api_node_count

Fixes type errors during onboarding by including required fields in
minimal payloads for:
- RUN_BUTTON_CLICKED (uses zeroed node metrics)
- EXECUTION_START (uses zeroed node metrics)

Implementation notes:
- Node metrics computed via collectAllNodes + nodeDefStore; safe
defaults on failure.
- Onboarding minimal payloads include zeroed metrics to satisfy new
typings.

This is a manual backport of
https://github.com/Comfy-Org/ComfyUI_frontend/pull/6468

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6492-Backport-telemetry-API-credit-node-metrics-fix-onboarding-payload-typing-29d6d73d365081a58d96c47fc069daa6)
by [Unito](https://www.unito.io)
This commit is contained in:
Benjamin Lu
2025-11-01 02:40:34 -07:00
committed by GitHub
parent 4412ae4bff
commit 52534a562c
2 changed files with 135 additions and 8 deletions

View File

@@ -1,7 +1,13 @@
import type { OverridedMixpanel } from 'mixpanel-browser'
import { app } from '@/scripts/app'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { NodeSourceType } from '@/types/nodeSource'
import { collectAllNodes } from '@/utils/graphTraversalUtil'
import type {
AuthMetadata,
CreditTopupMetadata,
ExecutionContext,
ExecutionErrorMetadata,
ExecutionSuccessMetadata,
@@ -282,13 +288,36 @@ 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
)
}
trackRunButton(options?: { subscribe_to_run?: boolean }): void {
if (this.isOnboardingMode) {
// During onboarding, track basic run button click without workflow context
this.trackEvent(TelemetryEvents.RUN_BUTTON_CLICKED, {
subscribe_to_run: options?.subscribe_to_run || false,
workflow_type: 'custom',
workflow_name: 'untitled'
workflow_name: 'untitled',
custom_node_count: 0,
total_node_count: 0,
subgraph_count: 0,
has_api_nodes: false,
api_node_names: []
})
return
}
@@ -298,7 +327,12 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
const runButtonProperties: RunButtonProperties = {
subscribe_to_run: options?.subscribe_to_run || false,
workflow_type: executionContext.is_template ? 'template' : 'custom',
workflow_name: executionContext.workflow_name ?? 'untitled'
workflow_name: executionContext.workflow_name ?? 'untitled',
custom_node_count: executionContext.custom_node_count,
total_node_count: executionContext.total_node_count,
subgraph_count: executionContext.subgraph_count,
has_api_nodes: executionContext.has_api_nodes,
api_node_names: executionContext.api_node_names
}
this.trackEvent(TelemetryEvents.RUN_BUTTON_CLICKED, runButtonProperties)
@@ -400,7 +434,13 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
// During onboarding, track basic execution without workflow context
this.trackEvent(TelemetryEvents.EXECUTION_START, {
is_template: false,
workflow_name: undefined
workflow_name: undefined,
custom_node_count: 0,
api_node_count: 0,
subgraph_count: 0,
total_node_count: 0,
has_api_nodes: false,
api_node_names: []
})
return
}
@@ -423,6 +463,61 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
void this.initializeComposables()
}
let nodeCounts: {
custom_node_count: number
api_node_count: number
subgraph_count: number
total_node_count: number
has_api_nodes: boolean
api_node_names: string[]
}
try {
const nodeDefStore = useNodeDefStore()
const nodes = collectAllNodes(app.graph)
let customNodeCount = 0
let apiNodeCount = 0
let subgraphCount = 0
let totalNodeCount = 0
let hasApiNodes = false
const apiNodeNames = new Set<string>()
for (const node of nodes) {
totalNodeCount += 1
const nodeDef = nodeDefStore.nodeDefsByName[node.type ?? '']
const isCustomNode =
nodeDef?.nodeSource?.type === NodeSourceType.CustomNodes
const isApiNode = nodeDef?.api_node === true
const isSubgraph = node.isSubgraphNode?.() === true
if (isCustomNode) customNodeCount += 1
if (isApiNode) {
apiNodeCount += 1
hasApiNodes = true
if (nodeDef?.name) apiNodeNames.add(nodeDef.name)
}
if (isSubgraph) subgraphCount += 1
}
nodeCounts = {
custom_node_count: customNodeCount,
api_node_count: apiNodeCount,
subgraph_count: subgraphCount,
total_node_count: totalNodeCount,
has_api_nodes: hasApiNodes,
api_node_names: Array.from(apiNodeNames)
}
} catch (error) {
console.error('Failed to compute node metrics:', error)
nodeCounts = {
custom_node_count: 0,
api_node_count: 0,
subgraph_count: 0,
total_node_count: 0,
has_api_nodes: false,
api_node_names: []
}
}
if (
!this._composablesReady ||
!this._workflowStore ||
@@ -430,7 +525,8 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
) {
return {
is_template: false,
workflow_name: undefined
workflow_name: undefined,
...nodeCounts
}
}
@@ -459,25 +555,29 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
template_tags: englishMetadata?.tags ?? template?.tags,
template_models: englishMetadata?.models ?? template?.models,
template_use_case: englishMetadata?.useCase ?? template?.useCase,
template_license: englishMetadata?.license ?? template?.license
template_license: englishMetadata?.license ?? template?.license,
...nodeCounts
}
}
return {
is_template: false,
workflow_name: activeWorkflow.filename
workflow_name: activeWorkflow.filename,
...nodeCounts
}
}
return {
is_template: false,
workflow_name: undefined
workflow_name: undefined,
...nodeCounts
}
} catch (error) {
console.error('Failed to get execution context:', error)
return {
is_template: false,
workflow_name: undefined
workflow_name: undefined,
...nodeCounts
}
}
}

View File

@@ -42,6 +42,11 @@ export interface RunButtonProperties {
subscribe_to_run: boolean
workflow_type: 'template' | 'custom'
workflow_name: string
total_node_count: number
custom_node_count: number
subgraph_count: number
has_api_nodes: boolean
api_node_names: string[]
}
/**
@@ -57,6 +62,13 @@ export interface ExecutionContext {
template_models?: string[]
template_use_case?: string
template_license?: string
// Node composition metrics
custom_node_count: number
api_node_count: number
subgraph_count: number
total_node_count: number
has_api_nodes: boolean
api_node_names: string[]
}
/**
@@ -76,6 +88,13 @@ export interface ExecutionSuccessMetadata {
jobId: string
}
/**
* API credit top-up purchase metadata
*/
export interface CreditTopupMetadata {
credit_amount: number
}
/**
* Template metadata for workflow tracking
*/
@@ -183,6 +202,9 @@ export interface TelemetryProvider {
// Subscription flow events
trackSubscription(event: 'modal_opened' | 'subscribe_clicked'): void
trackMonthlySubscriptionSucceeded(): void
trackAddApiCreditButtonClicked(): void
trackApiCreditTopupButtonPurchaseClicked(amount: number): void
trackRunButton(options?: { subscribe_to_run?: boolean }): void
// Survey flow events
@@ -236,6 +258,10 @@ 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',
// Onboarding Survey
USER_SURVEY_OPENED: 'app:user_survey_opened',
@@ -283,6 +309,7 @@ export type TelemetryEventProperties =
| RunButtonProperties
| ExecutionErrorMetadata
| ExecutionSuccessMetadata
| CreditTopupMetadata
| WorkflowImportMetadata
| TemplateLibraryMetadata
| TemplateLibraryClosedMetadata