[Backport to rh-test] fix(telemetry): remove redundant run tracking; keep click analytics + single execution event (#6552)

## Summary
Manual backport of #6518 to the `rh-test` branch.

Deduplicates workflow run telemetry and keeps a single source of truth
for execution while retaining click analytics and attributing initiator
source.

- Keep execution tracking in one place via `trackWorkflowExecution()`
- Keep click analytics via `trackRunButton(...)`
- Attribute initiator with `trigger_source` = 'button' | 'keybinding' |
'legacy_ui'
- Remove pre-tracking from keybindings to avoid double/triple counting
- Update legacy UI buttons to emit both click + execution events

## Backport Notes
This backport required manual conflict resolution in:
- `src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue` - Added
batchCount tracking and trigger_source metadata
- `src/composables/useCoreCommands.ts` - Added error handling and
execution tracking
- `src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts`
- Updated trackRunButton signature with trigger_source support

Additionally added:
- `trackUiButtonClicked` method to TelemetryProvider interface
- `UiButtonClickMetadata` type definition
- `UI_BUTTON_CLICKED` event constant

All conflicts resolved intelligently to maintain the intent of the
original PR while adapting to the rh-test branch codebase.

## Original PR
- Original PR: #6518  
- Original commit: 6fe88dba54

## Testing
-  Typecheck passed
-  Pre-commit hooks passed (lint, format)
-  All conflicts resolved

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6552-Backport-to-rh-test-fix-telemetry-remove-redundant-run-tracking-keep-click-analytics-2a06d73d365081f78e4ad46a16be69f1)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Christian Byrne <c.byrne@comfy.org>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Christian Byrne <chrbyrne96@gmail.com>
This commit is contained in:
Christian Byrne
2025-11-02 20:49:02 -08:00
committed by GitHub
parent 044b675138
commit 56412a4076
10 changed files with 128 additions and 57 deletions

View File

