mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-06-11 16:59:20 +00:00
Compare commits
4 Commits
main
...
bl/clarify
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fe6aafeda | ||
|
|
491786647d | ||
|
|
285d51dfe6 | ||
|
|
8385f0b830 |
@@ -222,7 +222,8 @@ watch(visible, async (newVisible) => {
|
||||
*/
|
||||
useEventListener(dragHandleRef, 'mousedown', () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'actionbar_run_handle_drag_start'
|
||||
button_id: 'actionbar_run_handle_drag_start',
|
||||
element_group: 'actionbar'
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -131,7 +131,8 @@ const queueModeMenuItemLookup = computed<Record<string, QueueModeMenuItem>>(
|
||||
tooltip: t('menu.onChangeTooltip'),
|
||||
command: () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'queue_mode_option_run_on_change_selected'
|
||||
button_id: 'queue_mode_option_run_on_change_selected',
|
||||
element_group: 'queue'
|
||||
})
|
||||
queueMode.value = 'change'
|
||||
}
|
||||
@@ -145,7 +146,8 @@ const queueModeMenuItemLookup = computed<Record<string, QueueModeMenuItem>>(
|
||||
tooltip: t('menu.instantTooltip'),
|
||||
command: () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'queue_mode_option_run_instant_selected'
|
||||
button_id: 'queue_mode_option_run_instant_selected',
|
||||
element_group: 'queue'
|
||||
})
|
||||
queueMode.value = 'instant-idle'
|
||||
}
|
||||
@@ -237,7 +239,8 @@ const queuePrompt = async (e: Event) => {
|
||||
|
||||
if (batchCount.value > 1) {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'queue_run_multiple_batches_submitted'
|
||||
button_id: 'queue_run_multiple_batches_submitted',
|
||||
element_group: 'queue'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,8 @@ const home = computed(() => ({
|
||||
isBlueprint: isBlueprint.value,
|
||||
command: () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'breadcrumb_subgraph_root_selected'
|
||||
button_id: 'breadcrumb_subgraph_root_selected',
|
||||
element_group: 'breadcrumb'
|
||||
})
|
||||
const canvas = useCanvasStore().getCanvas()
|
||||
if (!canvas.graph) throw new TypeError('Canvas has no graph')
|
||||
@@ -103,7 +104,8 @@ const items = computed(() => {
|
||||
key: `subgraph-${subgraph.id}`,
|
||||
command: () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'breadcrumb_subgraph_item_selected'
|
||||
button_id: 'breadcrumb_subgraph_item_selected',
|
||||
element_group: 'breadcrumb'
|
||||
})
|
||||
const canvas = useCanvasStore().getCanvas()
|
||||
if (!canvas.graph) throw new TypeError('Canvas has no graph')
|
||||
|
||||
@@ -40,7 +40,8 @@ function handleOpen(open: boolean) {
|
||||
if (open) {
|
||||
markAsSeen()
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: source
|
||||
button_id: source,
|
||||
element_group: 'workflow_actions'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,8 @@ const reportOpen = ref(false)
|
||||
*/
|
||||
const showReport = () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'error_dialog_show_report_clicked'
|
||||
button_id: 'error_dialog_show_report_clicked',
|
||||
element_group: 'error_dialog'
|
||||
})
|
||||
reportOpen.value = true
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ const queryString = computed(() => props.errorMessage + ' is:issue')
|
||||
|
||||
function openGitHubIssues() {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'error_dialog_find_existing_issues_clicked'
|
||||
button_id: 'error_dialog_find_existing_issues_clicked',
|
||||
element_group: 'error_dialog'
|
||||
})
|
||||
const query = encodeURIComponent(queryString.value)
|
||||
const url = `https://github.com/${props.repoOwner}/${props.repoName}/issues?q=${query}`
|
||||
|
||||
@@ -218,7 +218,8 @@ onMounted(() => {
|
||||
*/
|
||||
const onMinimapToggleClick = () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'graph_menu_minimap_toggle_clicked'
|
||||
button_id: 'graph_menu_minimap_toggle_clicked',
|
||||
element_group: 'graph_menu'
|
||||
})
|
||||
void commandStore.execute('Comfy.Canvas.ToggleMinimap')
|
||||
}
|
||||
@@ -228,7 +229,8 @@ const onMinimapToggleClick = () => {
|
||||
*/
|
||||
const onLinkVisibilityToggleClick = () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'graph_menu_hide_links_toggle_clicked'
|
||||
button_id: 'graph_menu_hide_links_toggle_clicked',
|
||||
element_group: 'graph_menu'
|
||||
})
|
||||
void commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')
|
||||
}
|
||||
|
||||
@@ -65,7 +65,8 @@ describe('InfoButton', () => {
|
||||
|
||||
expect(openNodeInfoMock).toHaveBeenCalled()
|
||||
expect(trackUiButtonClickedMock).toHaveBeenCalledWith({
|
||||
button_id: 'selection_toolbox_node_info_opened'
|
||||
button_id: 'selection_toolbox_node_info_opened',
|
||||
element_group: 'selection_toolbox'
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ const onInfoClick = () => {
|
||||
if (!openNodeInfo()) return
|
||||
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'selection_toolbox_node_info_opened'
|
||||
button_id: 'selection_toolbox_node_info_opened',
|
||||
element_group: 'selection_toolbox'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -58,7 +58,8 @@ describe('useErrorActions', () => {
|
||||
openGitHubIssues()
|
||||
|
||||
expect(mocks.trackUiButtonClicked).toHaveBeenCalledWith({
|
||||
button_id: 'error_tab_github_issues_clicked'
|
||||
button_id: 'error_tab_github_issues_clicked',
|
||||
element_group: 'errors_panel'
|
||||
})
|
||||
expect(windowOpenSpy).toHaveBeenCalledWith(
|
||||
mocks.staticUrls.githubIssues,
|
||||
@@ -123,7 +124,8 @@ describe('useErrorActions', () => {
|
||||
findOnGitHub('CUDA out of memory')
|
||||
|
||||
expect(mocks.trackUiButtonClicked).toHaveBeenCalledWith({
|
||||
button_id: 'error_tab_find_existing_issues_clicked'
|
||||
button_id: 'error_tab_find_existing_issues_clicked',
|
||||
element_group: 'errors_panel'
|
||||
})
|
||||
const expectedQuery = encodeURIComponent('CUDA out of memory is:issue')
|
||||
expect(windowOpenSpy).toHaveBeenCalledWith(
|
||||
|
||||
@@ -9,7 +9,8 @@ export function useErrorActions() {
|
||||
|
||||
function openGitHubIssues() {
|
||||
telemetry?.trackUiButtonClicked({
|
||||
button_id: 'error_tab_github_issues_clicked'
|
||||
button_id: 'error_tab_github_issues_clicked',
|
||||
element_group: 'errors_panel'
|
||||
})
|
||||
window.open(staticUrls.githubIssues, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
@@ -25,7 +26,8 @@ export function useErrorActions() {
|
||||
|
||||
function findOnGitHub(errorMessage: string) {
|
||||
telemetry?.trackUiButtonClicked({
|
||||
button_id: 'error_tab_find_existing_issues_clicked'
|
||||
button_id: 'error_tab_find_existing_issues_clicked',
|
||||
element_group: 'errors_panel'
|
||||
})
|
||||
const query = encodeURIComponent(errorMessage + ' is:issue')
|
||||
window.open(
|
||||
|
||||
@@ -150,7 +150,8 @@ const telemetry = useTelemetry()
|
||||
|
||||
function onLogoMenuClick(event: MouseEvent) {
|
||||
telemetry?.trackUiButtonClicked({
|
||||
button_id: 'sidebar_comfy_menu_opened'
|
||||
button_id: 'sidebar_comfy_menu_opened',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
menuRef.value?.toggle(event)
|
||||
}
|
||||
@@ -217,7 +218,8 @@ const extraMenuItems = computed(() => [
|
||||
icon: 'icon-[lucide--settings]',
|
||||
command: () => {
|
||||
telemetry?.trackUiButtonClicked({
|
||||
button_id: 'sidebar_settings_menu_opened'
|
||||
button_id: 'sidebar_settings_menu_opened',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
showSettings()
|
||||
}
|
||||
@@ -329,7 +331,8 @@ const handleNodes2ToggleClick = () => {
|
||||
const onNodes2ToggleChange = async (value: boolean) => {
|
||||
await settingStore.set('Comfy.VueNodes.Enabled', value)
|
||||
telemetry?.trackUiButtonClicked({
|
||||
button_id: `menu_nodes_2.0_toggle_${value ? 'enabled' : 'disabled'}`
|
||||
button_id: `menu_nodes_2.0_toggle_${value ? 'enabled' : 'disabled'}`,
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -138,19 +138,23 @@ const onTabClick = async (item: SidebarTabExtension) => {
|
||||
|
||||
if (isNodeLibraryTab)
|
||||
telemetry?.trackUiButtonClicked({
|
||||
button_id: 'sidebar_tab_node_library_selected'
|
||||
button_id: 'sidebar_tab_node_library_selected',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
else if (isModelLibraryTab)
|
||||
telemetry?.trackUiButtonClicked({
|
||||
button_id: 'sidebar_tab_model_library_selected'
|
||||
button_id: 'sidebar_tab_model_library_selected',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
else if (isWorkflowsTab)
|
||||
telemetry?.trackUiButtonClicked({
|
||||
button_id: 'sidebar_tab_workflows_selected'
|
||||
button_id: 'sidebar_tab_workflows_selected',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
else if (isAssetsTab)
|
||||
telemetry?.trackUiButtonClicked({
|
||||
button_id: 'sidebar_tab_assets_media_selected'
|
||||
button_id: 'sidebar_tab_assets_media_selected',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
|
||||
await commandStore.commands
|
||||
|
||||
@@ -21,7 +21,8 @@ const bottomPanelStore = useBottomPanelStore()
|
||||
*/
|
||||
const toggleConsole = () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'sidebar_bottom_panel_console_toggled'
|
||||
button_id: 'sidebar_bottom_panel_console_toggled',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
bottomPanelStore.toggleBottomPanel()
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ const tooltipText = computed(
|
||||
const showSettingsDialog = () => {
|
||||
command.function()
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'sidebar_settings_button_clicked'
|
||||
button_id: 'sidebar_settings_button_clicked',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -37,7 +37,8 @@ const tooltipText = computed(
|
||||
*/
|
||||
const toggleShortcutsPanel = () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'sidebar_shortcuts_panel_toggled'
|
||||
button_id: 'sidebar_shortcuts_panel_toggled',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
bottomPanelStore.togglePanel('shortcuts')
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ const isSmall = computed(
|
||||
*/
|
||||
const openTemplates = () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'sidebar_templates_dialog_opened'
|
||||
button_id: 'sidebar_templates_dialog_opened',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
useWorkflowTemplateSelectorDialog().show('sidebar')
|
||||
}
|
||||
|
||||
@@ -118,7 +118,8 @@ const toggleBookmark = async () => {
|
||||
|
||||
const onHelpClick = () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'node_library_help_button'
|
||||
button_id: 'node_library_help_button',
|
||||
element_group: 'node_library'
|
||||
})
|
||||
props.openNodeHelp(nodeDef.value)
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ export function useHelpCenter() {
|
||||
*/
|
||||
const toggleHelpCenter = () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'sidebar_help_center_toggled'
|
||||
button_id: 'sidebar_help_center_toggled',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
helpCenterStore.toggle()
|
||||
}
|
||||
|
||||
@@ -88,20 +88,23 @@ const { t } = useI18n()
|
||||
onMounted(() => {
|
||||
// Impression event — uses trackUiButtonClicked as no dedicated impression tracker exists
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'cloud_notification_modal_impression'
|
||||
button_id: 'cloud_notification_modal_impression',
|
||||
element_group: 'cloud_notification'
|
||||
})
|
||||
})
|
||||
|
||||
function onDismiss() {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'cloud_notification_continue_locally_clicked'
|
||||
button_id: 'cloud_notification_continue_locally_clicked',
|
||||
element_group: 'cloud_notification'
|
||||
})
|
||||
useDialogStore().closeDialog()
|
||||
}
|
||||
|
||||
function onExplore() {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'cloud_notification_explore_cloud_clicked'
|
||||
button_id: 'cloud_notification_explore_cloud_clicked',
|
||||
element_group: 'cloud_notification'
|
||||
})
|
||||
|
||||
const params = new URLSearchParams({
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('@/composables/useAppMode', () => ({
|
||||
useAppMode: () => ({
|
||||
mode: { value: 'app' },
|
||||
isAppMode: { value: true }
|
||||
})
|
||||
}))
|
||||
|
||||
import { GtmTelemetryProvider } from './GtmTelemetryProvider'
|
||||
|
||||
@@ -18,6 +25,7 @@ describe('GtmTelemetryProvider', () => {
|
||||
window.dataLayer = undefined
|
||||
window.gtag = undefined
|
||||
document.head.innerHTML = ''
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
it('injects the GTM runtime script', () => {
|
||||
@@ -184,11 +192,15 @@ describe('GtmTelemetryProvider', () => {
|
||||
|
||||
it('pushes run_workflow with trigger_source', () => {
|
||||
const provider = createInitializedProvider()
|
||||
localStorage.setItem('Comfy.MenuPosition.Docked', 'false')
|
||||
provider.trackRunButton({ trigger_source: 'button' })
|
||||
expect(lastDataLayerEntry()).toMatchObject({
|
||||
event: 'run_workflow',
|
||||
trigger_source: 'button',
|
||||
subscribe_to_run: false
|
||||
subscribe_to_run: false,
|
||||
view_mode: 'app',
|
||||
is_app_mode: true,
|
||||
dock_state: 'floating'
|
||||
})
|
||||
})
|
||||
|
||||
@@ -323,16 +335,33 @@ describe('GtmTelemetryProvider', () => {
|
||||
provider.trackShareFlow({
|
||||
step: 'link_copied',
|
||||
source: 'app_mode',
|
||||
view_mode: 'app',
|
||||
is_app_mode: true,
|
||||
share_id: 'share-1'
|
||||
})
|
||||
expect(lastDataLayerEntry()).toMatchObject({
|
||||
event: 'share_flow',
|
||||
step: 'link_copied',
|
||||
source: 'app_mode'
|
||||
source: 'app_mode',
|
||||
view_mode: 'app',
|
||||
is_app_mode: true
|
||||
})
|
||||
expect(lastDataLayerEntry()).not.toHaveProperty('share_id')
|
||||
})
|
||||
|
||||
it('pushes ui_button_click with element_group', () => {
|
||||
const provider = createInitializedProvider()
|
||||
provider.trackUiButtonClicked({
|
||||
button_id: 'sidebar_settings_button_clicked',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
expect(lastDataLayerEntry()).toMatchObject({
|
||||
event: 'ui_button_click',
|
||||
button_id: 'sidebar_settings_button_clicked',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
})
|
||||
|
||||
it('omits share_id from workflow import events', () => {
|
||||
const provider = createInitializedProvider()
|
||||
provider.trackWorkflowImported({
|
||||
|
||||
@@ -29,6 +29,8 @@ import type {
|
||||
WorkflowImportMetadata,
|
||||
WorkflowSavedMetadata
|
||||
} from '../../types'
|
||||
import { useAppMode } from '@/composables/useAppMode'
|
||||
import { getActionbarDockState } from '../../utils/getActionbarDockState'
|
||||
|
||||
/**
|
||||
* Google Tag Manager telemetry provider.
|
||||
@@ -185,9 +187,14 @@ export class GtmTelemetryProvider implements TelemetryProvider {
|
||||
subscribe_to_run?: boolean
|
||||
trigger_source?: ExecutionTriggerSource
|
||||
}): void {
|
||||
const { mode, isAppMode } = useAppMode()
|
||||
|
||||
this.pushEvent('run_workflow', {
|
||||
subscribe_to_run: options?.subscribe_to_run ?? false,
|
||||
trigger_source: options?.trigger_source ?? 'unknown'
|
||||
trigger_source: options?.trigger_source ?? 'unknown',
|
||||
view_mode: mode.value,
|
||||
is_app_mode: isAppMode.value,
|
||||
dock_state: getActionbarDockState()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -287,7 +294,9 @@ export class GtmTelemetryProvider implements TelemetryProvider {
|
||||
trackShareFlow(metadata: ShareFlowMetadata): void {
|
||||
this.pushEvent('share_flow', {
|
||||
step: metadata.step,
|
||||
source: metadata.source
|
||||
source: metadata.source,
|
||||
view_mode: metadata.view_mode,
|
||||
is_app_mode: metadata.is_app_mode
|
||||
})
|
||||
}
|
||||
|
||||
@@ -333,7 +342,8 @@ export class GtmTelemetryProvider implements TelemetryProvider {
|
||||
|
||||
trackUiButtonClicked(metadata: UiButtonClickMetadata): void {
|
||||
this.pushEvent('ui_button_click', {
|
||||
button_id: metadata.button_id
|
||||
button_id: metadata.button_id,
|
||||
element_group: metadata.element_group
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ vi.mock('@/composables/auth/useCurrentUser', () => ({
|
||||
|
||||
vi.mock('@/composables/useAppMode', () => ({
|
||||
useAppMode: () => ({
|
||||
mode: { value: 'workflow' },
|
||||
mode: { value: 'graph' },
|
||||
isAppMode: { value: false }
|
||||
})
|
||||
}))
|
||||
@@ -61,6 +61,7 @@ import type {
|
||||
EnterLinearMetadata,
|
||||
ShareFlowMetadata,
|
||||
SurveyResponses,
|
||||
TemplateFilterMetadata,
|
||||
TemplateLibraryClosedMetadata,
|
||||
TemplateLibraryMetadata,
|
||||
TemplateMetadata,
|
||||
@@ -74,6 +75,10 @@ const waitForMixpanelInit = () =>
|
||||
|
||||
type ConfigWindow = { __CONFIG__?: { mixpanel_token?: string } }
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
describe('MixpanelTelemetryProvider — without configured token', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
@@ -165,6 +170,44 @@ describe('MixpanelTelemetryProvider — with configured token', () => {
|
||||
expect(mockMixpanel.track).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('tracks enabled funnel events by default', async () => {
|
||||
const provider = new MixpanelTelemetryProvider()
|
||||
await waitForMixpanelInit()
|
||||
mockMixpanel.track.mockClear()
|
||||
|
||||
const templateFilterMetadata: TemplateFilterMetadata = {
|
||||
selected_models: [],
|
||||
selected_use_cases: [],
|
||||
selected_runs_on: [],
|
||||
sort_by: 'default',
|
||||
filtered_count: 1,
|
||||
total_count: 2
|
||||
}
|
||||
|
||||
provider.trackSettingChanged({ setting_id: 'theme' })
|
||||
provider.trackTemplateFilterChanged(templateFilterMetadata)
|
||||
provider.trackUiButtonClicked({
|
||||
button_id: 'sidebar_settings_button_clicked',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
|
||||
expect(mockMixpanel.track).toHaveBeenCalledWith(
|
||||
TelemetryEvents.SETTING_CHANGED,
|
||||
{ setting_id: 'theme' }
|
||||
)
|
||||
expect(mockMixpanel.track).toHaveBeenCalledWith(
|
||||
TelemetryEvents.TEMPLATE_FILTER_CHANGED,
|
||||
templateFilterMetadata
|
||||
)
|
||||
expect(mockMixpanel.track).toHaveBeenCalledWith(
|
||||
TelemetryEvents.UI_BUTTON_CLICKED,
|
||||
{
|
||||
button_id: 'sidebar_settings_button_clicked',
|
||||
element_group: 'sidebar'
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it.for<
|
||||
[
|
||||
'opened' | 'requested' | 'completed',
|
||||
@@ -285,7 +328,11 @@ describe('MixpanelTelemetryProvider — direct event tracking methods', () => {
|
||||
default_view: 'graph'
|
||||
}
|
||||
const enterLinearMetadata: EnterLinearMetadata = {}
|
||||
const shareFlowMetadata: ShareFlowMetadata = { step: 'dialog_opened' }
|
||||
const shareFlowMetadata: ShareFlowMetadata = {
|
||||
step: 'dialog_opened',
|
||||
view_mode: 'graph',
|
||||
is_app_mode: false
|
||||
}
|
||||
const authMetadata: AuthMetadata = {}
|
||||
|
||||
it.for<
|
||||
@@ -391,6 +438,7 @@ describe('MixpanelTelemetryProvider — direct event tracking methods', () => {
|
||||
const provider = new MixpanelTelemetryProvider()
|
||||
await waitForMixpanelInit()
|
||||
mockMixpanel.track.mockClear()
|
||||
localStorage.setItem('Comfy.MenuPosition.Docked', 'false')
|
||||
|
||||
provider.trackRunButton({
|
||||
subscribe_to_run: true,
|
||||
@@ -403,8 +451,9 @@ describe('MixpanelTelemetryProvider — direct event tracking methods', () => {
|
||||
subscribe_to_run: true,
|
||||
workflow_type: 'custom',
|
||||
trigger_source: 'button',
|
||||
view_mode: 'workflow',
|
||||
is_app_mode: false
|
||||
view_mode: 'graph',
|
||||
is_app_mode: false,
|
||||
dock_state: 'floating'
|
||||
})
|
||||
)
|
||||
})
|
||||
@@ -424,6 +473,8 @@ describe('MixpanelTelemetryProvider — direct event tracking methods', () => {
|
||||
provider.trackShareFlow({
|
||||
step: 'link_copied',
|
||||
source: 'app_mode',
|
||||
view_mode: 'app',
|
||||
is_app_mode: true,
|
||||
share_id: 'share-1'
|
||||
})
|
||||
|
||||
@@ -443,7 +494,9 @@ describe('MixpanelTelemetryProvider — direct event tracking methods', () => {
|
||||
TelemetryEvents.SHARE_FLOW,
|
||||
{
|
||||
step: 'link_copied',
|
||||
source: 'app_mode'
|
||||
source: 'app_mode',
|
||||
view_mode: 'app',
|
||||
is_app_mode: true
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -47,6 +47,7 @@ import type {
|
||||
import { remoteConfig } from '@/platform/remoteConfig/remoteConfig'
|
||||
import type { RemoteConfig } from '@/platform/remoteConfig/types'
|
||||
import { TelemetryEvents } from '../../types'
|
||||
import { getActionbarDockState } from '../../utils/getActionbarDockState'
|
||||
import { normalizeSurveyResponses } from '../../utils/surveyNormalization'
|
||||
|
||||
const DEFAULT_DISABLED_EVENTS = [
|
||||
@@ -55,13 +56,10 @@ const DEFAULT_DISABLED_EVENTS = [
|
||||
TelemetryEvents.TAB_COUNT_TRACKING,
|
||||
TelemetryEvents.NODE_SEARCH,
|
||||
TelemetryEvents.NODE_SEARCH_RESULT_SELECTED,
|
||||
TelemetryEvents.TEMPLATE_FILTER_CHANGED,
|
||||
TelemetryEvents.SETTING_CHANGED,
|
||||
TelemetryEvents.HELP_CENTER_OPENED,
|
||||
TelemetryEvents.HELP_RESOURCE_CLICKED,
|
||||
TelemetryEvents.HELP_CENTER_CLOSED,
|
||||
TelemetryEvents.WORKFLOW_CREATED,
|
||||
TelemetryEvents.UI_BUTTON_CLICKED
|
||||
TelemetryEvents.WORKFLOW_CREATED
|
||||
] as const satisfies TelemetryEventName[]
|
||||
|
||||
const TELEMETRY_EVENT_SET = new Set<TelemetryEventName>(
|
||||
@@ -297,7 +295,8 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
|
||||
toolkit_node_names: executionContext.toolkit_node_names,
|
||||
trigger_source: options?.trigger_source,
|
||||
view_mode: mode.value,
|
||||
is_app_mode: isAppMode.value
|
||||
is_app_mode: isAppMode.value,
|
||||
dock_state: getActionbarDockState()
|
||||
}
|
||||
|
||||
this.trackEvent(TelemetryEvents.RUN_BUTTON_CLICKED, runButtonProperties)
|
||||
|
||||
@@ -276,25 +276,50 @@ describe('PostHogTelemetryProvider', () => {
|
||||
|
||||
provider.trackShareLinkOpened({
|
||||
share_id: 'share-1',
|
||||
is_authenticated: true
|
||||
is_authenticated: true,
|
||||
view_mode: 'graph',
|
||||
is_app_mode: false
|
||||
})
|
||||
provider.trackShareFlow({
|
||||
step: 'link_created',
|
||||
source: 'app_mode',
|
||||
share_id: 'share-1',
|
||||
view_mode: 'app',
|
||||
is_app_mode: true
|
||||
})
|
||||
provider.trackSharedWorkflowRun({
|
||||
job_id: 'job-1',
|
||||
share_id: 'share-1'
|
||||
share_id: 'share-1',
|
||||
view_mode: 'app',
|
||||
is_app_mode: true
|
||||
})
|
||||
|
||||
expect(hoisted.mockCapture).toHaveBeenCalledWith(
|
||||
TelemetryEvents.SHARE_LINK_OPENED,
|
||||
{
|
||||
share_id: 'share-1',
|
||||
is_authenticated: true
|
||||
is_authenticated: true,
|
||||
view_mode: 'graph',
|
||||
is_app_mode: false
|
||||
}
|
||||
)
|
||||
expect(hoisted.mockCapture).toHaveBeenCalledWith(
|
||||
TelemetryEvents.SHARE_FLOW,
|
||||
{
|
||||
step: 'link_created',
|
||||
source: 'app_mode',
|
||||
share_id: 'share-1',
|
||||
view_mode: 'app',
|
||||
is_app_mode: true
|
||||
}
|
||||
)
|
||||
expect(hoisted.mockCapture).toHaveBeenCalledWith(
|
||||
TelemetryEvents.SHARED_WORKFLOW_RUN,
|
||||
{
|
||||
job_id: 'job-1',
|
||||
share_id: 'share-1'
|
||||
share_id: 'share-1',
|
||||
view_mode: 'app',
|
||||
is_app_mode: true
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -444,6 +469,48 @@ describe('PostHogTelemetryProvider', () => {
|
||||
{}
|
||||
)
|
||||
})
|
||||
|
||||
it('captures enabled funnel events by default', async () => {
|
||||
const provider = createProvider()
|
||||
await vi.dynamicImportSettled()
|
||||
|
||||
provider.trackSettingChanged({ setting_id: 'theme' })
|
||||
provider.trackTemplateFilterChanged({
|
||||
selected_models: [],
|
||||
selected_use_cases: [],
|
||||
selected_runs_on: [],
|
||||
sort_by: 'default',
|
||||
filtered_count: 1,
|
||||
total_count: 2
|
||||
})
|
||||
provider.trackUiButtonClicked({
|
||||
button_id: 'sidebar_settings_button_clicked',
|
||||
element_group: 'sidebar'
|
||||
})
|
||||
|
||||
expect(hoisted.mockCapture).toHaveBeenCalledWith(
|
||||
TelemetryEvents.SETTING_CHANGED,
|
||||
{ setting_id: 'theme' }
|
||||
)
|
||||
expect(hoisted.mockCapture).toHaveBeenCalledWith(
|
||||
TelemetryEvents.TEMPLATE_FILTER_CHANGED,
|
||||
{
|
||||
selected_models: [],
|
||||
selected_use_cases: [],
|
||||
selected_runs_on: [],
|
||||
sort_by: 'default',
|
||||
filtered_count: 1,
|
||||
total_count: 2
|
||||
}
|
||||
)
|
||||
expect(hoisted.mockCapture).toHaveBeenCalledWith(
|
||||
TelemetryEvents.UI_BUTTON_CLICKED,
|
||||
{
|
||||
button_id: 'sidebar_settings_button_clicked',
|
||||
element_group: 'sidebar'
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('survey tracking', () => {
|
||||
|
||||
@@ -45,6 +45,7 @@ import type {
|
||||
WorkflowSavedMetadata
|
||||
} from '../../types'
|
||||
import { TelemetryEvents } from '../../types'
|
||||
import { getActionbarDockState } from '../../utils/getActionbarDockState'
|
||||
import { getExecutionContext } from '../../utils/getExecutionContext'
|
||||
import { normalizeSurveyResponses } from '../../utils/surveyNormalization'
|
||||
|
||||
@@ -54,13 +55,10 @@ const DEFAULT_DISABLED_EVENTS = [
|
||||
TelemetryEvents.TAB_COUNT_TRACKING,
|
||||
TelemetryEvents.NODE_SEARCH,
|
||||
TelemetryEvents.NODE_SEARCH_RESULT_SELECTED,
|
||||
TelemetryEvents.TEMPLATE_FILTER_CHANGED,
|
||||
TelemetryEvents.SETTING_CHANGED,
|
||||
TelemetryEvents.HELP_CENTER_OPENED,
|
||||
TelemetryEvents.HELP_RESOURCE_CLICKED,
|
||||
TelemetryEvents.HELP_CENTER_CLOSED,
|
||||
TelemetryEvents.WORKFLOW_CREATED,
|
||||
TelemetryEvents.UI_BUTTON_CLICKED
|
||||
TelemetryEvents.WORKFLOW_CREATED
|
||||
] as const satisfies TelemetryEventName[]
|
||||
|
||||
const TELEMETRY_EVENT_SET = new Set<TelemetryEventName>(
|
||||
@@ -395,7 +393,8 @@ export class PostHogTelemetryProvider implements TelemetryProvider {
|
||||
toolkit_node_names: executionContext.toolkit_node_names,
|
||||
trigger_source: options?.trigger_source,
|
||||
view_mode: mode.value,
|
||||
is_app_mode: isAppMode.value
|
||||
is_app_mode: isAppMode.value,
|
||||
dock_state: getActionbarDockState()
|
||||
}
|
||||
|
||||
this.trackEvent(TelemetryEvents.RUN_BUTTON_CLICKED, runButtonProperties)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
* 3. Check dist/assets/*.js files contain no tracking code
|
||||
*/
|
||||
|
||||
import type { AppMode } from '@/composables/useAppMode'
|
||||
import type { SubscriptionDialogReason } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
|
||||
import type { TierKey } from '@/platform/cloud/subscription/constants/tierPricing'
|
||||
import type { BillingCycle } from '@/platform/cloud/subscription/utils/subscriptionTierRank'
|
||||
@@ -70,8 +71,9 @@ export interface RunButtonProperties {
|
||||
has_toolkit_nodes: boolean
|
||||
toolkit_node_names: string[]
|
||||
trigger_source?: ExecutionTriggerSource
|
||||
view_mode?: string
|
||||
is_app_mode?: boolean
|
||||
view_mode: AppMode
|
||||
is_app_mode: boolean
|
||||
dock_state: ActionbarDockState
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,8 +122,12 @@ export interface ExecutionSuccessMetadata {
|
||||
export interface SharedWorkflowRunMetadata {
|
||||
job_id: string
|
||||
share_id: string
|
||||
view_mode: AppMode
|
||||
is_app_mode: boolean
|
||||
}
|
||||
|
||||
export type ActionbarDockState = 'docked' | 'floating'
|
||||
|
||||
/**
|
||||
* Template metadata for workflow tracking
|
||||
*/
|
||||
@@ -197,11 +203,15 @@ export interface ShareFlowMetadata {
|
||||
step: ShareFlowStep
|
||||
source?: 'app_mode' | 'graph_mode'
|
||||
share_id?: string
|
||||
view_mode: AppMode
|
||||
is_app_mode: boolean
|
||||
}
|
||||
|
||||
export interface ShareLinkOpenedMetadata {
|
||||
share_id: string
|
||||
is_authenticated: boolean
|
||||
view_mode: AppMode
|
||||
is_app_mode: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -327,8 +337,8 @@ export interface TemplateFilterMetadata {
|
||||
* UI button click tracking metadata
|
||||
*/
|
||||
export interface UiButtonClickMetadata {
|
||||
/** Canonical identifier for the button (e.g., "comfy_logo") */
|
||||
button_id: string
|
||||
element_group: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
23
src/platform/telemetry/utils/getActionbarDockState.test.ts
Normal file
23
src/platform/telemetry/utils/getActionbarDockState.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { getActionbarDockState } from './getActionbarDockState'
|
||||
|
||||
describe('getActionbarDockState', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
it('returns docked when no preference is stored', () => {
|
||||
expect(getActionbarDockState()).toBe('docked')
|
||||
})
|
||||
|
||||
it('returns docked when the stored preference is true', () => {
|
||||
localStorage.setItem('Comfy.MenuPosition.Docked', 'true')
|
||||
expect(getActionbarDockState()).toBe('docked')
|
||||
})
|
||||
|
||||
it('returns floating when the stored preference is false', () => {
|
||||
localStorage.setItem('Comfy.MenuPosition.Docked', 'false')
|
||||
expect(getActionbarDockState()).toBe('floating')
|
||||
})
|
||||
})
|
||||
7
src/platform/telemetry/utils/getActionbarDockState.ts
Normal file
7
src/platform/telemetry/utils/getActionbarDockState.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { ActionbarDockState } from '@/platform/telemetry/types'
|
||||
|
||||
export function getActionbarDockState(): ActionbarDockState {
|
||||
return localStorage.getItem('Comfy.MenuPosition.Docked') === 'false'
|
||||
? 'floating'
|
||||
: 'docked'
|
||||
}
|
||||
@@ -26,9 +26,9 @@ import { refAutoReset } from '@vueuse/core'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import Input from '@/components/ui/input/Input.vue'
|
||||
import { useAppMode } from '@/composables/useAppMode'
|
||||
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useShareFlowContext } from '@/platform/workflow/sharing/composables/useShareFlowContext'
|
||||
|
||||
const { url, shareId } = defineProps<{
|
||||
url: string
|
||||
@@ -36,7 +36,7 @@ const { url, shareId } = defineProps<{
|
||||
}>()
|
||||
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
const { isAppMode } = useAppMode()
|
||||
const shareFlowContext = useShareFlowContext()
|
||||
const copied = refAutoReset(false, 2000)
|
||||
|
||||
async function handleCopy() {
|
||||
@@ -44,7 +44,7 @@ async function handleCopy() {
|
||||
copied.value = true
|
||||
useTelemetry()?.trackShareFlow({
|
||||
step: 'link_copied',
|
||||
source: isAppMode.value ? 'app_mode' : 'graph_mode',
|
||||
...shareFlowContext.value,
|
||||
share_id: shareId
|
||||
})
|
||||
}
|
||||
|
||||
@@ -387,6 +387,8 @@ describe('ShareWorkflowDialogContent', () => {
|
||||
expect(mockTrackShareFlow).toHaveBeenCalledWith({
|
||||
step: 'link_created',
|
||||
source: 'graph_mode',
|
||||
view_mode: 'graph',
|
||||
is_app_mode: false,
|
||||
share_id: 'test-123'
|
||||
})
|
||||
})
|
||||
@@ -407,6 +409,8 @@ describe('ShareWorkflowDialogContent', () => {
|
||||
expect(mockTrackShareFlow).toHaveBeenCalledWith({
|
||||
step: 'link_copied',
|
||||
source: 'graph_mode',
|
||||
view_mode: 'graph',
|
||||
is_app_mode: false,
|
||||
share_id: 'copy-123'
|
||||
})
|
||||
})
|
||||
|
||||
@@ -206,7 +206,7 @@ import type {
|
||||
import { useWorkflowShareService } from '@/platform/workflow/sharing/services/workflowShareService'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
|
||||
import { useAppMode } from '@/composables/useAppMode'
|
||||
import { useShareFlowContext } from '@/platform/workflow/sharing/composables/useShareFlowContext'
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { appendJsonExt } from '@/utils/formatUtil'
|
||||
@@ -223,11 +223,7 @@ const publishDialog = useComfyHubPublishDialog()
|
||||
const shareService = useWorkflowShareService()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const workflowService = useWorkflowService()
|
||||
const { isAppMode } = useAppMode()
|
||||
|
||||
function getShareSource() {
|
||||
return isAppMode.value ? 'app_mode' : ('graph_mode' as const)
|
||||
}
|
||||
const shareFlowContext = useShareFlowContext()
|
||||
|
||||
type DialogState = 'loading' | 'unsaved' | 'ready' | 'shared' | 'stale'
|
||||
type DialogMode = 'shareLink' | 'publishToHub'
|
||||
@@ -355,7 +351,7 @@ async function refreshDialogState() {
|
||||
dialogState.value = 'unsaved'
|
||||
useTelemetry()?.trackShareFlow({
|
||||
step: 'save_prompted',
|
||||
source: getShareSource()
|
||||
...shareFlowContext.value
|
||||
})
|
||||
if (workflow) {
|
||||
workflowName.value = stripJsonExtension(workflow.filename)
|
||||
@@ -440,7 +436,7 @@ const {
|
||||
acknowledged.value = false
|
||||
useTelemetry()?.trackShareFlow({
|
||||
step: 'link_created',
|
||||
source: getShareSource(),
|
||||
...shareFlowContext.value,
|
||||
share_id: result.shareId
|
||||
})
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ShareWorkflowDialogContent from '@/platform/workflow/sharing/components/ShareWorkflowDialogContent.vue'
|
||||
import { useAppMode } from '@/composables/useAppMode'
|
||||
import { useShareFlowContext } from '@/platform/workflow/sharing/composables/useShareFlowContext'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
@@ -15,7 +15,7 @@ export function useShareDialog() {
|
||||
const dialogStore = useDialogStore()
|
||||
const { pruneLinearData } = useAppModeStore()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { isAppMode } = useAppMode()
|
||||
const shareFlowContext = useShareFlowContext()
|
||||
|
||||
function hide() {
|
||||
dialogStore.closeDialog({ key: DIALOG_KEY })
|
||||
@@ -54,14 +54,10 @@ export function useShareDialog() {
|
||||
share()
|
||||
}
|
||||
|
||||
function getShareSource() {
|
||||
return isAppMode.value ? 'app_mode' : ('graph_mode' as const)
|
||||
}
|
||||
|
||||
function showShareDialog() {
|
||||
useTelemetry()?.trackShareFlow({
|
||||
step: 'dialog_opened',
|
||||
source: getShareSource()
|
||||
...shareFlowContext.value
|
||||
})
|
||||
dialogService.showLayoutDialog({
|
||||
key: DIALOG_KEY,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useAppMode } from '@/composables/useAppMode'
|
||||
import type { ShareFlowMetadata } from '@/platform/telemetry/types'
|
||||
|
||||
type ShareFlowContext = Pick<
|
||||
ShareFlowMetadata,
|
||||
'source' | 'view_mode' | 'is_app_mode'
|
||||
>
|
||||
|
||||
export function useShareFlowContext() {
|
||||
const { mode, isAppMode } = useAppMode()
|
||||
return computed<ShareFlowContext>(() => ({
|
||||
source: isAppMode.value ? 'app_mode' : 'graph_mode',
|
||||
view_mode: mode.value,
|
||||
is_app_mode: isAppMode.value
|
||||
}))
|
||||
}
|
||||
@@ -38,6 +38,13 @@ vi.mock('@/composables/auth/useCurrentUser', () => ({
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useAppMode', () => ({
|
||||
useAppMode: () => ({
|
||||
mode: { value: 'graph' },
|
||||
isAppMode: { value: false }
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/telemetry', () => ({
|
||||
useTelemetry: () => ({
|
||||
trackShareLinkOpened: mockTrackShareLinkOpened
|
||||
@@ -255,7 +262,9 @@ describe('useSharedWorkflowUrlLoader', () => {
|
||||
)
|
||||
expect(mockTrackShareLinkOpened).toHaveBeenCalledWith({
|
||||
share_id: 'share-id-1',
|
||||
is_authenticated: false
|
||||
is_authenticated: false,
|
||||
view_mode: 'graph',
|
||||
is_app_mode: false
|
||||
})
|
||||
expect(preservedQueryMocks.capturePreservedQuery).toHaveBeenCalledWith(
|
||||
'share_auth',
|
||||
@@ -281,7 +290,9 @@ describe('useSharedWorkflowUrlLoader', () => {
|
||||
expect(loaded).toBe('loaded')
|
||||
expect(mockTrackShareLinkOpened).toHaveBeenCalledWith({
|
||||
share_id: 'share-id-1',
|
||||
is_authenticated: true
|
||||
is_authenticated: true,
|
||||
view_mode: 'graph',
|
||||
is_app_mode: false
|
||||
})
|
||||
expect(preservedQueryMocks.capturePreservedQuery).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { useAppMode } from '@/composables/useAppMode'
|
||||
import { useWorkflowTemplateSelectorDialog } from '@/composables/useWorkflowTemplateSelectorDialog'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import OpenSharedWorkflowDialogContent from '@/platform/workflow/sharing/components/OpenSharedWorkflowDialogContent.vue'
|
||||
@@ -45,6 +46,7 @@ export function useSharedWorkflowUrlLoader() {
|
||||
const dialogStore = useDialogStore()
|
||||
const templateSelectorDialog = useWorkflowTemplateSelectorDialog()
|
||||
const { isLoggedIn } = useCurrentUser()
|
||||
const { mode, isAppMode } = useAppMode()
|
||||
const SHARE_NAMESPACE = PRESERVED_QUERY_NAMESPACES.SHARE
|
||||
|
||||
function isValidParameter(param: string): boolean {
|
||||
@@ -146,7 +148,9 @@ export function useSharedWorkflowUrlLoader() {
|
||||
|
||||
useTelemetry()?.trackShareLinkOpened({
|
||||
share_id: shareParam,
|
||||
is_authenticated: isLoggedIn.value
|
||||
is_authenticated: isLoggedIn.value,
|
||||
view_mode: mode.value,
|
||||
is_app_mode: isAppMode.value
|
||||
})
|
||||
if (!isLoggedIn.value) {
|
||||
capturePreservedQuery(
|
||||
|
||||
@@ -57,7 +57,8 @@ async function runButtonClick(e: Event) {
|
||||
|
||||
if (batchCount.value > 1) {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'queue_run_multiple_batches_submitted'
|
||||
button_id: 'queue_run_multiple_batches_submitted',
|
||||
element_group: 'app_mode'
|
||||
})
|
||||
}
|
||||
await commandStore.execute(commandId, {
|
||||
|
||||
@@ -674,7 +674,8 @@ const handleToggleAdvanced = () => {
|
||||
|
||||
const handleEnterSubgraph = () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'graph_node_open_subgraph_clicked'
|
||||
button_id: 'graph_node_open_subgraph_clicked',
|
||||
element_group: 'graph_node'
|
||||
})
|
||||
const graph = app.rootGraph
|
||||
if (!graph) {
|
||||
|
||||
@@ -103,7 +103,8 @@ export const useDialogService = () => {
|
||||
size: 'lg',
|
||||
onClose: () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'error_dialog_closed'
|
||||
button_id: 'error_dialog_closed',
|
||||
element_group: 'error_dialog'
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -169,7 +170,8 @@ export const useDialogService = () => {
|
||||
size: 'lg',
|
||||
onClose: () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'error_dialog_closed'
|
||||
button_id: 'error_dialog_closed',
|
||||
element_group: 'error_dialog'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,21 @@ const {
|
||||
mockTrackExecutionSuccess: vi.fn(),
|
||||
mockTrackSharedWorkflowRun: vi.fn()
|
||||
}))
|
||||
|
||||
const mockAppModeState = vi.hoisted(() => ({
|
||||
mode: { value: 'graph' },
|
||||
isAppMode: { value: false }
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useAppMode', () => ({
|
||||
useAppMode: () => mockAppModeState
|
||||
}))
|
||||
|
||||
beforeEach(() => {
|
||||
mockAppModeState.mode.value = 'graph'
|
||||
mockAppModeState.isAppMode.value = false
|
||||
})
|
||||
|
||||
import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
|
||||
@@ -1128,7 +1143,9 @@ describe('useExecutionStore - WebSocket event handlers', () => {
|
||||
})
|
||||
expect(mockTrackSharedWorkflowRun).toHaveBeenCalledWith({
|
||||
job_id: 'job-1',
|
||||
share_id: 'share-1'
|
||||
share_id: 'share-1',
|
||||
view_mode: 'graph',
|
||||
is_app_mode: false
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1148,7 +1165,33 @@ describe('useExecutionStore - WebSocket event handlers', () => {
|
||||
|
||||
expect(mockTrackSharedWorkflowRun).toHaveBeenCalledWith({
|
||||
job_id: 'job-1',
|
||||
share_id: 'share-1'
|
||||
share_id: 'share-1',
|
||||
view_mode: 'graph',
|
||||
is_app_mode: false
|
||||
})
|
||||
})
|
||||
|
||||
it('attributes shared workflow run to queue-time mode, not completion-time mode', () => {
|
||||
const workflow = createQueuedWorkflow()
|
||||
workflow.shareId = 'share-1'
|
||||
store.storeJob({
|
||||
nodes: ['a'],
|
||||
id: 'job-1',
|
||||
promptOutput: {
|
||||
a: createPromptNode('Node A', 'NodeA')
|
||||
},
|
||||
workflow
|
||||
})
|
||||
|
||||
mockAppModeState.mode.value = 'app'
|
||||
mockAppModeState.isAppMode.value = true
|
||||
fire('execution_success', { prompt_id: 'job-1', timestamp: 0 })
|
||||
|
||||
expect(mockTrackSharedWorkflowRun).toHaveBeenCalledWith({
|
||||
job_id: 'job-1',
|
||||
share_id: 'share-1',
|
||||
view_mode: 'graph',
|
||||
is_app_mode: false
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,6 +2,8 @@ import { defineStore } from 'pinia'
|
||||
import { computed, ref, shallowRef } from 'vue'
|
||||
|
||||
import { useNodeProgressText } from '@/composables/node/useNodeProgressText'
|
||||
import type { AppMode } from '@/composables/useAppMode'
|
||||
import { useAppMode } from '@/composables/useAppMode'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
|
||||
@@ -60,6 +62,12 @@ interface QueuedJob {
|
||||
* `workflow.shareId`, which can gain attribution after the job was queued.
|
||||
*/
|
||||
shareId?: string
|
||||
/**
|
||||
* View-mode attribution snapshotted at queue time, so mode switches during
|
||||
* the run don't misattribute completion events.
|
||||
*/
|
||||
viewMode?: AppMode
|
||||
isAppMode?: boolean
|
||||
}
|
||||
|
||||
function buildExecutionNodeLookup(
|
||||
@@ -87,6 +95,7 @@ export const useExecutionStore = defineStore('execution', () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const canvasStore = useCanvasStore()
|
||||
const executionErrorStore = useExecutionErrorStore()
|
||||
const { mode, isAppMode } = useAppMode()
|
||||
|
||||
const clientId = ref<string | null>(null)
|
||||
const activeJobId = ref<JobId | null>(null)
|
||||
@@ -310,7 +319,9 @@ export const useExecutionStore = defineStore('execution', () => {
|
||||
if (queuedJob.shareId) {
|
||||
telemetry?.trackSharedWorkflowRun({
|
||||
job_id: jobId,
|
||||
share_id: queuedJob.shareId
|
||||
share_id: queuedJob.shareId,
|
||||
view_mode: queuedJob.viewMode ?? mode.value,
|
||||
is_app_mode: queuedJob.isAppMode ?? isAppMode.value
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -594,6 +605,8 @@ export const useExecutionStore = defineStore('execution', () => {
|
||||
queuedJob.nodeLookup = buildExecutionNodeLookup(promptOutput)
|
||||
queuedJob.workflow = workflow
|
||||
queuedJob.shareId = workflow?.shareId
|
||||
queuedJob.viewMode = mode.value
|
||||
queuedJob.isAppMode = isAppMode.value
|
||||
const wid = workflow?.activeState?.id ?? workflow?.initialState?.id
|
||||
if (wid) {
|
||||
jobIdToWorkflowId.value.set(id, wid)
|
||||
|
||||
Reference in New Issue
Block a user