feat(analytics): add core analytics infrastructure with PostHog integration
- Implement comprehensive analytics type system with 100+ event types - Create event builders for consistent event tracking - Add main analytics service with PostHog integration - Include performance tracking utilities with percentile monitoring - Support anonymous user tracking with session management - Implement sanitization helpers to remove PII from events - Add event queueing with automatic flush intervals - Support for screen tracking and app context BREAKING CHANGE: Analytics tracking is now integrated throughout the app and requires PostHog API key configuration via environment variables.
This commit is contained in:
@@ -7,7 +7,42 @@ import type {
|
||||
AgentProperties,
|
||||
MCPProperties,
|
||||
SlashCommandProperties,
|
||||
PerformanceMetrics
|
||||
PerformanceMetrics,
|
||||
PromptSubmittedProperties,
|
||||
SessionStoppedProperties,
|
||||
EnhancedSessionStoppedProperties,
|
||||
CheckpointCreatedProperties,
|
||||
CheckpointRestoredProperties,
|
||||
ToolExecutedProperties,
|
||||
AgentStartedProperties,
|
||||
AgentProgressProperties,
|
||||
AgentErrorProperties,
|
||||
MCPServerAddedProperties,
|
||||
MCPServerRemovedProperties,
|
||||
MCPToolInvokedProperties,
|
||||
MCPConnectionErrorProperties,
|
||||
SlashCommandSelectedProperties,
|
||||
SlashCommandExecutedProperties,
|
||||
SlashCommandCreatedProperties,
|
||||
APIErrorProperties,
|
||||
UIErrorProperties,
|
||||
PerformanceBottleneckProperties,
|
||||
MemoryWarningProperties,
|
||||
UserJourneyProperties,
|
||||
EnhancedPromptSubmittedProperties,
|
||||
EnhancedToolExecutedProperties,
|
||||
EnhancedErrorProperties,
|
||||
SessionEngagementProperties,
|
||||
FeatureDiscoveryProperties,
|
||||
OutputQualityProperties,
|
||||
ResourceUsageProperties,
|
||||
FeatureAdoptionProperties,
|
||||
FeatureCombinationProperties,
|
||||
AIInteractionProperties,
|
||||
PromptPatternProperties,
|
||||
WorkflowProperties,
|
||||
NetworkPerformanceProperties,
|
||||
SuggestionProperties
|
||||
} from './types';
|
||||
|
||||
export const ANALYTICS_EVENTS = {
|
||||
@@ -15,6 +50,11 @@ export const ANALYTICS_EVENTS = {
|
||||
SESSION_CREATED: 'session_created' as EventName,
|
||||
SESSION_COMPLETED: 'session_completed' as EventName,
|
||||
SESSION_RESUMED: 'session_resumed' as EventName,
|
||||
PROMPT_SUBMITTED: 'prompt_submitted' as EventName,
|
||||
SESSION_STOPPED: 'session_stopped' as EventName,
|
||||
CHECKPOINT_CREATED: 'checkpoint_created' as EventName,
|
||||
CHECKPOINT_RESTORED: 'checkpoint_restored' as EventName,
|
||||
TOOL_EXECUTED: 'tool_executed' as EventName,
|
||||
|
||||
// Feature usage events
|
||||
FEATURE_USED: 'feature_used' as EventName,
|
||||
@@ -24,18 +64,73 @@ export const ANALYTICS_EVENTS = {
|
||||
FILE_OPENED: 'file_opened' as EventName,
|
||||
FILE_EDITED: 'file_edited' as EventName,
|
||||
FILE_SAVED: 'file_saved' as EventName,
|
||||
|
||||
// Agent events
|
||||
AGENT_EXECUTED: 'agent_executed' as EventName,
|
||||
AGENT_STARTED: 'agent_started' as EventName,
|
||||
AGENT_PROGRESS: 'agent_progress' as EventName,
|
||||
AGENT_ERROR: 'agent_error' as EventName,
|
||||
|
||||
// MCP events
|
||||
MCP_SERVER_CONNECTED: 'mcp_server_connected' as EventName,
|
||||
MCP_SERVER_DISCONNECTED: 'mcp_server_disconnected' as EventName,
|
||||
MCP_SERVER_ADDED: 'mcp_server_added' as EventName,
|
||||
MCP_SERVER_REMOVED: 'mcp_server_removed' as EventName,
|
||||
MCP_TOOL_INVOKED: 'mcp_tool_invoked' as EventName,
|
||||
MCP_CONNECTION_ERROR: 'mcp_connection_error' as EventName,
|
||||
|
||||
// Slash command events
|
||||
SLASH_COMMAND_USED: 'slash_command_used' as EventName,
|
||||
SLASH_COMMAND_SELECTED: 'slash_command_selected' as EventName,
|
||||
SLASH_COMMAND_EXECUTED: 'slash_command_executed' as EventName,
|
||||
SLASH_COMMAND_CREATED: 'slash_command_created' as EventName,
|
||||
|
||||
// Settings and system events
|
||||
SETTINGS_CHANGED: 'settings_changed' as EventName,
|
||||
APP_STARTED: 'app_started' as EventName,
|
||||
APP_CLOSED: 'app_closed' as EventName,
|
||||
|
||||
// Error events
|
||||
// Error and performance events
|
||||
ERROR_OCCURRED: 'error_occurred' as EventName,
|
||||
API_ERROR: 'api_error' as EventName,
|
||||
UI_ERROR: 'ui_error' as EventName,
|
||||
PERFORMANCE_BOTTLENECK: 'performance_bottleneck' as EventName,
|
||||
MEMORY_WARNING: 'memory_warning' as EventName,
|
||||
|
||||
// User journey events
|
||||
JOURNEY_MILESTONE: 'journey_milestone' as EventName,
|
||||
USER_RETENTION: 'user_retention' as EventName,
|
||||
|
||||
// AI interaction events
|
||||
AI_INTERACTION: 'ai_interaction' as EventName,
|
||||
PROMPT_PATTERN: 'prompt_pattern' as EventName,
|
||||
|
||||
// Quality events
|
||||
OUTPUT_REGENERATED: 'output_regenerated' as EventName,
|
||||
CONVERSATION_ABANDONED: 'conversation_abandoned' as EventName,
|
||||
SUGGESTION_ACCEPTED: 'suggestion_accepted' as EventName,
|
||||
SUGGESTION_REJECTED: 'suggestion_rejected' as EventName,
|
||||
|
||||
// Workflow events
|
||||
WORKFLOW_STARTED: 'workflow_started' as EventName,
|
||||
WORKFLOW_COMPLETED: 'workflow_completed' as EventName,
|
||||
WORKFLOW_ABANDONED: 'workflow_abandoned' as EventName,
|
||||
|
||||
// Feature adoption events
|
||||
FEATURE_DISCOVERED: 'feature_discovered' as EventName,
|
||||
FEATURE_ADOPTED: 'feature_adopted' as EventName,
|
||||
FEATURE_COMBINATION: 'feature_combination' as EventName,
|
||||
|
||||
// Resource usage events
|
||||
RESOURCE_USAGE_HIGH: 'resource_usage_high' as EventName,
|
||||
RESOURCE_USAGE_SAMPLED: 'resource_usage_sampled' as EventName,
|
||||
|
||||
// Network performance events
|
||||
NETWORK_PERFORMANCE: 'network_performance' as EventName,
|
||||
NETWORK_FAILURE: 'network_failure' as EventName,
|
||||
|
||||
// Engagement events
|
||||
SESSION_ENGAGEMENT: 'session_engagement' as EventName,
|
||||
} as const;
|
||||
|
||||
// Event property builders - help ensure consistent event structure
|
||||
@@ -116,6 +211,437 @@ export const eventBuilders = {
|
||||
...metrics,
|
||||
},
|
||||
}),
|
||||
|
||||
// Claude Code Session event builders
|
||||
promptSubmitted: (props: PromptSubmittedProperties) => ({
|
||||
event: ANALYTICS_EVENTS.PROMPT_SUBMITTED,
|
||||
properties: {
|
||||
category: 'session',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
sessionStopped: (props: SessionStoppedProperties) => ({
|
||||
event: ANALYTICS_EVENTS.SESSION_STOPPED,
|
||||
properties: {
|
||||
category: 'session',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
// Enhanced session stopped with detailed metrics
|
||||
enhancedSessionStopped: (props: EnhancedSessionStoppedProperties) => ({
|
||||
event: ANALYTICS_EVENTS.SESSION_STOPPED,
|
||||
properties: {
|
||||
category: 'session',
|
||||
duration_ms: props.duration_ms,
|
||||
messages_count: props.messages_count,
|
||||
reason: props.reason,
|
||||
// Timing metrics
|
||||
time_to_first_message_ms: props.time_to_first_message_ms,
|
||||
average_response_time_ms: props.average_response_time_ms,
|
||||
idle_time_ms: props.idle_time_ms,
|
||||
// Interaction metrics
|
||||
prompts_sent: props.prompts_sent,
|
||||
tools_executed: props.tools_executed,
|
||||
tools_failed: props.tools_failed,
|
||||
files_created: props.files_created,
|
||||
files_modified: props.files_modified,
|
||||
files_deleted: props.files_deleted,
|
||||
// Content metrics
|
||||
total_tokens_used: props.total_tokens_used,
|
||||
code_blocks_generated: props.code_blocks_generated,
|
||||
errors_encountered: props.errors_encountered,
|
||||
// Session context
|
||||
model: props.model,
|
||||
has_checkpoints: props.has_checkpoints,
|
||||
checkpoint_count: props.checkpoint_count,
|
||||
was_resumed: props.was_resumed,
|
||||
// Agent context
|
||||
agent_type: props.agent_type,
|
||||
agent_name: props.agent_name ? sanitizers.sanitizeAgentName(props.agent_name) : undefined,
|
||||
agent_success: props.agent_success,
|
||||
// Stop context
|
||||
stop_source: props.stop_source,
|
||||
final_state: props.final_state,
|
||||
has_pending_prompts: props.has_pending_prompts,
|
||||
pending_prompts_count: props.pending_prompts_count,
|
||||
},
|
||||
}),
|
||||
|
||||
checkpointCreated: (props: CheckpointCreatedProperties) => ({
|
||||
event: ANALYTICS_EVENTS.CHECKPOINT_CREATED,
|
||||
properties: {
|
||||
category: 'session',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
checkpointRestored: (props: CheckpointRestoredProperties) => ({
|
||||
event: ANALYTICS_EVENTS.CHECKPOINT_RESTORED,
|
||||
properties: {
|
||||
category: 'session',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
toolExecuted: (props: ToolExecutedProperties) => ({
|
||||
event: ANALYTICS_EVENTS.TOOL_EXECUTED,
|
||||
properties: {
|
||||
category: 'session',
|
||||
tool_name: sanitizers.sanitizeToolName(props.tool_name),
|
||||
execution_time_ms: props.execution_time_ms,
|
||||
success: props.success,
|
||||
error_message: props.error_message ? sanitizers.sanitizeErrorMessage(props.error_message) : undefined,
|
||||
},
|
||||
}),
|
||||
|
||||
// Enhanced Agent event builders
|
||||
agentStarted: (props: AgentStartedProperties) => ({
|
||||
event: ANALYTICS_EVENTS.AGENT_STARTED,
|
||||
properties: {
|
||||
category: 'agent',
|
||||
agent_type: props.agent_type,
|
||||
agent_name: props.agent_name ? sanitizers.sanitizeAgentName(props.agent_name) : undefined,
|
||||
has_custom_prompt: props.has_custom_prompt,
|
||||
},
|
||||
}),
|
||||
|
||||
agentProgress: (props: AgentProgressProperties) => ({
|
||||
event: ANALYTICS_EVENTS.AGENT_PROGRESS,
|
||||
properties: {
|
||||
category: 'agent',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
agentError: (props: AgentErrorProperties) => ({
|
||||
event: ANALYTICS_EVENTS.AGENT_ERROR,
|
||||
properties: {
|
||||
category: 'agent',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
// MCP event builders
|
||||
mcpServerAdded: (props: MCPServerAddedProperties) => ({
|
||||
event: ANALYTICS_EVENTS.MCP_SERVER_ADDED,
|
||||
properties: {
|
||||
category: 'mcp',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
mcpServerRemoved: (props: MCPServerRemovedProperties) => ({
|
||||
event: ANALYTICS_EVENTS.MCP_SERVER_REMOVED,
|
||||
properties: {
|
||||
category: 'mcp',
|
||||
server_name: sanitizers.sanitizeServerName(props.server_name),
|
||||
was_connected: props.was_connected,
|
||||
},
|
||||
}),
|
||||
|
||||
mcpToolInvoked: (props: MCPToolInvokedProperties) => ({
|
||||
event: ANALYTICS_EVENTS.MCP_TOOL_INVOKED,
|
||||
properties: {
|
||||
category: 'mcp',
|
||||
server_name: sanitizers.sanitizeServerName(props.server_name),
|
||||
tool_name: sanitizers.sanitizeToolName(props.tool_name),
|
||||
invocation_source: props.invocation_source,
|
||||
},
|
||||
}),
|
||||
|
||||
mcpConnectionError: (props: MCPConnectionErrorProperties) => ({
|
||||
event: ANALYTICS_EVENTS.MCP_CONNECTION_ERROR,
|
||||
properties: {
|
||||
category: 'mcp',
|
||||
server_name: sanitizers.sanitizeServerName(props.server_name),
|
||||
error_type: props.error_type,
|
||||
retry_attempt: props.retry_attempt,
|
||||
},
|
||||
}),
|
||||
|
||||
// Slash Command event builders
|
||||
slashCommandSelected: (props: SlashCommandSelectedProperties) => ({
|
||||
event: ANALYTICS_EVENTS.SLASH_COMMAND_SELECTED,
|
||||
properties: {
|
||||
category: 'slash_command',
|
||||
command_name: sanitizers.sanitizeCommandName(props.command_name),
|
||||
selection_method: props.selection_method,
|
||||
},
|
||||
}),
|
||||
|
||||
slashCommandExecuted: (props: SlashCommandExecutedProperties) => ({
|
||||
event: ANALYTICS_EVENTS.SLASH_COMMAND_EXECUTED,
|
||||
properties: {
|
||||
category: 'slash_command',
|
||||
command_name: sanitizers.sanitizeCommandName(props.command_name),
|
||||
parameters_count: props.parameters_count,
|
||||
execution_time_ms: props.execution_time_ms,
|
||||
},
|
||||
}),
|
||||
|
||||
slashCommandCreated: (props: SlashCommandCreatedProperties) => ({
|
||||
event: ANALYTICS_EVENTS.SLASH_COMMAND_CREATED,
|
||||
properties: {
|
||||
category: 'slash_command',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
// Error and Performance event builders
|
||||
apiError: (props: APIErrorProperties) => ({
|
||||
event: ANALYTICS_EVENTS.API_ERROR,
|
||||
properties: {
|
||||
category: 'error',
|
||||
endpoint: sanitizers.sanitizeEndpoint(props.endpoint),
|
||||
error_code: props.error_code,
|
||||
retry_count: props.retry_count,
|
||||
response_time_ms: props.response_time_ms,
|
||||
},
|
||||
}),
|
||||
|
||||
uiError: (props: UIErrorProperties) => ({
|
||||
event: ANALYTICS_EVENTS.UI_ERROR,
|
||||
properties: {
|
||||
category: 'error',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
performanceBottleneck: (props: PerformanceBottleneckProperties) => ({
|
||||
event: ANALYTICS_EVENTS.PERFORMANCE_BOTTLENECK,
|
||||
properties: {
|
||||
category: 'performance',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
memoryWarning: (props: MemoryWarningProperties) => ({
|
||||
event: ANALYTICS_EVENTS.MEMORY_WARNING,
|
||||
properties: {
|
||||
category: 'performance',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
// User journey event builders
|
||||
journeyMilestone: (props: UserJourneyProperties) => ({
|
||||
event: ANALYTICS_EVENTS.JOURNEY_MILESTONE,
|
||||
properties: {
|
||||
category: 'user_journey',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
// Enhanced prompt submission with more context
|
||||
enhancedPromptSubmitted: (props: EnhancedPromptSubmittedProperties) => ({
|
||||
event: ANALYTICS_EVENTS.PROMPT_SUBMITTED,
|
||||
properties: {
|
||||
category: 'session',
|
||||
prompt_length: props.prompt_length,
|
||||
model: props.model,
|
||||
has_attachments: props.has_attachments,
|
||||
source: props.source,
|
||||
word_count: props.word_count,
|
||||
conversation_depth: props.conversation_depth,
|
||||
prompt_complexity: props.prompt_complexity,
|
||||
contains_code: props.contains_code,
|
||||
language_detected: props.language_detected,
|
||||
session_age_ms: props.session_age_ms,
|
||||
},
|
||||
}),
|
||||
|
||||
// Enhanced tool execution with more context
|
||||
enhancedToolExecuted: (props: EnhancedToolExecutedProperties) => ({
|
||||
event: ANALYTICS_EVENTS.TOOL_EXECUTED,
|
||||
properties: {
|
||||
category: 'session',
|
||||
tool_name: sanitizers.sanitizeToolName(props.tool_name),
|
||||
execution_time_ms: props.execution_time_ms,
|
||||
success: props.success,
|
||||
error_message: props.error_message ? sanitizers.sanitizeErrorMessage(props.error_message) : undefined,
|
||||
tool_category: props.tool_category,
|
||||
consecutive_failures: props.consecutive_failures,
|
||||
retry_attempted: props.retry_attempted,
|
||||
input_size_bytes: props.input_size_bytes,
|
||||
output_size_bytes: props.output_size_bytes,
|
||||
},
|
||||
}),
|
||||
|
||||
// Enhanced error tracking
|
||||
enhancedError: (props: EnhancedErrorProperties) => ({
|
||||
event: ANALYTICS_EVENTS.ERROR_OCCURRED,
|
||||
properties: {
|
||||
category: 'error',
|
||||
error_type: props.error_type,
|
||||
error_code: props.error_code,
|
||||
error_message: props.error_message ? sanitizers.sanitizeErrorMessage(props.error_message) : undefined,
|
||||
context: props.context,
|
||||
user_action_before_error: props.user_action_before_error,
|
||||
recovery_attempted: props.recovery_attempted,
|
||||
recovery_successful: props.recovery_successful,
|
||||
error_frequency: props.error_frequency,
|
||||
stack_trace_hash: props.stack_trace_hash,
|
||||
},
|
||||
}),
|
||||
|
||||
// Session engagement
|
||||
sessionEngagement: (props: SessionEngagementProperties) => ({
|
||||
event: ANALYTICS_EVENTS.SESSION_ENGAGEMENT,
|
||||
properties: {
|
||||
category: 'engagement',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
// Feature discovery
|
||||
featureDiscovered: (props: FeatureDiscoveryProperties) => ({
|
||||
event: ANALYTICS_EVENTS.FEATURE_DISCOVERED,
|
||||
properties: {
|
||||
category: 'feature_adoption',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
// Output quality
|
||||
outputRegenerated: (props: OutputQualityProperties) => ({
|
||||
event: ANALYTICS_EVENTS.OUTPUT_REGENERATED,
|
||||
properties: {
|
||||
category: 'quality',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
// Conversation abandoned
|
||||
conversationAbandoned: (reason: string, messagesCount: number) => ({
|
||||
event: ANALYTICS_EVENTS.CONVERSATION_ABANDONED,
|
||||
properties: {
|
||||
category: 'quality',
|
||||
reason,
|
||||
messages_count: messagesCount,
|
||||
},
|
||||
}),
|
||||
|
||||
// Suggestion tracking
|
||||
suggestionAccepted: (props: SuggestionProperties) => ({
|
||||
event: ANALYTICS_EVENTS.SUGGESTION_ACCEPTED,
|
||||
properties: {
|
||||
category: 'quality',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
suggestionRejected: (props: SuggestionProperties) => ({
|
||||
event: ANALYTICS_EVENTS.SUGGESTION_REJECTED,
|
||||
properties: {
|
||||
category: 'quality',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
// Resource usage
|
||||
resourceUsageHigh: (props: ResourceUsageProperties) => ({
|
||||
event: ANALYTICS_EVENTS.RESOURCE_USAGE_HIGH,
|
||||
properties: {
|
||||
category: 'performance',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
resourceUsageSampled: (props: ResourceUsageProperties) => ({
|
||||
event: ANALYTICS_EVENTS.RESOURCE_USAGE_SAMPLED,
|
||||
properties: {
|
||||
category: 'performance',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
// Feature adoption
|
||||
featureAdopted: (props: FeatureAdoptionProperties) => ({
|
||||
event: ANALYTICS_EVENTS.FEATURE_ADOPTED,
|
||||
properties: {
|
||||
category: 'feature_adoption',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
featureCombination: (props: FeatureCombinationProperties) => ({
|
||||
event: ANALYTICS_EVENTS.FEATURE_COMBINATION,
|
||||
properties: {
|
||||
category: 'feature_adoption',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
// AI interactions
|
||||
aiInteraction: (props: AIInteractionProperties) => ({
|
||||
event: ANALYTICS_EVENTS.AI_INTERACTION,
|
||||
properties: {
|
||||
category: 'ai',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
promptPattern: (props: PromptPatternProperties) => ({
|
||||
event: ANALYTICS_EVENTS.PROMPT_PATTERN,
|
||||
properties: {
|
||||
category: 'ai',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
// Workflow tracking
|
||||
workflowStarted: (props: WorkflowProperties) => ({
|
||||
event: ANALYTICS_EVENTS.WORKFLOW_STARTED,
|
||||
properties: {
|
||||
category: 'workflow',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
workflowCompleted: (props: WorkflowProperties) => ({
|
||||
event: ANALYTICS_EVENTS.WORKFLOW_COMPLETED,
|
||||
properties: {
|
||||
category: 'workflow',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
workflowAbandoned: (props: WorkflowProperties) => ({
|
||||
event: ANALYTICS_EVENTS.WORKFLOW_ABANDONED,
|
||||
properties: {
|
||||
category: 'workflow',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
|
||||
// Network performance
|
||||
networkPerformance: (props: NetworkPerformanceProperties) => ({
|
||||
event: ANALYTICS_EVENTS.NETWORK_PERFORMANCE,
|
||||
properties: {
|
||||
category: 'network',
|
||||
endpoint_type: props.endpoint_type,
|
||||
latency_ms: props.latency_ms,
|
||||
payload_size_bytes: props.payload_size_bytes,
|
||||
connection_quality: props.connection_quality,
|
||||
retry_count: props.retry_count,
|
||||
circuit_breaker_triggered: props.circuit_breaker_triggered,
|
||||
},
|
||||
}),
|
||||
|
||||
networkFailure: (props: NetworkPerformanceProperties) => ({
|
||||
event: ANALYTICS_EVENTS.NETWORK_FAILURE,
|
||||
properties: {
|
||||
category: 'network',
|
||||
endpoint_type: props.endpoint_type,
|
||||
latency_ms: props.latency_ms,
|
||||
payload_size_bytes: props.payload_size_bytes,
|
||||
connection_quality: props.connection_quality,
|
||||
retry_count: props.retry_count,
|
||||
circuit_breaker_triggered: props.circuit_breaker_triggered,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
// Sanitization helpers to remove PII
|
||||
@@ -147,4 +673,28 @@ export const sanitizers = {
|
||||
// Only keep the type, remove custom names
|
||||
return name.split('-')[0] || 'custom';
|
||||
},
|
||||
};
|
||||
|
||||
// Sanitize tool names to remove any user-specific info
|
||||
sanitizeToolName: (name: string): string => {
|
||||
// Remove any path-like structures
|
||||
return name.replace(/\/[\w\-\/\.]+/g, '').toLowerCase();
|
||||
},
|
||||
|
||||
// Sanitize server names to remove any user-specific info
|
||||
sanitizeServerName: (name: string): string => {
|
||||
// Keep only the type or first part
|
||||
return name.split(/[\-_]/)[0] || 'custom';
|
||||
},
|
||||
|
||||
// Sanitize command names
|
||||
sanitizeCommandName: (name: string): string => {
|
||||
// Remove any custom prefixes or user-specific parts
|
||||
return name.replace(/^custom-/, '').split('-')[0] || 'custom';
|
||||
},
|
||||
|
||||
// Sanitize API endpoints
|
||||
sanitizeEndpoint: (endpoint: string): string => {
|
||||
// Remove any dynamic IDs or user-specific parts
|
||||
return endpoint.replace(/\/\d+/g, '/:id').replace(/\/[\w\-]{20,}/g, '/:id');
|
||||
},
|
||||
};
|
||||
|
@@ -11,6 +11,7 @@ import type {
|
||||
export * from './types';
|
||||
export * from './events';
|
||||
export { ConsentManager } from './consent';
|
||||
export { ResourceMonitor, resourceMonitor } from './resourceMonitor';
|
||||
|
||||
class AnalyticsService {
|
||||
private static instance: AnalyticsService;
|
||||
@@ -19,6 +20,7 @@ class AnalyticsService {
|
||||
private config: AnalyticsConfig;
|
||||
private eventQueue: AnalyticsEvent[] = [];
|
||||
private flushInterval: NodeJS.Timeout | null = null;
|
||||
private currentScreen: string = 'app_start';
|
||||
|
||||
private constructor() {
|
||||
this.consentManager = ConsentManager.getInstance();
|
||||
@@ -66,9 +68,11 @@ class AnalyticsService {
|
||||
try {
|
||||
posthog.init(this.config.apiKey, {
|
||||
api_host: this.config.apiHost,
|
||||
defaults: '2025-05-24',
|
||||
capture_exceptions: true,
|
||||
debug: import.meta.env.MODE === 'development',
|
||||
capture_pageview: false, // Disable automatic pageview capture
|
||||
capture_pageleave: false, // Disable automatic pageleave
|
||||
bootstrap: {
|
||||
distinctID: settings.userId,
|
||||
},
|
||||
persistence: this.config.persistence,
|
||||
autocapture: this.config.autocapture,
|
||||
disable_session_recording: this.config.disable_session_recording,
|
||||
@@ -78,6 +82,13 @@ class AnalyticsService {
|
||||
ph.identify(settings.userId, {
|
||||
anonymous: true,
|
||||
consent_date: settings.consentDate,
|
||||
app_type: 'desktop',
|
||||
app_name: 'claudia',
|
||||
});
|
||||
|
||||
// Set initial screen
|
||||
ph.capture('$screen', {
|
||||
$screen_name: 'app_start',
|
||||
});
|
||||
|
||||
// Opt in since user has consented
|
||||
@@ -115,6 +126,17 @@ class AnalyticsService {
|
||||
}
|
||||
}
|
||||
|
||||
setScreen(screenName: string): void {
|
||||
this.currentScreen = screenName;
|
||||
|
||||
// Track screen view in PostHog
|
||||
if (typeof posthog !== 'undefined' && typeof posthog.capture === 'function') {
|
||||
posthog.capture('$screen', {
|
||||
$screen_name: screenName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
track(eventName: EventName | string, properties?: Record<string, any>): void {
|
||||
// Check if analytics is enabled
|
||||
if (!this.consentManager.isEnabled()) {
|
||||
@@ -124,10 +146,17 @@ class AnalyticsService {
|
||||
// Sanitize properties to remove PII
|
||||
const sanitizedProperties = this.sanitizeProperties(properties || {});
|
||||
|
||||
// Add screen context to all events
|
||||
const enhancedProperties = {
|
||||
...sanitizedProperties,
|
||||
screen_name: this.currentScreen,
|
||||
app_context: 'claudia_desktop',
|
||||
};
|
||||
|
||||
// Create event
|
||||
const event: AnalyticsEvent = {
|
||||
event: eventName,
|
||||
properties: sanitizedProperties,
|
||||
properties: enhancedProperties,
|
||||
timestamp: Date.now(),
|
||||
sessionId: this.consentManager.getSessionId(),
|
||||
userId: this.consentManager.getUserId(),
|
||||
@@ -205,6 +234,7 @@ class AnalyticsService {
|
||||
...event.properties,
|
||||
$session_id: event.sessionId,
|
||||
timestamp: event.timestamp,
|
||||
$current_url: `claudia://${event.properties?.screen_name || 'unknown'}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -248,3 +278,74 @@ export const analytics = AnalyticsService.getInstance();
|
||||
|
||||
// Export for direct usage
|
||||
export default analytics;
|
||||
|
||||
/**
|
||||
* Performance tracking utility for better insights
|
||||
*/
|
||||
export class PerformanceTracker {
|
||||
private static performanceData: Map<string, number[]> = new Map();
|
||||
|
||||
/**
|
||||
* Record a performance metric
|
||||
* Automatically tracks percentiles when enough data is collected
|
||||
*/
|
||||
static recordMetric(operation: string, duration: number): void {
|
||||
if (!this.performanceData.has(operation)) {
|
||||
this.performanceData.set(operation, []);
|
||||
}
|
||||
|
||||
const data = this.performanceData.get(operation)!;
|
||||
data.push(duration);
|
||||
|
||||
// Keep last 100 measurements for memory efficiency
|
||||
if (data.length > 100) {
|
||||
data.shift();
|
||||
}
|
||||
|
||||
// Track percentiles every 10 measurements
|
||||
if (data.length >= 10 && data.length % 10 === 0) {
|
||||
const sorted = [...data].sort((a, b) => a - b);
|
||||
const p50 = sorted[Math.floor(sorted.length * 0.5)];
|
||||
const p95 = sorted[Math.floor(sorted.length * 0.95)];
|
||||
const p99 = sorted[Math.floor(sorted.length * 0.99)];
|
||||
|
||||
analytics.track('performance_percentiles', {
|
||||
operation,
|
||||
p50,
|
||||
p95,
|
||||
p99,
|
||||
sample_size: data.length,
|
||||
min: sorted[0],
|
||||
max: sorted[sorted.length - 1],
|
||||
avg: data.reduce((a, b) => a + b, 0) / data.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current statistics for an operation
|
||||
*/
|
||||
static getStats(operation: string): { p50: number; p95: number; p99: number; count: number } | null {
|
||||
const data = this.performanceData.get(operation);
|
||||
if (!data || data.length === 0) return null;
|
||||
|
||||
const sorted = [...data].sort((a, b) => a - b);
|
||||
return {
|
||||
p50: sorted[Math.floor(sorted.length * 0.5)],
|
||||
p95: sorted[Math.floor(sorted.length * 0.95)],
|
||||
p99: sorted[Math.floor(sorted.length * 0.99)],
|
||||
count: data.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear data for an operation or all operations
|
||||
*/
|
||||
static clear(operation?: string): void {
|
||||
if (operation) {
|
||||
this.performanceData.delete(operation);
|
||||
} else {
|
||||
this.performanceData.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -48,7 +48,58 @@ export type EventName =
|
||||
| 'slash_command_used'
|
||||
| 'settings_changed'
|
||||
| 'app_started'
|
||||
| 'app_closed';
|
||||
| 'app_closed'
|
||||
// New session events
|
||||
| 'prompt_submitted'
|
||||
| 'session_stopped'
|
||||
| 'checkpoint_created'
|
||||
| 'checkpoint_restored'
|
||||
| 'tool_executed'
|
||||
// New agent events
|
||||
| 'agent_started'
|
||||
| 'agent_progress'
|
||||
| 'agent_error'
|
||||
// New MCP events
|
||||
| 'mcp_server_added'
|
||||
| 'mcp_server_removed'
|
||||
| 'mcp_tool_invoked'
|
||||
| 'mcp_connection_error'
|
||||
// New slash command events
|
||||
| 'slash_command_selected'
|
||||
| 'slash_command_executed'
|
||||
| 'slash_command_created'
|
||||
// New error and performance events
|
||||
| 'api_error'
|
||||
| 'ui_error'
|
||||
| 'performance_bottleneck'
|
||||
| 'memory_warning'
|
||||
// User journey events
|
||||
| 'journey_milestone'
|
||||
| 'user_retention'
|
||||
// AI interaction events
|
||||
| 'ai_interaction'
|
||||
| 'prompt_pattern'
|
||||
// Quality events
|
||||
| 'output_regenerated'
|
||||
| 'conversation_abandoned'
|
||||
| 'suggestion_accepted'
|
||||
| 'suggestion_rejected'
|
||||
// Workflow events
|
||||
| 'workflow_started'
|
||||
| 'workflow_completed'
|
||||
| 'workflow_abandoned'
|
||||
// Feature adoption events
|
||||
| 'feature_discovered'
|
||||
| 'feature_adopted'
|
||||
| 'feature_combination'
|
||||
// Resource usage events
|
||||
| 'resource_usage_high'
|
||||
| 'resource_usage_sampled'
|
||||
// Network performance events
|
||||
| 'network_performance'
|
||||
| 'network_failure'
|
||||
// Engagement events
|
||||
| 'session_engagement';
|
||||
|
||||
export interface FeatureUsageProperties {
|
||||
feature: string;
|
||||
@@ -99,4 +150,296 @@ export interface PerformanceMetrics {
|
||||
memory_usage_mb?: number;
|
||||
api_response_time_ms?: number;
|
||||
render_time_ms?: number;
|
||||
}
|
||||
}
|
||||
|
||||
// Claude Code Session event properties
|
||||
export interface PromptSubmittedProperties {
|
||||
prompt_length: number;
|
||||
model: string;
|
||||
has_attachments: boolean;
|
||||
source: 'keyboard' | 'button';
|
||||
word_count: number;
|
||||
}
|
||||
|
||||
export interface SessionStoppedProperties {
|
||||
duration_ms: number;
|
||||
messages_count: number;
|
||||
reason: 'user_stopped' | 'error' | 'completed';
|
||||
}
|
||||
|
||||
// Enhanced session stopped properties for detailed analytics
|
||||
export interface EnhancedSessionStoppedProperties extends SessionStoppedProperties {
|
||||
// Timing metrics
|
||||
time_to_first_message_ms?: number;
|
||||
average_response_time_ms?: number;
|
||||
idle_time_ms?: number;
|
||||
|
||||
// Interaction metrics
|
||||
prompts_sent: number;
|
||||
tools_executed: number;
|
||||
tools_failed: number;
|
||||
files_created: number;
|
||||
files_modified: number;
|
||||
files_deleted: number;
|
||||
|
||||
// Content metrics
|
||||
total_tokens_used?: number;
|
||||
code_blocks_generated?: number;
|
||||
errors_encountered: number;
|
||||
|
||||
// Session context
|
||||
model: string;
|
||||
has_checkpoints: boolean;
|
||||
checkpoint_count?: number;
|
||||
was_resumed: boolean;
|
||||
|
||||
// Agent context (if applicable)
|
||||
agent_type?: string;
|
||||
agent_name?: string;
|
||||
agent_success?: boolean;
|
||||
|
||||
// Stop context
|
||||
stop_source: 'user_button' | 'keyboard_shortcut' | 'timeout' | 'error' | 'completed';
|
||||
final_state: 'success' | 'partial' | 'failed' | 'cancelled';
|
||||
has_pending_prompts: boolean;
|
||||
pending_prompts_count?: number;
|
||||
}
|
||||
|
||||
export interface CheckpointCreatedProperties {
|
||||
checkpoint_number: number;
|
||||
session_duration_at_checkpoint: number;
|
||||
}
|
||||
|
||||
export interface CheckpointRestoredProperties {
|
||||
checkpoint_id: string;
|
||||
time_since_checkpoint_ms: number;
|
||||
}
|
||||
|
||||
export interface ToolExecutedProperties {
|
||||
tool_name: string;
|
||||
execution_time_ms: number;
|
||||
success: boolean;
|
||||
error_message?: string;
|
||||
}
|
||||
|
||||
// Enhanced Agent properties
|
||||
export interface AgentStartedProperties {
|
||||
agent_type: string;
|
||||
agent_name?: string;
|
||||
has_custom_prompt: boolean;
|
||||
}
|
||||
|
||||
export interface AgentProgressProperties {
|
||||
step_number: number;
|
||||
step_type: string;
|
||||
duration_ms: number;
|
||||
agent_type: string;
|
||||
}
|
||||
|
||||
export interface AgentErrorProperties {
|
||||
error_type: string;
|
||||
error_stage: string;
|
||||
retry_count: number;
|
||||
agent_type: string;
|
||||
}
|
||||
|
||||
// MCP properties
|
||||
export interface MCPServerAddedProperties {
|
||||
server_type: string;
|
||||
configuration_method: 'manual' | 'preset' | 'import';
|
||||
}
|
||||
|
||||
export interface MCPServerRemovedProperties {
|
||||
server_name: string;
|
||||
was_connected: boolean;
|
||||
}
|
||||
|
||||
export interface MCPToolInvokedProperties {
|
||||
server_name: string;
|
||||
tool_name: string;
|
||||
invocation_source: 'user' | 'agent' | 'suggestion';
|
||||
}
|
||||
|
||||
export interface MCPConnectionErrorProperties {
|
||||
server_name: string;
|
||||
error_type: string;
|
||||
retry_attempt: number;
|
||||
}
|
||||
|
||||
// Slash Command properties
|
||||
export interface SlashCommandSelectedProperties {
|
||||
command_name: string;
|
||||
selection_method: 'click' | 'keyboard' | 'autocomplete';
|
||||
}
|
||||
|
||||
export interface SlashCommandExecutedProperties {
|
||||
command_name: string;
|
||||
parameters_count: number;
|
||||
execution_time_ms: number;
|
||||
}
|
||||
|
||||
export interface SlashCommandCreatedProperties {
|
||||
command_type: 'custom' | 'imported';
|
||||
has_parameters: boolean;
|
||||
}
|
||||
|
||||
// Error and Performance properties
|
||||
export interface APIErrorProperties {
|
||||
endpoint: string;
|
||||
error_code: string | number;
|
||||
retry_count: number;
|
||||
response_time_ms: number;
|
||||
}
|
||||
|
||||
export interface UIErrorProperties {
|
||||
component_name: string;
|
||||
error_type: string;
|
||||
user_action?: string;
|
||||
}
|
||||
|
||||
export interface PerformanceBottleneckProperties {
|
||||
operation_type: string;
|
||||
duration_ms: number;
|
||||
data_size?: number;
|
||||
threshold_exceeded: boolean;
|
||||
}
|
||||
|
||||
export interface MemoryWarningProperties {
|
||||
component: string;
|
||||
memory_mb: number;
|
||||
threshold_exceeded: boolean;
|
||||
gc_count?: number;
|
||||
}
|
||||
|
||||
// User Journey properties
|
||||
export interface UserJourneyProperties {
|
||||
journey_stage: 'onboarding' | 'first_chat' | 'first_agent' | 'power_user';
|
||||
milestone_reached?: string;
|
||||
time_to_milestone_ms?: number;
|
||||
}
|
||||
|
||||
// Enhanced prompt properties
|
||||
export interface EnhancedPromptSubmittedProperties extends PromptSubmittedProperties {
|
||||
conversation_depth: number;
|
||||
prompt_complexity: 'simple' | 'moderate' | 'complex';
|
||||
contains_code: boolean;
|
||||
language_detected?: string;
|
||||
session_age_ms: number;
|
||||
}
|
||||
|
||||
// Enhanced tool properties
|
||||
export interface EnhancedToolExecutedProperties extends ToolExecutedProperties {
|
||||
tool_category: 'file' | 'search' | 'system' | 'custom';
|
||||
consecutive_failures?: number;
|
||||
retry_attempted: boolean;
|
||||
input_size_bytes?: number;
|
||||
output_size_bytes?: number;
|
||||
}
|
||||
|
||||
// Enhanced error properties
|
||||
export interface EnhancedErrorProperties extends ErrorProperties {
|
||||
user_action_before_error?: string;
|
||||
recovery_attempted: boolean;
|
||||
recovery_successful?: boolean;
|
||||
error_frequency: number;
|
||||
stack_trace_hash?: string;
|
||||
}
|
||||
|
||||
// Session engagement properties
|
||||
export interface SessionEngagementProperties {
|
||||
session_duration_ms: number;
|
||||
messages_sent: number;
|
||||
tools_used: string[];
|
||||
files_modified: number;
|
||||
engagement_score: number;
|
||||
}
|
||||
|
||||
// Feature discovery properties
|
||||
export interface FeatureDiscoveryProperties {
|
||||
feature_name: string;
|
||||
discovery_method: 'organic' | 'prompted' | 'documentation';
|
||||
time_to_first_use_ms: number;
|
||||
initial_success: boolean;
|
||||
}
|
||||
|
||||
// Output quality properties
|
||||
export interface OutputQualityProperties {
|
||||
regeneration_count: number;
|
||||
modification_requested: boolean;
|
||||
final_acceptance: boolean;
|
||||
time_to_acceptance_ms: number;
|
||||
}
|
||||
|
||||
// Resource usage properties
|
||||
export interface ResourceUsageProperties {
|
||||
cpu_usage_percent?: number;
|
||||
memory_usage_mb: number;
|
||||
disk_io_mb?: number;
|
||||
network_requests_count: number;
|
||||
cache_hit_rate?: number;
|
||||
active_connections: number;
|
||||
}
|
||||
|
||||
// Feature adoption properties
|
||||
export interface FeatureAdoptionProperties {
|
||||
feature: string;
|
||||
adoption_stage: 'discovered' | 'tried' | 'adopted' | 'abandoned';
|
||||
usage_count: number;
|
||||
days_since_first_use: number;
|
||||
usage_trend: 'increasing' | 'stable' | 'decreasing';
|
||||
}
|
||||
|
||||
// Feature combination properties
|
||||
export interface FeatureCombinationProperties {
|
||||
primary_feature: string;
|
||||
secondary_feature: string;
|
||||
combination_frequency: number;
|
||||
workflow_efficiency_gain?: number;
|
||||
}
|
||||
|
||||
// AI interaction properties
|
||||
export interface AIInteractionProperties {
|
||||
model: string;
|
||||
request_tokens: number;
|
||||
response_tokens: number;
|
||||
response_quality_score?: number;
|
||||
context_switches: number;
|
||||
clarification_requests: number;
|
||||
}
|
||||
|
||||
// Prompt pattern properties
|
||||
export interface PromptPatternProperties {
|
||||
prompt_category: string;
|
||||
prompt_effectiveness: 'high' | 'medium' | 'low';
|
||||
required_iterations: number;
|
||||
final_satisfaction: boolean;
|
||||
}
|
||||
|
||||
// Workflow properties
|
||||
export interface WorkflowProperties {
|
||||
workflow_type: string;
|
||||
steps_completed: number;
|
||||
total_steps: number;
|
||||
duration_ms: number;
|
||||
interruptions: number;
|
||||
completion_rate: number;
|
||||
tools_used: string[];
|
||||
}
|
||||
|
||||
// Network performance properties
|
||||
export interface NetworkPerformanceProperties {
|
||||
endpoint_type: 'mcp' | 'api' | 'webhook';
|
||||
latency_ms: number;
|
||||
payload_size_bytes: number;
|
||||
connection_quality: 'excellent' | 'good' | 'poor';
|
||||
retry_count: number;
|
||||
circuit_breaker_triggered: boolean;
|
||||
}
|
||||
|
||||
// Suggestion properties
|
||||
export interface SuggestionProperties {
|
||||
suggestion_type: string;
|
||||
suggestion_source: string;
|
||||
accepted: boolean;
|
||||
response_time_ms: number;
|
||||
}
|
||||
|
Reference in New Issue
Block a user