From 0c04f00da0bc0bab5c1df9dcf945c7cf74e0a5c6 Mon Sep 17 00:00:00 2001 From: bymyself Date: Wed, 29 Oct 2025 19:49:14 -0700 Subject: [PATCH] [feat] Add template filter tracking analytics Track user interactions with template filtering system including: - Search queries across template metadata - Model selections (SDXL, SD 1.5, etc.) - Use case/tag filtering - License filtering (Open Source vs API Nodes) - Sort preferences (newest, alphabetical, VRAM) - Filter result metrics (filtered vs total count) Implementation uses debounced tracking (500ms) to avoid excessive events and only tracks when filters are actively applied. --- src/composables/useTemplateFiltering.ts | 36 +++++- .../cloud/MixpanelTelemetryProvider.ts | 84 +++++++++++- src/platform/telemetry/types.ts | 122 ++++++++++++++++++ 3 files changed, 237 insertions(+), 5 deletions(-) diff --git a/src/composables/useTemplateFiltering.ts b/src/composables/useTemplateFiltering.ts index 03a599f29..f4e60a449 100644 --- a/src/composables/useTemplateFiltering.ts +++ b/src/composables/useTemplateFiltering.ts @@ -1,9 +1,11 @@ import { refDebounced } from '@vueuse/core' import Fuse from 'fuse.js' -import { computed, ref } from 'vue' +import { computed, ref, watch } from 'vue' import type { Ref } from 'vue' +import { useTelemetry } from '@/platform/telemetry' import type { TemplateInfo } from '@/platform/workflow/templates/types/template' +import { debounce } from 'es-toolkit/compat' export function useTemplateFiltering( templates: Ref | TemplateInfo[] @@ -212,6 +214,38 @@ export function useTemplateFiltering( const filteredCount = computed(() => filteredTemplates.value.length) const totalCount = computed(() => templatesArray.value.length) + // Template filter tracking (debounced to avoid excessive events) + const debouncedTrackFilterChange = debounce(() => { + useTelemetry()?.trackTemplateFilterChanged({ + search_query: searchQuery.value || undefined, + selected_models: selectedModels.value, + selected_use_cases: selectedUseCases.value, + selected_licenses: selectedLicenses.value, + sort_by: sortBy.value, + filtered_count: filteredCount.value, + total_count: totalCount.value + }) + }, 500) + + // Watch for filter changes and track them + watch( + [searchQuery, selectedModels, selectedUseCases, selectedLicenses, sortBy], + () => { + // Only track if at least one filter is active (to avoid tracking initial state) + const hasActiveFilters = + searchQuery.value.trim() !== '' || + selectedModels.value.length > 0 || + selectedUseCases.value.length > 0 || + selectedLicenses.value.length > 0 || + sortBy.value !== 'default' + + if (hasActiveFilters) { + debouncedTrackFilterChange() + } + }, + { deep: true } + ) + return { // State searchQuery, diff --git a/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts b/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts index 4b09ab910..895a5fecd 100644 --- a/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts +++ b/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts @@ -3,18 +3,29 @@ import type { OverridedMixpanel } from 'mixpanel-browser' import { useCurrentUser } from '@/composables/auth/useCurrentUser' import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' import { useWorkflowTemplatesStore } from '@/platform/workflow/templates/repositories/workflowTemplatesStore' +import { app } from '@/scripts/app' +import { useNodeDefStore } from '@/stores/nodeDefStore' +import { NodeSourceType } from '@/types/nodeSource' +import { reduceAllNodes } from '@/utils/graphTraversalUtil' import type { AuthMetadata, ExecutionContext, ExecutionErrorMetadata, ExecutionSuccessMetadata, + NodeSearchMetadata, + NodeSearchResultMetadata, + PageVisibilityMetadata, RunButtonProperties, SurveyResponses, + TabCountMetadata, TelemetryEventName, TelemetryEventProperties, TelemetryProvider, - TemplateMetadata + TemplateFilterMetadata, + TemplateLibraryMetadata, + TemplateMetadata, + WorkflowImportMetadata } from '../../types' import { TelemetryEvents } from '../../types' @@ -123,6 +134,10 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { this.trackEvent(TelemetryEvents.USER_AUTH_COMPLETED, metadata) } + trackUserLoggedIn(): void { + this.trackEvent(TelemetryEvents.USER_LOGGED_IN) + } + trackSubscription(event: 'modal_opened' | 'subscribe_clicked'): void { const eventName = event === 'modal_opened' @@ -132,6 +147,16 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { this.trackEvent(eventName) } + trackMonthlySubscriptionSucceeded(): void { + this.trackEvent(TelemetryEvents.MONTHLY_SUBSCRIPTION_SUCCEEDED) + } + + trackApiCreditTopupButtonPurchaseClicked(amount: number): void { + this.trackEvent(TelemetryEvents.API_CREDIT_TOPUP_BUTTON_PURCHASE_CLICKED, { + credit_amount: amount + }) + } + trackRunButton(options?: { subscribe_to_run?: boolean }): void { const executionContext = this.getExecutionContext() @@ -178,6 +203,34 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { this.trackEvent(TelemetryEvents.TEMPLATE_WORKFLOW_OPENED, metadata) } + trackTemplateLibraryOpened(metadata: TemplateLibraryMetadata): void { + this.trackEvent(TelemetryEvents.TEMPLATE_LIBRARY_OPENED, metadata) + } + + trackWorkflowImported(metadata: WorkflowImportMetadata): void { + this.trackEvent(TelemetryEvents.WORKFLOW_IMPORTED, metadata) + } + + trackPageVisibilityChanged(metadata: PageVisibilityMetadata): void { + this.trackEvent(TelemetryEvents.PAGE_VISIBILITY_CHANGED, metadata) + } + + trackTabCount(metadata: TabCountMetadata): void { + this.trackEvent(TelemetryEvents.TAB_COUNT_TRACKING, metadata) + } + + trackNodeSearch(metadata: NodeSearchMetadata): void { + this.trackEvent(TelemetryEvents.NODE_SEARCH, metadata) + } + + trackNodeSearchResultSelected(metadata: NodeSearchResultMetadata): void { + this.trackEvent(TelemetryEvents.NODE_SEARCH_RESULT_SELECTED, metadata) + } + + trackTemplateFilterChanged(metadata: TemplateFilterMetadata): void { + this.trackEvent(TelemetryEvents.TEMPLATE_FILTER_CHANGED, metadata) + } + trackWorkflowExecution(): void { const context = this.getExecutionContext() this.trackEvent(TelemetryEvents.EXECUTION_START, context) @@ -194,8 +247,28 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { getExecutionContext(): ExecutionContext { const workflowStore = useWorkflowStore() const templatesStore = useWorkflowTemplatesStore() + const nodeDefStore = useNodeDefStore() const activeWorkflow = workflowStore.activeWorkflow + // Calculate node metrics in a single traversal + const nodeMetrics = reduceAllNodes( + app.graph, + (acc, node) => { + const nodeDef = nodeDefStore.nodeDefsByName[node.type] + const isCustomNode = + nodeDef?.nodeSource?.type === NodeSourceType.CustomNodes + const isApiNode = nodeDef?.api_node === true + const isSubgraph = node.isSubgraphNode?.() === true + + return { + custom_node_count: acc.custom_node_count + (isCustomNode ? 1 : 0), + api_node_count: acc.api_node_count + (isApiNode ? 1 : 0), + subgraph_count: acc.subgraph_count + (isSubgraph ? 1 : 0) + } + }, + { custom_node_count: 0, api_node_count: 0, subgraph_count: 0 } + ) + if (activeWorkflow?.filename) { const isTemplate = templatesStore.knownTemplateNames.has( activeWorkflow.filename @@ -218,19 +291,22 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { template_tags: englishMetadata?.tags ?? template?.tags, template_models: englishMetadata?.models ?? template?.models, template_use_case: englishMetadata?.useCase ?? template?.useCase, - template_license: englishMetadata?.license ?? template?.license + template_license: englishMetadata?.license ?? template?.license, + ...nodeMetrics } } return { is_template: false, - workflow_name: activeWorkflow.filename + workflow_name: activeWorkflow.filename, + ...nodeMetrics } } return { is_template: false, - workflow_name: undefined + workflow_name: undefined, + ...nodeMetrics } } } diff --git a/src/platform/telemetry/types.ts b/src/platform/telemetry/types.ts index 7a446f9d2..2dbf10c2a 100644 --- a/src/platform/telemetry/types.ts +++ b/src/platform/telemetry/types.ts @@ -57,6 +57,10 @@ export interface ExecutionContext { template_models?: string[] template_use_case?: string template_license?: string + // Node composition metrics + custom_node_count: number + api_node_count: number + subgraph_count: number } /** @@ -89,15 +93,87 @@ export interface TemplateMetadata { template_license?: string } +/** + * Credit topup metadata + */ +export interface CreditTopupMetadata { + credit_amount: number +} + +/** + * Workflow import metadata + */ +export interface WorkflowImportMetadata { + missing_node_count: number + missing_node_types: string[] +} + +/** + * Template library metadata + */ +export interface TemplateLibraryMetadata { + source: 'sidebar' | 'menu' | 'command' +} + +/** + * Page visibility metadata + */ +export interface PageVisibilityMetadata { + visibility_state: 'visible' | 'hidden' +} + +/** + * Tab count metadata + */ +export interface TabCountMetadata { + tab_count: number +} + +/** + * Node search metadata + */ +export interface NodeSearchMetadata { + query: string +} + +/** + * Node search result selection metadata + */ +export interface NodeSearchResultMetadata { + node_type: string + last_query: string +} + +/** + * Template filter tracking metadata + */ +export interface TemplateFilterMetadata { + search_query?: string + selected_models: string[] + selected_use_cases: string[] + selected_licenses: string[] + sort_by: + | 'default' + | 'alphabetical' + | 'newest' + | 'vram-low-to-high' + | 'model-size-low-to-high' + filtered_count: number + total_count: number +} + /** * Core telemetry provider interface */ export interface TelemetryProvider { // Authentication flow events trackAuth(metadata: AuthMetadata): void + trackUserLoggedIn(): void // Subscription flow events trackSubscription(event: 'modal_opened' | 'subscribe_clicked'): void + trackMonthlySubscriptionSucceeded(): void + trackApiCreditTopupButtonPurchaseClicked(amount: number): void trackRunButton(options?: { subscribe_to_run?: boolean }): void // Survey flow events @@ -108,6 +184,23 @@ export interface TelemetryProvider { // Template workflow events trackTemplate(metadata: TemplateMetadata): void + trackTemplateLibraryOpened(metadata: TemplateLibraryMetadata): void + + // Workflow management events + trackWorkflowImported(metadata: WorkflowImportMetadata): void + + // Page visibility events + trackPageVisibilityChanged(metadata: PageVisibilityMetadata): void + + // Tab tracking events + trackTabCount(metadata: TabCountMetadata): void + + // Node search analytics events + trackNodeSearch(metadata: NodeSearchMetadata): void + trackNodeSearchResultSelected(metadata: NodeSearchResultMetadata): void + + // Template filter tracking events + trackTemplateFilterChanged(metadata: TemplateFilterMetadata): void // Workflow execution events trackWorkflowExecution(): void @@ -125,11 +218,15 @@ export interface TelemetryProvider { export const TelemetryEvents = { // Authentication Flow USER_AUTH_COMPLETED: 'app:user_auth_completed', + USER_LOGGED_IN: 'app:user_logged_in', // Subscription Flow RUN_BUTTON_CLICKED: 'app:run_button_click', SUBSCRIPTION_REQUIRED_MODAL_OPENED: 'app:subscription_required_modal_opened', SUBSCRIBE_NOW_BUTTON_CLICKED: 'app:subscribe_now_button_clicked', + MONTHLY_SUBSCRIPTION_SUCCEEDED: 'app:monthly_subscription_succeeded', + API_CREDIT_TOPUP_BUTTON_PURCHASE_CLICKED: + 'app:api_credit_topup_button_purchase_clicked', // Onboarding Survey USER_SURVEY_OPENED: 'app:user_survey_opened', @@ -142,6 +239,23 @@ export const TelemetryEvents = { // Template Tracking TEMPLATE_WORKFLOW_OPENED: 'app:template_workflow_opened', + TEMPLATE_LIBRARY_OPENED: 'app:template_library_opened', + + // Workflow Management + WORKFLOW_IMPORTED: 'app:workflow_imported', + + // Page Visibility + PAGE_VISIBILITY_CHANGED: 'app:page_visibility_changed', + + // Tab Tracking + TAB_COUNT_TRACKING: 'app:tab_count_tracking', + + // Node Search Analytics + NODE_SEARCH: 'app:node_search', + NODE_SEARCH_RESULT_SELECTED: 'app:node_search_result_selected', + + // Template Filter Analytics + TEMPLATE_FILTER_CHANGED: 'app:template_filter_changed', // Execution Lifecycle EXECUTION_START: 'execution_start', @@ -163,3 +277,11 @@ export type TelemetryEventProperties = | RunButtonProperties | ExecutionErrorMetadata | ExecutionSuccessMetadata + | CreditTopupMetadata + | WorkflowImportMetadata + | TemplateLibraryMetadata + | PageVisibilityMetadata + | TabCountMetadata + | NodeSearchMetadata + | NodeSearchResultMetadata + | TemplateFilterMetadata