Skip to content

Commit 6fe88db

Browse files
christian-byrneChristian ByrneDrJKLbenceruleanluclaude
authored
fix(telemetry): remove redundant run tracking; keep click analytics + single execution event (#6518)
## Summary 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 (they bypass commands) ## Problem PR #6499 added tracking at multiple layers: 1) Keybindings tracked via a dedicated method and then executed a command 2) Menu items tracked via a dedicated method and then executed a command 3) Commands also tracked execution Because these ultimately trigger the same command path, this produced duplicate (sometimes triplicate) events per user action and made it hard to attribute initiator precisely. ## Solution - Remove redundant tracking from keybindings (and previous legacy menu handler) - Commands now emit both: - `trackRunButton(...)` (click analytics, includes `trigger_source` when provided) - `trackWorkflowExecution()` (single execution start; includes the last `trigger_source`) - Legacy UI buttons (which call `app.queuePrompt(...)` directly) now also emit both events with `trigger_source = 'legacy_ui'` - Add `ExecutionTriggerSource` type and wire `trigger_source` through provider so `EXECUTION_START` matches the most recent click intent ### Telemetry behavior after this change - `RUN_BUTTON_CLICKED` (click analytics) - Emitted when a run is initiated via: - Button: `trigger_source = 'button'` - Keybinding: `trigger_source = 'keybinding'` - Legacy UI: `trigger_source = 'legacy_ui'` - `EXECUTION_START` (execution lifecycle) - Emitted once per run at start; includes `trigger_source` matched to the click intent above - Paired with `EXECUTION_SUCCESS` / `EXECUTION_ERROR` from execution handlers ## Benefits - ✅ Accurate counts by removing duplicated run events - ✅ Clear initiator attribution (button vs keybinding vs legacy UI) - ✅ Separation of “intent” (click) vs. “lifecycle” (execution) - ✅ Simpler implementation and maintenance ## Files Changed (high level) - `src/services/keybindingService.ts`: Route run commands with `trigger_source = 'keybinding'` - `src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue`: Send `trigger_source = 'button'` to commands - `src/scripts/ui.ts`: Legacy queue buttons emit `trackRunButton({ trigger_source: 'legacy_ui' })` and `trackWorkflowExecution()` - `src/composables/useCoreCommands.ts`: Commands emit `trackRunButton(...)` + `trackWorkflowExecution()`; accept telemetry metadata - `src/platform/telemetry/types.ts`: Add `ExecutionTriggerSource` and optional `trigger_source` in click + execution payloads - `src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts`: Carry `trigger_source` from click → execution and reset after use - `src/stores/commandStore.ts`: Allow commands to receive args (for telemetry metadata) - `src/extensions/core/groupNode.ts`: Adjust command function signatures to new execute signature ## Related - Reverts the multi‑event approach from #6499 - Keeps `trackWorkflowExecution()` as the canonical execution event while preserving click analytics and initiator attribution with `trackRunButton(...)` ┆Issue is synchronized with this Notion page by Unito --------- Co-authored-by: Christian Byrne <[email protected]> Co-authored-by: Alexander Brown <[email protected]> Co-authored-by: Benjamin Lu <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent 8df0a38 commit 6fe88db

File tree

10 files changed

+90
-55
lines changed

10 files changed

+90
-55
lines changed

src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,18 @@ const queuePrompt = async (e: Event) => {
164164
? 'Comfy.QueuePromptFront'
165165
: 'Comfy.QueuePrompt'
166166
167-
useTelemetry()?.trackRunButton({ subscribe_to_run: false })
168-
169167
if (batchCount.value > 1) {
170168
useTelemetry()?.trackUiButtonClicked({
171169
button_id: 'queue_run_multiple_batches_submitted'
172170
})
173171
}
174172
175-
await commandStore.execute(commandId)
173+
await commandStore.execute(commandId, {
174+
metadata: {
175+
subscribe_to_run: false,
176+
trigger_source: 'button'
177+
}
178+
})
176179
}
177180
</script>
178181

src/composables/useCoreCommands.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { useSubscription } from '@/platform/cloud/subscription/composables/useSu
2222
import { useSettingStore } from '@/platform/settings/settingStore'
2323
import { SUPPORT_URL } from '@/platform/support/config'
2424
import { useTelemetry } from '@/platform/telemetry'
25+
import type { ExecutionTriggerSource } from '@/platform/telemetry/types'
2526
import { useToastStore } from '@/platform/updates/common/toastStore'
2627
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
2728
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
@@ -466,7 +467,11 @@ export function useCoreCommands(): ComfyCommand[] {
466467
label: 'Queue Prompt',
467468
versionAdded: '1.3.7',
468469
category: 'essentials' as const,
469-
function: async () => {
470+
function: async (metadata?: {
471+
subscribe_to_run?: boolean
472+
trigger_source?: ExecutionTriggerSource
473+
}) => {
474+
useTelemetry()?.trackRunButton(metadata)
470475
if (!isActiveSubscription.value) {
471476
showSubscriptionDialog()
472477
return
@@ -485,7 +490,11 @@ export function useCoreCommands(): ComfyCommand[] {
485490
label: 'Queue Prompt (Front)',
486491
versionAdded: '1.3.7',
487492
category: 'essentials' as const,
488-
function: async () => {
493+
function: async (metadata?: {
494+
subscribe_to_run?: boolean
495+
trigger_source?: ExecutionTriggerSource
496+
}) => {
497+
useTelemetry()?.trackRunButton(metadata)
489498
if (!isActiveSubscription.value) {
490499
showSubscriptionDialog()
491500
return
@@ -503,7 +512,11 @@ export function useCoreCommands(): ComfyCommand[] {
503512
icon: 'pi pi-play',
504513
label: 'Queue Selected Output Nodes',
505514
versionAdded: '1.19.6',
506-
function: async () => {
515+
function: async (metadata?: {
516+
subscribe_to_run?: boolean
517+
trigger_source?: ExecutionTriggerSource
518+
}) => {
519+
useTelemetry()?.trackRunButton(metadata)
507520
if (!isActiveSubscription.value) {
508521
showSubscriptionDialog()
509522
return
@@ -526,6 +539,7 @@ export function useCoreCommands(): ComfyCommand[] {
526539
// Get execution IDs for all selected output nodes and their descendants
527540
const executionIds =
528541
getExecutionIdsForSelectedNodes(selectedOutputNodes)
542+
529543
if (executionIds.length === 0) {
530544
toastStore.add({
531545
severity: 'error',
@@ -535,6 +549,7 @@ export function useCoreCommands(): ComfyCommand[] {
535549
})
536550
return
537551
}
552+
useTelemetry()?.trackWorkflowExecution()
538553
await app.queuePrompt(0, batchCount, executionIds)
539554
}
540555
},

src/extensions/core/groupNode.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,21 +1694,22 @@ const ext: ComfyExtension = {
16941694
label: 'Convert selected nodes to group node',
16951695
icon: 'pi pi-sitemap',
16961696
versionAdded: '1.3.17',
1697-
function: convertSelectedNodesToGroupNode
1697+
function: () => convertSelectedNodesToGroupNode()
16981698
},
16991699
{
17001700
id: 'Comfy.GroupNode.UngroupSelectedGroupNodes',
17011701
label: 'Ungroup selected group nodes',
17021702
icon: 'pi pi-sitemap',
17031703
versionAdded: '1.3.17',
1704-
function: ungroupSelectedGroupNodes
1704+
function: () => ungroupSelectedGroupNodes()
17051705
},
17061706
{
17071707
id: 'Comfy.GroupNode.ManageGroupNodes',
17081708
label: 'Manage group nodes',
17091709
icon: 'pi pi-cog',
17101710
versionAdded: '1.3.17',
1711-
function: manageGroupNodes
1711+
function: (...args: unknown[]) =>
1712+
manageGroupNodes(args[0] as string | undefined)
17121713
}
17131714
],
17141715
keybindings: [

src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
AuthMetadata,
1818
CreditTopupMetadata,
1919
ExecutionContext,
20+
ExecutionTriggerSource,
2021
ExecutionErrorMetadata,
2122
ExecutionSuccessMetadata,
2223
HelpCenterClosedMetadata,
@@ -65,6 +66,7 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
6566
private mixpanel: OverridedMixpanel | null = null
6667
private eventQueue: QueuedEvent[] = []
6768
private isInitialized = false
69+
private lastTriggerSource: ExecutionTriggerSource | undefined
6870

6971
constructor() {
7072
const token = window.__CONFIG__?.mixpanel_token
@@ -196,7 +198,10 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
196198
clearTopupUtil()
197199
}
198200

199-
trackRunButton(options?: { subscribe_to_run?: boolean }): void {
201+
trackRunButton(options?: {
202+
subscribe_to_run?: boolean
203+
trigger_source?: ExecutionTriggerSource
204+
}): void {
200205
const executionContext = this.getExecutionContext()
201206

202207
const runButtonProperties: RunButtonProperties = {
@@ -207,20 +212,14 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
207212
total_node_count: executionContext.total_node_count,
208213
subgraph_count: executionContext.subgraph_count,
209214
has_api_nodes: executionContext.has_api_nodes,
210-
api_node_names: executionContext.api_node_names
215+
api_node_names: executionContext.api_node_names,
216+
trigger_source: options?.trigger_source
211217
}
212218

219+
this.lastTriggerSource = options?.trigger_source
213220
this.trackEvent(TelemetryEvents.RUN_BUTTON_CLICKED, runButtonProperties)
214221
}
215222

216-
trackRunTriggeredViaKeybinding(): void {
217-
this.trackEvent(TelemetryEvents.RUN_TRIGGERED_KEYBINDING)
218-
}
219-
220-
trackRunTriggeredViaMenu(): void {
221-
this.trackEvent(TelemetryEvents.RUN_TRIGGERED_MENU)
222-
}
223-
224223
trackSurvey(
225224
stage: 'opened' | 'submitted',
226225
responses?: SurveyResponses
@@ -323,7 +322,12 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
323322

324323
trackWorkflowExecution(): void {
325324
const context = this.getExecutionContext()
326-
this.trackEvent(TelemetryEvents.EXECUTION_START, context)
325+
const eventContext: ExecutionContext = {
326+
...context,
327+
trigger_source: this.lastTriggerSource ?? 'unknown'
328+
}
329+
this.trackEvent(TelemetryEvents.EXECUTION_START, eventContext)
330+
this.lastTriggerSource = undefined
327331
}
328332

329333
trackExecutionError(metadata: ExecutionErrorMetadata): void {

src/platform/telemetry/types.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export interface RunButtonProperties {
4747
subgraph_count: number
4848
has_api_nodes: boolean
4949
api_node_names: string[]
50+
trigger_source?: ExecutionTriggerSource
5051
}
5152

5253
/**
@@ -69,6 +70,7 @@ export interface ExecutionContext {
6970
total_node_count: number
7071
has_api_nodes: boolean
7172
api_node_names: string[]
73+
trigger_source?: ExecutionTriggerSource
7274
}
7375

7476
/**
@@ -265,9 +267,10 @@ export interface TelemetryProvider {
265267
trackAddApiCreditButtonClicked(): void
266268
trackApiCreditTopupButtonPurchaseClicked(amount: number): void
267269
trackApiCreditTopupSucceeded(): void
268-
trackRunButton(options?: { subscribe_to_run?: boolean }): void
269-
trackRunTriggeredViaKeybinding(): void
270-
trackRunTriggeredViaMenu(): void
270+
trackRunButton(options?: {
271+
subscribe_to_run?: boolean
272+
trigger_source?: ExecutionTriggerSource
273+
}): void
271274

272275
// Credit top-up tracking (composition with internal utilities)
273276
startTopupTracking(): void
@@ -336,8 +339,6 @@ export const TelemetryEvents = {
336339

337340
// Subscription Flow
338341
RUN_BUTTON_CLICKED: 'app:run_button_click',
339-
RUN_TRIGGERED_KEYBINDING: 'app:run_triggered_keybinding',
340-
RUN_TRIGGERED_MENU: 'app:run_triggered_menu',
341342
SUBSCRIPTION_REQUIRED_MODAL_OPENED: 'app:subscription_required_modal_opened',
342343
SUBSCRIBE_NOW_BUTTON_CLICKED: 'app:subscribe_now_button_clicked',
343344
MONTHLY_SUBSCRIPTION_SUCCEEDED: 'app:monthly_subscription_succeeded',
@@ -399,6 +400,12 @@ export const TelemetryEvents = {
399400
export type TelemetryEventName =
400401
(typeof TelemetryEvents)[keyof typeof TelemetryEvents]
401402

403+
export type ExecutionTriggerSource =
404+
| 'button'
405+
| 'keybinding'
406+
| 'legacy_ui'
407+
| 'unknown'
408+
402409
/**
403410
* Union type for all possible telemetry event properties
404411
*/

src/scripts/ui.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,8 @@ export class ComfyUI {
476476
textContent: 'Queue Prompt',
477477
onclick: () => {
478478
if (isCloud) {
479-
useTelemetry()?.trackRunTriggeredViaMenu()
479+
useTelemetry()?.trackRunButton({ trigger_source: 'legacy_ui' })
480+
useTelemetry()?.trackWorkflowExecution()
480481
}
481482
app.queuePrompt(0, this.batchCount)
482483
}
@@ -583,7 +584,8 @@ export class ComfyUI {
583584
textContent: 'Queue Front',
584585
onclick: () => {
585586
if (isCloud) {
586-
useTelemetry()?.trackRunTriggeredViaMenu()
587+
useTelemetry()?.trackRunButton({ trigger_source: 'legacy_ui' })
588+
useTelemetry()?.trackWorkflowExecution()
587589
}
588590
app.queuePrompt(-1, this.batchCount)
589591
}

src/services/keybindingService.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { CORE_KEYBINDINGS } from '@/constants/coreKeybindings'
2-
import { isCloud } from '@/platform/distribution/types'
32
import { useSettingStore } from '@/platform/settings/settingStore'
4-
import { useTelemetry } from '@/platform/telemetry'
53
import { app } from '@/scripts/app'
64
import { useCommandStore } from '@/stores/commandStore'
75
import { useDialogStore } from '@/stores/dialogStore'
@@ -66,15 +64,20 @@ export const useKeybindingService = () => {
6664

6765
// Prevent default browser behavior first, then execute the command
6866
event.preventDefault()
69-
if (
70-
isCloud &&
71-
(keybinding.commandId === 'Comfy.QueuePrompt' ||
72-
keybinding.commandId === 'Comfy.QueuePromptFront' ||
73-
keybinding.commandId === 'Comfy.QueueSelectedOutputNodes')
74-
) {
75-
useTelemetry()?.trackRunTriggeredViaKeybinding()
67+
const runCommandIds = new Set([
68+
'Comfy.QueuePrompt',
69+
'Comfy.QueuePromptFront',
70+
'Comfy.QueueSelectedOutputNodes'
71+
])
72+
if (runCommandIds.has(keybinding.commandId)) {
73+
await commandStore.execute(keybinding.commandId, {
74+
metadata: {
75+
trigger_source: 'keybinding'
76+
}
77+
})
78+
} else {
79+
await commandStore.execute(keybinding.commandId)
7680
}
77-
await commandStore.execute(keybinding.commandId)
7881
return
7982
}
8083

src/stores/commandStore.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { KeybindingImpl } from './keybindingStore'
99

1010
export interface ComfyCommand {
1111
id: string
12-
function: () => void | Promise<void>
12+
function: (metadata?: Record<string, unknown>) => void | Promise<void>
1313

1414
label?: string | (() => string)
1515
icon?: string | (() => string)
@@ -24,7 +24,7 @@ export interface ComfyCommand {
2424

2525
export class ComfyCommandImpl implements ComfyCommand {
2626
id: string
27-
function: () => void | Promise<void>
27+
function: (metadata?: Record<string, unknown>) => void | Promise<void>
2828
_label?: string | (() => string)
2929
_icon?: string | (() => string)
3030
_tooltip?: string | (() => string)
@@ -96,11 +96,17 @@ export const useCommandStore = defineStore('command', () => {
9696
const { wrapWithErrorHandlingAsync } = useErrorHandling()
9797
const execute = async (
9898
commandId: string,
99-
errorHandler?: (error: any) => void
99+
options?: {
100+
errorHandler?: (error: unknown) => void
101+
metadata?: Record<string, unknown>
102+
}
100103
) => {
101104
const command = getCommand(commandId)
102105
if (command) {
103-
await wrapWithErrorHandlingAsync(command.function, errorHandler)()
106+
await wrapWithErrorHandlingAsync(
107+
() => command.function(options?.metadata),
108+
options?.errorHandler
109+
)()
104110
} else {
105111
throw new Error(`Command ${commandId} not found`)
106112
}

src/stores/menuItemStore.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import type { MenuItem } from 'primevue/menuitem'
33
import { ref } from 'vue'
44

55
import { CORE_MENU_COMMANDS } from '@/constants/coreMenuCommands'
6-
import { isCloud } from '@/platform/distribution/types'
7-
import { useTelemetry } from '@/platform/telemetry'
86
import type { ComfyExtension } from '@/types/comfy'
97

108
import { useCommandStore } from './commandStore'
@@ -64,17 +62,7 @@ export const useMenuItemStore = defineStore('menuItem', () => {
6462
.map(
6563
(command) =>
6664
({
67-
command: () => {
68-
if (
69-
isCloud &&
70-
(command.id === 'Comfy.QueuePrompt' ||
71-
command.id === 'Comfy.QueuePromptFront' ||
72-
command.id === 'Comfy.QueueSelectedOutputNodes')
73-
) {
74-
useTelemetry()?.trackRunTriggeredViaMenu()
75-
}
76-
return commandStore.execute(command.id)
77-
},
65+
command: () => commandStore.execute(command.id),
7866
label: command.menubarLabel,
7967
icon: command.icon,
8068
tooltip: command.tooltip,

src/types/extensionTypes.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,11 @@ export interface ExtensionManager {
114114

115115
export interface CommandManager {
116116
commands: ComfyCommand[]
117-
execute(command: string, errorHandler?: (error: any) => void): void
117+
execute(
118+
command: string,
119+
options?: {
120+
errorHandler?: (error: unknown) => void
121+
metadata?: Record<string, unknown>
122+
}
123+
): void
118124
}

0 commit comments

Comments
 (0)