diff --git a/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts b/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts index 4da27c807..f4d7a63df 100644 --- a/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts +++ b/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts @@ -1,7 +1,13 @@ import type { OverridedMixpanel } from 'mixpanel-browser' +import { app } from '@/scripts/app' +import { useNodeDefStore } from '@/stores/nodeDefStore' +import { NodeSourceType } from '@/types/nodeSource' +import { collectAllNodes } from '@/utils/graphTraversalUtil' + import type { AuthMetadata, + CreditTopupMetadata, ExecutionContext, ExecutionErrorMetadata, ExecutionSuccessMetadata, @@ -282,13 +288,36 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { this.trackEvent(eventName) } + trackAddApiCreditButtonClicked(): void { + this.trackEvent(TelemetryEvents.ADD_API_CREDIT_BUTTON_CLICKED) + } + + trackMonthlySubscriptionSucceeded(): void { + this.trackEvent(TelemetryEvents.MONTHLY_SUBSCRIPTION_SUCCEEDED) + } + + trackApiCreditTopupButtonPurchaseClicked(amount: number): void { + const metadata: CreditTopupMetadata = { + credit_amount: amount + } + this.trackEvent( + TelemetryEvents.API_CREDIT_TOPUP_BUTTON_PURCHASE_CLICKED, + metadata + ) + } + trackRunButton(options?: { subscribe_to_run?: boolean }): void { if (this.isOnboardingMode) { // During onboarding, track basic run button click without workflow context this.trackEvent(TelemetryEvents.RUN_BUTTON_CLICKED, { subscribe_to_run: options?.subscribe_to_run || false, workflow_type: 'custom', - workflow_name: 'untitled' + workflow_name: 'untitled', + custom_node_count: 0, + total_node_count: 0, + subgraph_count: 0, + has_api_nodes: false, + api_node_names: [] }) return } @@ -298,7 +327,12 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { const runButtonProperties: RunButtonProperties = { subscribe_to_run: options?.subscribe_to_run || false, workflow_type: executionContext.is_template ? 'template' : 'custom', - workflow_name: executionContext.workflow_name ?? 'untitled' + workflow_name: executionContext.workflow_name ?? 'untitled', + custom_node_count: executionContext.custom_node_count, + 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 } this.trackEvent(TelemetryEvents.RUN_BUTTON_CLICKED, runButtonProperties) @@ -400,7 +434,13 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { // During onboarding, track basic execution without workflow context this.trackEvent(TelemetryEvents.EXECUTION_START, { is_template: false, - workflow_name: undefined + workflow_name: undefined, + custom_node_count: 0, + api_node_count: 0, + subgraph_count: 0, + total_node_count: 0, + has_api_nodes: false, + api_node_names: [] }) return } @@ -423,6 +463,61 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { void this.initializeComposables() } + let nodeCounts: { + custom_node_count: number + api_node_count: number + subgraph_count: number + total_node_count: number + has_api_nodes: boolean + api_node_names: string[] + } + try { + const nodeDefStore = useNodeDefStore() + const nodes = collectAllNodes(app.graph) + + let customNodeCount = 0 + let apiNodeCount = 0 + let subgraphCount = 0 + let totalNodeCount = 0 + let hasApiNodes = false + const apiNodeNames = new Set() + + for (const node of nodes) { + totalNodeCount += 1 + const nodeDef = nodeDefStore.nodeDefsByName[node.type ?? ''] + const isCustomNode = + nodeDef?.nodeSource?.type === NodeSourceType.CustomNodes + const isApiNode = nodeDef?.api_node === true + const isSubgraph = node.isSubgraphNode?.() === true + if (isCustomNode) customNodeCount += 1 + if (isApiNode) { + apiNodeCount += 1 + hasApiNodes = true + if (nodeDef?.name) apiNodeNames.add(nodeDef.name) + } + if (isSubgraph) subgraphCount += 1 + } + + nodeCounts = { + custom_node_count: customNodeCount, + api_node_count: apiNodeCount, + subgraph_count: subgraphCount, + total_node_count: totalNodeCount, + has_api_nodes: hasApiNodes, + api_node_names: Array.from(apiNodeNames) + } + } catch (error) { + console.error('Failed to compute node metrics:', error) + nodeCounts = { + custom_node_count: 0, + api_node_count: 0, + subgraph_count: 0, + total_node_count: 0, + has_api_nodes: false, + api_node_names: [] + } + } + if ( !this._composablesReady || !this._workflowStore || @@ -430,7 +525,8 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { ) { return { is_template: false, - workflow_name: undefined + workflow_name: undefined, + ...nodeCounts } } @@ -459,25 +555,29 @@ 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, + ...nodeCounts } } return { is_template: false, - workflow_name: activeWorkflow.filename + workflow_name: activeWorkflow.filename, + ...nodeCounts } } return { is_template: false, - workflow_name: undefined + workflow_name: undefined, + ...nodeCounts } } catch (error) { console.error('Failed to get execution context:', error) return { is_template: false, - workflow_name: undefined + workflow_name: undefined, + ...nodeCounts } } } diff --git a/src/platform/telemetry/types.ts b/src/platform/telemetry/types.ts index 57cfa5a57..7823791e1 100644 --- a/src/platform/telemetry/types.ts +++ b/src/platform/telemetry/types.ts @@ -42,6 +42,11 @@ export interface RunButtonProperties { subscribe_to_run: boolean workflow_type: 'template' | 'custom' workflow_name: string + total_node_count: number + custom_node_count: number + subgraph_count: number + has_api_nodes: boolean + api_node_names: string[] } /** @@ -57,6 +62,13 @@ 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 + total_node_count: number + has_api_nodes: boolean + api_node_names: string[] } /** @@ -76,6 +88,13 @@ export interface ExecutionSuccessMetadata { jobId: string } +/** + * API credit top-up purchase metadata + */ +export interface CreditTopupMetadata { + credit_amount: number +} + /** * Template metadata for workflow tracking */ @@ -183,6 +202,9 @@ export interface TelemetryProvider { // Subscription flow events trackSubscription(event: 'modal_opened' | 'subscribe_clicked'): void + trackMonthlySubscriptionSucceeded(): void + trackAddApiCreditButtonClicked(): void + trackApiCreditTopupButtonPurchaseClicked(amount: number): void trackRunButton(options?: { subscribe_to_run?: boolean }): void // Survey flow events @@ -236,6 +258,10 @@ export const TelemetryEvents = { 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', + ADD_API_CREDIT_BUTTON_CLICKED: 'app:add_api_credit_button_clicked', + API_CREDIT_TOPUP_BUTTON_PURCHASE_CLICKED: + 'app:api_credit_topup_button_purchase_clicked', // Onboarding Survey USER_SURVEY_OPENED: 'app:user_survey_opened', @@ -283,6 +309,7 @@ export type TelemetryEventProperties = | RunButtonProperties | ExecutionErrorMetadata | ExecutionSuccessMetadata + | CreditTopupMetadata | WorkflowImportMetadata | TemplateLibraryMetadata | TemplateLibraryClosedMetadata