mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-23 00:04:06 +00:00
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:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user