mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-02 22:37:32 +00:00
[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:
@@ -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>
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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`)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user