@@ -100,7 +100,7 @@ import BatchCountEdit from '../BatchCountEdit.vue'
const workspaceStore = useWorkspaceStore()
const queueCountStore = storeToRefs(useQueuePendingTaskCountStore())
const { mode: queueMode } = storeToRefs(useQueueSettingsStore())
const { mode: queueMode, batchCount } = storeToRefs(useQueueSettingsStore())
const { t } = useI18n()
const queueModeMenuItemLookup = computed(() => {
@@ -158,9 +158,18 @@ const queuePrompt = async (e: Event) => {
? 'Comfy.QueuePromptFront'
: 'Comfy.QueuePrompt'
useTelemetry()?.trackRunButton({ subscribe_to_run: false })
if (batchCount.value > 1) {
useTelemetry()?.trackUiButtonClicked({
button_id: 'queue_run_multiple_batches_submitted'
})
}
await commandStore.execute(commandId)
await commandStore.execute(commandId, {
metadata: {
subscribe_to_run: false,
trigger_source: 'button'
}
})
}
</script>

View File

@@ -21,6 +21,7 @@ import { useSubscription } from '@/platform/cloud/subscription/composables/useSu
import { useSettingStore } from '@/platform/settings/settingStore'
import { SUPPORT_URL } from '@/platform/support/config'
import { useTelemetry } from '@/platform/telemetry'
import type { ExecutionTriggerSource } from '@/platform/telemetry/types'
import { useToastStore } from '@/platform/updates/common/toastStore'
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
@@ -465,7 +466,11 @@ export function useCoreCommands(): ComfyCommand[] {
label: 'Queue Prompt',
versionAdded: '1.3.7',
category: 'essentials' as const,
function: async () => {
function: async (metadata?: {
subscribe_to_run?: boolean
trigger_source?: ExecutionTriggerSource
}) => {
useTelemetry()?.trackRunButton(metadata)
if (!isActiveSubscription.value) {
showSubscriptionDialog()
return
@@ -484,7 +489,11 @@ export function useCoreCommands(): ComfyCommand[] {
label: 'Queue Prompt (Front)',
versionAdded: '1.3.7',
category: 'essentials' as const,
function: async () => {
function: async (metadata?: {
subscribe_to_run?: boolean
trigger_source?: ExecutionTriggerSource
}) => {
useTelemetry()?.trackRunButton(metadata)
if (!isActiveSubscription.value) {
showSubscriptionDialog()
return
@@ -502,7 +511,11 @@ export function useCoreCommands(): ComfyCommand[] {
icon: 'pi pi-play',
label: 'Queue Selected Output Nodes',
versionAdded: '1.19.6',
function: async () => {
function: async (metadata?: {
subscribe_to_run?: boolean
trigger_source?: ExecutionTriggerSource
}) => {
useTelemetry()?.trackRunButton(metadata)
if (!isActiveSubscription.value) {
showSubscriptionDialog()
return
@@ -525,6 +538,17 @@ export function useCoreCommands(): ComfyCommand[] {
// Get execution IDs for all selected output nodes and their descendants
const executionIds =
getExecutionIdsForSelectedNodes(selectedOutputNodes)
if (executionIds.length === 0) {
toastStore.add({
severity: 'error',
summary: t('toastMessages.failedToQueue'),
detail: t('toastMessages.failedExecutionPathResolution'),
life: 3000
})
return
}
useTelemetry()?.trackWorkflowExecution()
await app.queuePrompt(0, batchCount, executionIds)
}
},

View File

@@ -1742,21 +1742,22 @@ const ext: ComfyExtension = {
label: 'Convert selected nodes to group node',
icon: 'pi pi-sitemap',
versionAdded: '1.3.17',
function: convertSelectedNodesToGroupNode
function: () => convertSelectedNodesToGroupNode()
},
{
id: 'Comfy.GroupNode.UngroupSelectedGroupNodes',
label: 'Ungroup selected group nodes',
icon: 'pi pi-sitemap',
versionAdded: '1.3.17',
function: ungroupSelectedGroupNodes
function: () => ungroupSelectedGroupNodes()
},
{
id: 'Comfy.GroupNode.ManageGroupNodes',
label: 'Manage group nodes',
icon: 'pi pi-cog',
versionAdded: '1.3.17',
function: manageGroupNodes
function: (...args: unknown[]) =>
manageGroupNodes(args[0] as string | undefined)
}
],
keybindings: [

View File

@@ -16,6 +16,7 @@ import type {
ExecutionContext,
ExecutionErrorMetadata,
ExecutionSuccessMetadata,
ExecutionTriggerSource,
HelpCenterClosedMetadata,
HelpCenterOpenedMetadata,
HelpResourceClickedMetadata,
@@ -32,6 +33,7 @@ import type {
TemplateLibraryClosedMetadata,
TemplateLibraryMetadata,
TemplateMetadata,
UiButtonClickMetadata,
WorkflowCreatedMetadata,
WorkflowImportMetadata
} from '../../types'
@@ -59,6 +61,7 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
private mixpanel: OverridedMixpanel | null = null
private eventQueue: QueuedEvent[] = []
private isInitialized = false
private lastTriggerSource: ExecutionTriggerSource | undefined
// Onboarding mode - starts true, set to false when app is fully ready
private isOnboardingMode = true
@@ -354,7 +357,10 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
clearTopupUtil()
}
trackRunButton(options?: { subscribe_to_run?: boolean }): void {
trackRunButton(options?: {
subscribe_to_run?: boolean
trigger_source?: ExecutionTriggerSource
}): void {
if (this.isOnboardingMode) {
// During onboarding, track basic run button click without workflow context
this.trackEvent(TelemetryEvents.RUN_BUTTON_CLICKED, {
@@ -365,7 +371,8 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
total_node_count: 0,
subgraph_count: 0,
has_api_nodes: false,
api_node_names: []
api_node_names: [],
trigger_source: options?.trigger_source
})
return
}
@@ -380,20 +387,14 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
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
api_node_names: executionContext.api_node_names,
trigger_source: options?.trigger_source
}
this.lastTriggerSource = options?.trigger_source
this.trackEvent(TelemetryEvents.RUN_BUTTON_CLICKED, runButtonProperties)
}
trackRunTriggeredViaKeybinding(): void {
this.trackEvent(TelemetryEvents.RUN_TRIGGERED_KEYBINDING)
}
trackRunTriggeredViaMenu(): void {
this.trackEvent(TelemetryEvents.RUN_TRIGGERED_MENU)
}
trackSurvey(
stage: 'opened' | 'submitted',
responses?: SurveyResponses
@@ -501,6 +502,10 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
this.trackEvent(TelemetryEvents.WORKFLOW_CREATED, metadata)
}
trackUiButtonClicked(metadata: UiButtonClickMetadata): void {
this.trackEvent(TelemetryEvents.UI_BUTTON_CLICKED, metadata)
}
trackWorkflowExecution(): void {
if (this.isOnboardingMode) {
// During onboarding, track basic execution without workflow context
@@ -518,7 +523,12 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
}
const context = this.getExecutionContext()
this.trackEvent(TelemetryEvents.EXECUTION_START, context)
const eventContext: ExecutionContext = {
...context,
trigger_source: this.lastTriggerSource ?? 'unknown'
}
this.trackEvent(TelemetryEvents.EXECUTION_START, eventContext)
this.lastTriggerSource = undefined
}
trackExecutionError(metadata: ExecutionErrorMetadata): void {

View File

@@ -48,6 +48,7 @@ export interface RunButtonProperties {
subgraph_count: number
has_api_nodes: boolean
api_node_names: string[]
trigger_source?: ExecutionTriggerSource
}
/**
@@ -70,6 +71,7 @@ export interface ExecutionContext {
total_node_count: number
has_api_nodes: boolean
api_node_names: string[]
trigger_source?: ExecutionTriggerSource
}
/**
@@ -193,6 +195,14 @@ export interface TemplateFilterMetadata {
total_count: number
}
/**
* UI button click tracking metadata
*/
export interface UiButtonClickMetadata {
/** Canonical identifier for the button (e.g., "comfy_logo") */
button_id: string
}
/**
* Help center opened metadata
*/
@@ -250,9 +260,10 @@ export interface TelemetryProvider {
trackAddApiCreditButtonClicked(): void
trackApiCreditTopupButtonPurchaseClicked(amount: number): void
trackApiCreditTopupSucceeded(): void
trackRunButton(options?: { subscribe_to_run?: boolean }): void
trackRunTriggeredViaKeybinding(): void
trackRunTriggeredViaMenu(): void
trackRunButton(options?: {
subscribe_to_run?: boolean
trigger_source?: ExecutionTriggerSource
}): void
// Credit top-up tracking (composition with internal utilities)
startTopupTracking(): void
@@ -284,6 +295,9 @@ export interface TelemetryProvider {
// Template filter tracking events
trackTemplateFilterChanged(metadata: TemplateFilterMetadata): void
// Generic UI button click events
trackUiButtonClicked(metadata: UiButtonClickMetadata): void
// Help center events
trackHelpCenterOpened(metadata: HelpCenterOpenedMetadata): void
trackHelpResourceClicked(metadata: HelpResourceClickedMetadata): void
@@ -321,8 +335,6 @@ export const TelemetryEvents = {
// Subscription Flow
RUN_BUTTON_CLICKED: 'app:run_button_click',
RUN_TRIGGERED_KEYBINDING: 'app:run_triggered_keybinding',
RUN_TRIGGERED_MENU: 'app:run_triggered_menu',
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',
@@ -368,12 +380,21 @@ export const TelemetryEvents = {
// Execution Lifecycle
EXECUTION_START: 'execution_start',
EXECUTION_ERROR: 'execution_error',
EXECUTION_SUCCESS: 'execution_success'
EXECUTION_SUCCESS: 'execution_success',
// Generic UI Button Click
UI_BUTTON_CLICKED: 'app:ui_button_clicked'
} as const
export type TelemetryEventName =
(typeof TelemetryEvents)[keyof typeof TelemetryEvents]
export type ExecutionTriggerSource =
| 'button'
| 'keybinding'
| 'legacy_ui'
| 'unknown'
/**
* Union type for all possible telemetry event properties
*/
@@ -394,6 +415,7 @@ export type TelemetryEventProperties =
| NodeSearchMetadata
| NodeSearchResultMetadata
| TemplateFilterMetadata
| UiButtonClickMetadata
| HelpCenterOpenedMetadata
| HelpResourceClickedMetadata
| HelpCenterClosedMetadata

View File

@@ -480,7 +480,8 @@ export class ComfyUI {
textContent: 'Queue Prompt',
onclick: () => {
if (isCloud) {
useTelemetry()?.trackRunTriggeredViaMenu()
useTelemetry()?.trackRunButton({ trigger_source: 'legacy_ui' })
useTelemetry()?.trackWorkflowExecution()
}
app.queuePrompt(0, this.batchCount)
}
@@ -587,7 +588,8 @@ export class ComfyUI {
textContent: 'Queue Front',
onclick: () => {
if (isCloud) {
useTelemetry()?.trackRunTriggeredViaMenu()
useTelemetry()?.trackRunButton({ trigger_source: 'legacy_ui' })
useTelemetry()?.trackWorkflowExecution()
}
app.queuePrompt(-1, this.batchCount)
}

View File

@@ -1,7 +1,5 @@
import { CORE_KEYBINDINGS } from '@/constants/coreKeybindings'
import { isCloud } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { app } from '@/scripts/app'
import { useCommandStore } from '@/stores/commandStore'
import { useDialogStore } from '@/stores/dialogStore'
@@ -66,15 +64,20 @@ export const useKeybindingService = () => {
// Prevent default browser behavior first, then execute the command
event.preventDefault()
if (
isCloud &&
(keybinding.commandId === 'Comfy.QueuePrompt' ||
keybinding.commandId === 'Comfy.QueuePromptFront' ||
keybinding.commandId === 'Comfy.QueueSelectedOutputNodes')
) {
useTelemetry()?.trackRunTriggeredViaKeybinding()
const runCommandIds = new Set([
'Comfy.QueuePrompt',
'Comfy.QueuePromptFront',
'Comfy.QueueSelectedOutputNodes'
])
if (runCommandIds.has(keybinding.commandId)) {
await commandStore.execute(keybinding.commandId, {
metadata: {
trigger_source: 'keybinding'
}
})
} else {
await commandStore.execute(keybinding.commandId)
}
await commandStore.execute(keybinding.commandId)
return
}

View File

@@ -9,7 +9,7 @@ import type { KeybindingImpl } from './keybindingStore'
export interface ComfyCommand {
id: string
function: () => void | Promise<void>
function: (metadata?: Record<string, unknown>) => void | Promise<void>
label?: string | (() => string)
icon?: string | (() => string)
@@ -24,7 +24,7 @@ export interface ComfyCommand {
export class ComfyCommandImpl implements ComfyCommand {
id: string
function: () => void | Promise<void>
function: (metadata?: Record<string, unknown>) => void | Promise<void>
_label?: string | (() => string)
_icon?: string | (() => string)
_tooltip?: string | (() => string)
@@ -96,11 +96,17 @@ export const useCommandStore = defineStore('command', () => {
const { wrapWithErrorHandlingAsync } = useErrorHandling()
const execute = async (
commandId: string,
errorHandler?: (error: any) => void
options?: {
errorHandler?: (error: unknown) => void
metadata?: Record<string, unknown>
}
) => {
const command = getCommand(commandId)
if (command) {
await wrapWithErrorHandlingAsync(command.function, errorHandler)()
await wrapWithErrorHandlingAsync(
() => command.function(options?.metadata),
options?.errorHandler
)()
} else {
throw new Error(`Command ${commandId} not found`)
}

View File

@@ -3,8 +3,6 @@ import type { MenuItem } from 'primevue/menuitem'
import { ref } from 'vue'
import { CORE_MENU_COMMANDS } from '@/constants/coreMenuCommands'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import type { ComfyExtension } from '@/types/comfy'
import { useCommandStore } from './commandStore'
@@ -64,17 +62,7 @@ export const useMenuItemStore = defineStore('menuItem', () => {
.map(
(command) =>
({
command: () => {
if (
isCloud &&
(command.id === 'Comfy.QueuePrompt' ||
command.id === 'Comfy.QueuePromptFront' ||
command.id === 'Comfy.QueueSelectedOutputNodes')
) {
useTelemetry()?.trackRunTriggeredViaMenu()
}
return commandStore.execute(command.id)
},
command: () => commandStore.execute(command.id),
label: command.menubarLabel,
icon: command.icon,
tooltip: command.tooltip,

View File

@@ -114,5 +114,11 @@ export interface ExtensionManager {
export interface CommandManager {
commands: ComfyCommand[]
execute(command: string, errorHandler?: (error: any) => void): void
execute(
command: string,
options?: {
errorHandler?: (error: unknown) => void
metadata?: Record<string, unknown>
}
): void
}